Merge pull request #24861 from alexef/new-events-implementation
fix: move from event-source-polyfill to fetch-event-source
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Internal updates to allow reusing Backstage's `fetchApi` implementation for event source requests. This allows you to for example, override the `Authorization` header.
|
||||
@@ -70,6 +70,7 @@
|
||||
"@material-ui/core": "^4.12.2",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@react-hookz/web": "^24.0.0",
|
||||
"@rjsf/core": "5.18.2",
|
||||
"@rjsf/material-ui": "5.18.2",
|
||||
@@ -78,7 +79,6 @@
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"@uiw/react-codemirror": "^4.9.3",
|
||||
"classnames": "^2.2.6",
|
||||
"event-source-polyfill": "^1.0.31",
|
||||
"git-url-parse": "^14.0.0",
|
||||
"humanize-duration": "^3.25.1",
|
||||
"json-schema": "^0.4.0",
|
||||
|
||||
@@ -20,14 +20,13 @@ import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { ScaffolderClient } from './api';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
|
||||
const MockedEventSource = EventSourcePolyfill as jest.MockedClass<
|
||||
typeof EventSourcePolyfill
|
||||
jest.mock('@microsoft/fetch-event-source');
|
||||
const mockFetchEventSource = fetchEventSource as jest.MockedFunction<
|
||||
typeof fetchEventSource
|
||||
>;
|
||||
|
||||
jest.mock('event-source-polyfill');
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
describe('api', () => {
|
||||
@@ -87,26 +86,23 @@ describe('api', () => {
|
||||
describe('streamEvents', () => {
|
||||
describe('eventsource', () => {
|
||||
it('should work', async () => {
|
||||
MockedEventSource.prototype.addEventListener.mockImplementation(
|
||||
(type, fn) => {
|
||||
if (typeof fn !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'log') {
|
||||
fn({
|
||||
data: '{"id":1,"taskId":"a-random-id","type":"log","createdAt":"","body":{"message":"My log message"}}',
|
||||
} as any);
|
||||
} else if (type === 'completion') {
|
||||
fn({
|
||||
data: '{"id":2,"taskId":"a-random-id","type":"completion","createdAt":"","body":{"message":"Finished!"}}',
|
||||
} as any);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const token = 'fake-token';
|
||||
identityApi.getCredentials.mockResolvedValue({ token: token });
|
||||
mockFetchEventSource.mockImplementation(async (_url, options) => {
|
||||
const { onopen, onmessage } = options;
|
||||
await Promise.resolve();
|
||||
await onopen?.({ ok: true } as Response);
|
||||
await Promise.resolve();
|
||||
onmessage?.({
|
||||
id: '',
|
||||
event: 'log',
|
||||
data: '{"id":1,"taskId":"a-random-id","type":"log","createdAt":"","body":{"message":"My log message"}}',
|
||||
});
|
||||
await Promise.resolve();
|
||||
onmessage?.({
|
||||
id: '',
|
||||
event: 'completion',
|
||||
data: '{"id":2,"taskId":"a-random-id","type":"completion","createdAt":"","body":{"message":"Finished!"}}',
|
||||
});
|
||||
});
|
||||
|
||||
const next = jest.fn();
|
||||
|
||||
@@ -116,14 +112,15 @@ describe('api', () => {
|
||||
.subscribe({ next, complete });
|
||||
});
|
||||
|
||||
expect(MockedEventSource).toHaveBeenCalledWith(
|
||||
expect(mockFetchEventSource).toHaveBeenCalledWith(
|
||||
'http://backstage/api/v2/tasks/a-random-task-id/eventstream',
|
||||
{
|
||||
withCredentials: true,
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
fetch: fetchApi.fetch,
|
||||
onmessage: expect.any(Function),
|
||||
onerror: expect.any(Function),
|
||||
signal: expect.any(AbortSignal),
|
||||
},
|
||||
);
|
||||
expect(MockedEventSource.prototype.close).toHaveBeenCalled();
|
||||
|
||||
expect(next).toHaveBeenCalledTimes(2);
|
||||
expect(next).toHaveBeenCalledWith({
|
||||
|
||||
@@ -41,7 +41,10 @@ import {
|
||||
} from '@backstage/plugin-scaffolder-react';
|
||||
|
||||
import queryString from 'qs';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import {
|
||||
EventSourceMessage,
|
||||
fetchEventSource,
|
||||
} from '@microsoft/fetch-event-source';
|
||||
|
||||
/**
|
||||
* An API to interact with the scaffolder backend.
|
||||
@@ -226,11 +229,8 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
params.set('after', String(Number(after)));
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
this.discoveryApi.getBaseUrl('scaffolder'),
|
||||
this.identityApi?.getCredentials(),
|
||||
]).then(
|
||||
([baseUrl, credentials]) => {
|
||||
this.discoveryApi.getBaseUrl('scaffolder').then(
|
||||
baseUrl => {
|
||||
const url = `${baseUrl}/v2/tasks/${encodeURIComponent(
|
||||
taskId,
|
||||
)}/eventstream`;
|
||||
@@ -245,22 +245,25 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
}
|
||||
};
|
||||
|
||||
const eventSource = new EventSourcePolyfill(url, {
|
||||
withCredentials: true,
|
||||
headers: credentials?.token
|
||||
? { Authorization: `Bearer ${credentials.token}` }
|
||||
: {},
|
||||
});
|
||||
eventSource.addEventListener('log', processEvent);
|
||||
eventSource.addEventListener('recovered', processEvent);
|
||||
eventSource.addEventListener('cancelled', processEvent);
|
||||
eventSource.addEventListener('completion', (event: any) => {
|
||||
processEvent(event);
|
||||
eventSource.close();
|
||||
subscriber.complete();
|
||||
});
|
||||
eventSource.addEventListener('error', event => {
|
||||
subscriber.error(event);
|
||||
const ctrl = new AbortController();
|
||||
fetchEventSource(url, {
|
||||
fetch: this.fetchApi.fetch,
|
||||
signal: ctrl.signal,
|
||||
onmessage(e: EventSourceMessage) {
|
||||
if (e.event === 'log') {
|
||||
processEvent(e);
|
||||
return;
|
||||
} else if (e.event === 'completion') {
|
||||
processEvent(e);
|
||||
subscriber.complete();
|
||||
ctrl.abort();
|
||||
return;
|
||||
}
|
||||
processEvent(e);
|
||||
},
|
||||
onerror(err) {
|
||||
subscriber.error(err);
|
||||
},
|
||||
});
|
||||
},
|
||||
error => {
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
const { EventSourcePolyfill } = jest.requireMock('event-source-polyfill');
|
||||
global.EventSource = EventSourcePolyfill;
|
||||
|
||||
// Patch jsdom to add feature used by CodeMirror
|
||||
document.createRange = () => {
|
||||
const range = new Range();
|
||||
|
||||
@@ -465,8 +465,8 @@ export class TechDocsStorageClient implements TechDocsStorageApi_2 {
|
||||
constructor(options: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
fetchApi: FetchApi;
|
||||
identityApi?: IdentityApi;
|
||||
});
|
||||
// (undocumented)
|
||||
configApi: Config;
|
||||
@@ -485,8 +485,6 @@ export class TechDocsStorageClient implements TechDocsStorageApi_2 {
|
||||
getEntityDocs(entityId: CompoundEntityRef, path: string): Promise<string>;
|
||||
// (undocumented)
|
||||
getStorageUrl(): Promise<string>;
|
||||
// (undocumented)
|
||||
identityApi: IdentityApi;
|
||||
syncEntityDocs(
|
||||
entityId: CompoundEntityRef,
|
||||
logHandler?: (line: string) => void,
|
||||
|
||||
@@ -25,11 +25,7 @@ import {
|
||||
techdocsStorageApiRef,
|
||||
} from '../src';
|
||||
|
||||
import {
|
||||
configApiRef,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { configApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
|
||||
import { TechDocsReaderPageProvider } from '@backstage/plugin-techdocs-react';
|
||||
import { Header, Page, TabbedLayout } from '@backstage/core-components';
|
||||
|
||||
@@ -135,7 +131,6 @@ createDevApp()
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
},
|
||||
factory: () => apiBridge,
|
||||
})
|
||||
|
||||
@@ -70,9 +70,9 @@
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.61",
|
||||
"@material-ui/styles": "^4.10.0",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
|
||||
"dompurify": "^3.0.0",
|
||||
"event-source-polyfill": "1.0.25",
|
||||
"git-url-parse": "^14.0.0",
|
||||
"jss": "~10.10.0",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -90,7 +90,6 @@
|
||||
"@testing-library/react": "^15.0.0",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/dompurify": "^3.0.0",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"canvas": "^2.10.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
createApiFactory,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
compatWrapper,
|
||||
@@ -55,14 +54,12 @@ const techDocsStorageApi = createApiExtension({
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ configApi, discoveryApi, identityApi, fetchApi }) =>
|
||||
factory: ({ configApi, discoveryApi, fetchApi }) =>
|
||||
new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -17,16 +17,15 @@
|
||||
import { UrlPatternDiscovery } from '@backstage/core-app-api';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import { MockConfigApi, MockFetchApi } from '@backstage/test-utils';
|
||||
import { TechDocsStorageClient } from './client';
|
||||
|
||||
const MockedEventSource = EventSourcePolyfill as jest.MockedClass<
|
||||
typeof EventSourcePolyfill
|
||||
jest.mock('@microsoft/fetch-event-source');
|
||||
const mockFetchEventSource = fetchEventSource as jest.MockedFunction<
|
||||
typeof fetchEventSource
|
||||
>;
|
||||
|
||||
jest.mock('event-source-polyfill');
|
||||
|
||||
const mockEntity = {
|
||||
kind: 'Component',
|
||||
namespace: 'default',
|
||||
@@ -51,7 +50,6 @@ describe('TechDocsStorageClient', () => {
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
});
|
||||
|
||||
@@ -72,7 +70,6 @@ describe('TechDocsStorageClient', () => {
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
});
|
||||
|
||||
@@ -84,71 +81,50 @@ describe('TechDocsStorageClient', () => {
|
||||
});
|
||||
|
||||
describe('syncEntityDocs', () => {
|
||||
it('should create eventsource without headers', async () => {
|
||||
it('should create eventsource with fetch', async () => {
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
});
|
||||
|
||||
MockedEventSource.prototype.addEventListener.mockImplementation(
|
||||
(type, fn) => {
|
||||
if (type === 'finish' && typeof fn === 'function') {
|
||||
fn({ data: '{"updated": false}' } as any);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
mockFetchEventSource.mockImplementation(async (_url, options) => {
|
||||
const { onopen, onmessage } = options;
|
||||
await Promise.resolve();
|
||||
await onopen?.({ ok: true } as Response);
|
||||
await Promise.resolve();
|
||||
onmessage?.({ id: '', event: 'finish', data: '{"updated": false}' });
|
||||
});
|
||||
identityApi.getCredentials.mockResolvedValue({});
|
||||
await storageApi.syncEntityDocs(mockEntity);
|
||||
|
||||
expect(MockedEventSource).toHaveBeenCalledWith(
|
||||
expect(mockFetchEventSource).toHaveBeenCalledWith(
|
||||
'http://backstage:9191/api/techdocs/sync/default/Component/test-component',
|
||||
{ withCredentials: true, headers: {} },
|
||||
);
|
||||
});
|
||||
|
||||
it('should create eventsource with headers', async () => {
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
});
|
||||
|
||||
MockedEventSource.prototype.addEventListener.mockImplementation(
|
||||
(type, fn) => {
|
||||
if (type === 'finish' && typeof fn === 'function') {
|
||||
fn({ data: '{"updated": false}' } as any);
|
||||
}
|
||||
{
|
||||
fetch: fetchApi.fetch,
|
||||
onerror: expect.any(Function),
|
||||
onmessage: expect.any(Function),
|
||||
signal: expect.any(AbortSignal),
|
||||
},
|
||||
);
|
||||
|
||||
identityApi.getCredentials.mockResolvedValue({ token: 'token' });
|
||||
await storageApi.syncEntityDocs(mockEntity);
|
||||
|
||||
expect(MockedEventSource).toHaveBeenCalledWith(
|
||||
'http://backstage:9191/api/techdocs/sync/default/Component/test-component',
|
||||
{ withCredentials: true, headers: { Authorization: 'Bearer token' } },
|
||||
);
|
||||
});
|
||||
|
||||
it('should resolve to cached', async () => {
|
||||
const myFetchApi = new MockFetchApi({
|
||||
injectIdentityAuth: { identityApi },
|
||||
});
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
fetchApi: myFetchApi,
|
||||
});
|
||||
mockFetchEventSource.mockImplementation(async (_url, options) => {
|
||||
const { onopen, onmessage } = options;
|
||||
await Promise.resolve();
|
||||
await onopen?.({ ok: true } as Response);
|
||||
await Promise.resolve();
|
||||
onmessage?.({ id: '', event: 'finish', data: '{"updated": false}' });
|
||||
});
|
||||
|
||||
MockedEventSource.prototype.addEventListener.mockImplementation(
|
||||
(type, fn) => {
|
||||
if (type === 'finish' && typeof fn === 'function') {
|
||||
fn({ data: '{"updated": false}' } as any);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
identityApi.getCredentials.mockResolvedValue({});
|
||||
await expect(storageApi.syncEntityDocs(mockEntity)).resolves.toEqual(
|
||||
@@ -160,17 +136,15 @@ describe('TechDocsStorageClient', () => {
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
});
|
||||
|
||||
MockedEventSource.prototype.addEventListener.mockImplementation(
|
||||
(type, fn) => {
|
||||
if (type === 'finish' && typeof fn === 'function') {
|
||||
fn({ data: '{"updated": true}' } as any);
|
||||
}
|
||||
},
|
||||
);
|
||||
mockFetchEventSource.mockImplementation(async (_url, options) => {
|
||||
const { onopen, onmessage } = options;
|
||||
await Promise.resolve();
|
||||
await onopen?.({ ok: true } as Response);
|
||||
await Promise.resolve();
|
||||
onmessage?.({ id: '', event: 'finish', data: '{"updated": true}' });
|
||||
});
|
||||
|
||||
identityApi.getCredentials.mockResolvedValue({});
|
||||
await expect(storageApi.syncEntityDocs(mockEntity)).resolves.toEqual(
|
||||
@@ -182,21 +156,18 @@ describe('TechDocsStorageClient', () => {
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
});
|
||||
|
||||
MockedEventSource.prototype.addEventListener.mockImplementation(
|
||||
(type, fn) => {
|
||||
if (type === 'log' && typeof fn === 'function') {
|
||||
fn({ data: '"A log message"' } as any);
|
||||
}
|
||||
|
||||
if (type === 'finish' && typeof fn === 'function') {
|
||||
fn({ data: '{"updated": false}' } as any);
|
||||
}
|
||||
},
|
||||
);
|
||||
mockFetchEventSource.mockImplementation(async (_url, options) => {
|
||||
const { onopen, onmessage } = options;
|
||||
await Promise.resolve();
|
||||
await onopen?.({ ok: true } as Response);
|
||||
await Promise.resolve();
|
||||
onmessage?.({ id: '', event: 'log', data: '"A log message"' });
|
||||
await Promise.resolve();
|
||||
onmessage?.({ id: '', event: 'finish', data: '{"updated": false}' });
|
||||
});
|
||||
|
||||
identityApi.getCredentials.mockResolvedValue({});
|
||||
const logHandler = jest.fn();
|
||||
@@ -212,25 +183,18 @@ describe('TechDocsStorageClient', () => {
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
});
|
||||
|
||||
mockFetchEventSource.mockImplementation(async (_url, options) => {
|
||||
const { onerror } = options;
|
||||
onerror?.(new NotFoundError('Some not found warning'));
|
||||
});
|
||||
|
||||
// we await later after we emitted the error
|
||||
identityApi.getCredentials.mockResolvedValue({});
|
||||
const promise = storageApi.syncEntityDocs(mockEntity).then();
|
||||
|
||||
// flush the event loop
|
||||
await new Promise(r => setTimeout(r));
|
||||
|
||||
const instance = MockedEventSource.mock
|
||||
.instances[0] as jest.Mocked<EventSource>;
|
||||
|
||||
instance.onerror?.({
|
||||
status: 404,
|
||||
message: 'Some not found warning',
|
||||
} as any);
|
||||
|
||||
await expect(promise).rejects.toThrow(NotFoundError);
|
||||
await expect(promise).rejects.toThrow('Some not found warning');
|
||||
});
|
||||
@@ -239,7 +203,6 @@ describe('TechDocsStorageClient', () => {
|
||||
const storageApi = new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
});
|
||||
|
||||
@@ -247,16 +210,10 @@ describe('TechDocsStorageClient', () => {
|
||||
identityApi.getCredentials.mockResolvedValue({});
|
||||
const promise = storageApi.syncEntityDocs(mockEntity).then();
|
||||
|
||||
// flush the event loop
|
||||
await new Promise(r => setTimeout(r));
|
||||
|
||||
const instance = MockedEventSource.mock
|
||||
.instances[0] as jest.Mocked<EventSource>;
|
||||
|
||||
instance.onerror?.({
|
||||
type: 'error',
|
||||
data: 'Some other error',
|
||||
} as any);
|
||||
mockFetchEventSource.mockImplementation(async (_url, options) => {
|
||||
const { onerror } = options;
|
||||
onerror?.(new Error('Some other error'));
|
||||
});
|
||||
|
||||
await expect(promise).rejects.toThrow(Error);
|
||||
await expect(promise).rejects.toThrow('Some other error');
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
TechDocsMetadata,
|
||||
TechDocsStorageApi,
|
||||
} from '@backstage/plugin-techdocs-react';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
|
||||
/**
|
||||
* API to talk to `techdocs-backend`.
|
||||
@@ -124,18 +124,17 @@ export class TechDocsClient implements TechDocsApi {
|
||||
export class TechDocsStorageClient implements TechDocsStorageApi {
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
public identityApi: IdentityApi;
|
||||
private fetchApi: FetchApi;
|
||||
|
||||
constructor(options: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
identityApi: IdentityApi;
|
||||
fetchApi: FetchApi;
|
||||
/** @deprecated identityApi is not needed any more */
|
||||
identityApi?: IdentityApi;
|
||||
}) {
|
||||
this.configApi = options.configApi;
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.identityApi = options.identityApi;
|
||||
this.fetchApi = options.fetchApi;
|
||||
}
|
||||
|
||||
@@ -213,47 +212,32 @@ export class TechDocsStorageClient implements TechDocsStorageApi {
|
||||
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const url = `${apiOrigin}/sync/${namespace}/${kind}/${name}`;
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// Polyfill is used to add support for custom headers and auth
|
||||
const source = new EventSourcePolyfill(url, {
|
||||
withCredentials: true,
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
|
||||
source.addEventListener('log', (e: any) => {
|
||||
if (e.data) {
|
||||
logHandler(JSON.parse(e.data));
|
||||
}
|
||||
});
|
||||
|
||||
source.addEventListener('finish', (e: any) => {
|
||||
let updated: boolean = false;
|
||||
|
||||
if (e.data) {
|
||||
({ updated } = JSON.parse(e.data));
|
||||
}
|
||||
|
||||
resolve(updated ? 'updated' : 'cached');
|
||||
});
|
||||
|
||||
source.onerror = (e: any) => {
|
||||
source.close();
|
||||
|
||||
switch (e.status) {
|
||||
// the endpoint returned a 404 status
|
||||
case 404:
|
||||
reject(new NotFoundError(e.message));
|
||||
return;
|
||||
|
||||
// also handles the event-stream close. the reject is ignored if the Promise was already
|
||||
// resolved by a finish event.
|
||||
default:
|
||||
const ctrl = new AbortController();
|
||||
fetchEventSource(url, {
|
||||
fetch: this.fetchApi.fetch,
|
||||
signal: ctrl.signal,
|
||||
onmessage(e: any) {
|
||||
if (e.event === 'log') {
|
||||
if (e.data) {
|
||||
logHandler(JSON.parse(e.data));
|
||||
}
|
||||
} else if (e.event === 'finish') {
|
||||
let updated: boolean = false;
|
||||
if (e.data) {
|
||||
({ updated } = JSON.parse(e.data));
|
||||
}
|
||||
resolve(updated ? 'updated' : 'cached');
|
||||
} else if (e.event === 'error') {
|
||||
reject(new Error(e.data));
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
onerror(err) {
|
||||
ctrl.abort();
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
declare module 'event-source-polyfill' {
|
||||
export class EventSourcePolyfill extends EventSource {
|
||||
constructor(
|
||||
url: string,
|
||||
options?: {
|
||||
withCredentials?: boolean;
|
||||
headers?: HeadersInit;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
createRoutableExtension,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
createSearchResultListItemExtension,
|
||||
@@ -52,14 +51,12 @@ export const techdocsPlugin = createPlugin({
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
identityApi: identityApiRef,
|
||||
fetchApi: fetchApiRef,
|
||||
},
|
||||
factory: ({ configApi, discoveryApi, identityApi, fetchApi }) =>
|
||||
factory: ({ configApi, discoveryApi, fetchApi }) =>
|
||||
new TechDocsStorageClient({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
identityApi,
|
||||
fetchApi,
|
||||
}),
|
||||
}),
|
||||
|
||||
@@ -6979,6 +6979,7 @@ __metadata:
|
||||
"@material-ui/core": ^4.12.2
|
||||
"@material-ui/icons": ^4.9.1
|
||||
"@material-ui/lab": 4.0.0-alpha.61
|
||||
"@microsoft/fetch-event-source": ^2.0.1
|
||||
"@react-hookz/web": ^24.0.0
|
||||
"@rjsf/core": 5.18.2
|
||||
"@rjsf/material-ui": 5.18.2
|
||||
@@ -6993,7 +6994,6 @@ __metadata:
|
||||
"@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
"@uiw/react-codemirror": ^4.9.3
|
||||
classnames: ^2.2.6
|
||||
event-source-polyfill: ^1.0.31
|
||||
git-url-parse: ^14.0.0
|
||||
humanize-duration: ^3.25.1
|
||||
json-schema: ^0.4.0
|
||||
@@ -7557,16 +7557,15 @@ __metadata:
|
||||
"@material-ui/icons": ^4.9.1
|
||||
"@material-ui/lab": 4.0.0-alpha.61
|
||||
"@material-ui/styles": ^4.10.0
|
||||
"@microsoft/fetch-event-source": ^2.0.1
|
||||
"@testing-library/dom": ^10.0.0
|
||||
"@testing-library/jest-dom": ^6.0.0
|
||||
"@testing-library/react": ^15.0.0
|
||||
"@testing-library/user-event": ^14.0.0
|
||||
"@types/dompurify": ^3.0.0
|
||||
"@types/event-source-polyfill": ^1.0.0
|
||||
"@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0
|
||||
canvas: ^2.10.2
|
||||
dompurify: ^3.0.0
|
||||
event-source-polyfill: 1.0.25
|
||||
git-url-parse: ^14.0.0
|
||||
jss: ~10.10.0
|
||||
lodash: ^4.17.21
|
||||
@@ -10529,6 +10528,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/fetch-event-source@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "@microsoft/fetch-event-source@npm:2.0.1"
|
||||
checksum: a50e1c0f33220206967266d0a4bbba0703e2793b079d9f6e6bfd48f71b2115964a803e14cf6e902c6fab321edc084f26022334f5eaacc2cec87f174715d41852
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@microsoft/microsoft-graph-types@npm:^2.6.0":
|
||||
version: 2.40.0
|
||||
resolution: "@microsoft/microsoft-graph-types@npm:2.40.0"
|
||||
@@ -17149,13 +17155,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/event-source-polyfill@npm:^1.0.0":
|
||||
version: 1.0.5
|
||||
resolution: "@types/event-source-polyfill@npm:1.0.5"
|
||||
checksum: f506b68710162f2ade1bccbc5691b8c67e5a703e565df2bc0b7b5be2637ba838ef81ec6c10b03248fe4d054386d95a6e827c7aace6e924986c2b9985f77b55de
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/expect@npm:^1.20.4":
|
||||
version: 1.20.4
|
||||
resolution: "@types/expect@npm:1.20.4"
|
||||
@@ -25522,20 +25521,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"event-source-polyfill@npm:1.0.25":
|
||||
version: 1.0.25
|
||||
resolution: "event-source-polyfill@npm:1.0.25"
|
||||
checksum: ed30428cc80eadfd693d267ba4a72dceaae938174cd116081ce38ad62bfd95f199430be7e8341e6f8f1e29489bbd5cfd4b3f6c8d6d463435623f7f91ae5f71b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"event-source-polyfill@npm:^1.0.31":
|
||||
version: 1.0.31
|
||||
resolution: "event-source-polyfill@npm:1.0.31"
|
||||
checksum: 973f226404e2a1b14ed7ef15c718b89e213b41d7cfeeb1c10937fd09229f13904f3d7c3075ab28ccf858c213007559908eecdd577577330352f53a351383dd75
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"event-target-shim@npm:^5.0.0":
|
||||
version: 5.0.1
|
||||
resolution: "event-target-shim@npm:5.0.1"
|
||||
|
||||
Reference in New Issue
Block a user