Skip to content

Commit

Permalink
refactor(client): allow to close gracefully the current event source (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Thenkei authored Jul 10, 2023
1 parent 045a6c5 commit 0712dea
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 11 deletions.
10 changes: 10 additions & 0 deletions packages/agent/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ export default class Agent<S extends TSchema = TSchema> extends FrameworkMounter
await this.mount(router);
}

/**
* Stop the agent.
*/
override async stop(): Promise<void> {
// Close anything related to ForestAdmin client
this.options.forestAdminClient.close();
// Stop at framework level
await super.stop();
}

/**
* Restart the agent at runtime (remount routes).
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/agent/src/framework-mounter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default class FrameworkMounter {
for (const task of this.onEachStart) await task(router); // eslint-disable-line no-await-in-loop
}

async stop(): Promise<void> {
public async stop(): Promise<void> {
for (const task of this.onStop) await task(); // eslint-disable-line no-await-in-loop
}

Expand Down
1 change: 1 addition & 0 deletions packages/agent/test/__factories__/forest-admin-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const forestAdminClientFactory = ForestAdminClientFactory.define(() => ({
getConfiguration: jest.fn(),
},
subscribeToServerEvents: jest.fn(),
close: jest.fn(),
onRefreshCustomizations: jest.fn(),
}));

Expand Down
11 changes: 11 additions & 0 deletions packages/agent/test/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,4 +280,15 @@ describe('Agent', () => {
expect(mockPostSchema).not.toHaveBeenCalled();
});
});

describe('stop', () => {
test('stop should close the Forest Admin client', async () => {
const options = factories.forestAdminHttpDriverOptions.build();
const agent = new Agent(options);

await agent.stop();

expect(options.forestAdminClient.close).toHaveBeenCalledTimes(1);
});
});
});
29 changes: 21 additions & 8 deletions packages/forestadmin-client/src/events-subscription/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
import { ForestAdminClientOptionsWithDefaults } from '../types';

export default class EventsSubscriptionService implements BaseEventsSubscriptionService {
private eventSource: EventSource;

constructor(
private readonly options: ForestAdminClientOptionsWithDefaults,
private readonly refreshEventsHandlerService: RefreshEventsHandlerService,
Expand All @@ -35,30 +37,41 @@ export default class EventsSubscriptionService implements BaseEventsSubscription
};
const url = new URL('/liana/v4/subscribe-to-events', this.options.forestServerUrl).toString();

const source = new EventSource(url, eventSourceConfig);
const eventSource = new EventSource(url, eventSourceConfig);
// Override reconnect interval to 5 seconds
source.reconnectInterval = 5000;
eventSource.reconnectInterval = 5000;

source.addEventListener('error', this.onEventError.bind(this));
eventSource.addEventListener('error', this.onEventError.bind(this));

// Only listen after first open
source.once('open', () => source.addEventListener('open', () => this.onEventOpenAgain()));
eventSource.once('open', () =>
eventSource.addEventListener('open', () => this.onEventOpenAgain()),
);

source.addEventListener(ServerEventType.RefreshUsers, async () =>
eventSource.addEventListener(ServerEventType.RefreshUsers, async () =>
this.refreshEventsHandlerService.refreshUsers(),
);

source.addEventListener(ServerEventType.RefreshRoles, async () =>
eventSource.addEventListener(ServerEventType.RefreshRoles, async () =>
this.refreshEventsHandlerService.refreshRoles(),
);

source.addEventListener(ServerEventType.RefreshRenderings, async (event: ServerEvent) =>
eventSource.addEventListener(ServerEventType.RefreshRenderings, async (event: ServerEvent) =>
this.handleSeverEventRefreshRenderings(event),
);

source.addEventListener(ServerEventType.RefreshCustomizations, async () =>
eventSource.addEventListener(ServerEventType.RefreshCustomizations, async () =>
this.refreshEventsHandlerService.refreshCustomizations(),
);

this.eventSource = eventSource;
}

/**
* Close the current EventSource
*/
public close() {
this.eventSource?.close();
}

private async handleSeverEventRefreshRenderings(event: ServerEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ export interface RefreshEventsHandlerService {
*/
export interface BaseEventsSubscriptionService {
subscribeEvents(): Promise<void>;
close(): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export default class ForestAdminClientWithCache implements ForestAdminClient {
await this.eventsSubscription.subscribeEvents();
}

public close() {
this.eventsSubscription.close();
}

public onRefreshCustomizations(handler: () => void | Promise<void>) {
this.eventsHandler.onRefreshCustomizations(handler);
}
Expand Down
1 change: 1 addition & 0 deletions packages/forestadmin-client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export interface ForestAdminClient {
markScopesAsUpdated(renderingId: number | string): void;

subscribeToServerEvents(): Promise<void>;
close(): void;
onRefreshCustomizations(handler: () => void | Promise<void>): void;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class EventsSubscriptionServiceFactory extends Factory<EventsSubscription
mockAllMethods() {
return this.afterBuild(service => {
service.subscribeEvents = jest.fn();
service.close = jest.fn();
});
}
}
Expand Down
37 changes: 37 additions & 0 deletions packages/forestadmin-client/test/events-subscription/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ const once = jest.fn((event, callback) => {
events[event] = callback;
});

const close = jest.fn();

jest.mock('eventsource', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
addEventListener,
once,
close,
})),
}));

Expand Down Expand Up @@ -66,6 +69,40 @@ describe('EventsSubscriptionService', () => {
});
});

describe('close', () => {
test('should close current Event Source', async () => {
const eventsSubscriptionService = factories.eventsSubscription.build();
eventsSubscriptionService.subscribeEvents();

eventsSubscriptionService.close();

expect(close).toHaveBeenCalled();
});

describe('when server events are deactivated', () => {
test('should not do anything', async () => {
const eventsSubscriptionService = new EventsSubscriptionService(
{ ...options, instantCacheRefresh: false },
refreshEventsHandlerService,
);

eventsSubscriptionService.close();

expect(close).not.toHaveBeenCalled();
});
});

describe('when no event source instantiated yet', () => {
test('should not do anything', async () => {
const eventsSubscriptionService = factories.eventsSubscription.build();

eventsSubscriptionService.close();

expect(close).not.toHaveBeenCalled();
});
});
});

describe('handleSeverEvents', () => {
describe('on RefreshUsers event', () => {
test('should delegate to refreshEventsHandlerService', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ describe('ForestAdminClientWithCache', () => {
});

describe('subscribeToServerEvents', () => {
it('should subscribes to Server Events rendering service', async () => {
it('should subscribes to Server Events service', async () => {
const eventsSubscriptionService = factories.eventsSubscription.mockAllMethods().build();
const forestAdminClient = new ForestAdminClient(
factories.forestAdminClientOptions.build(),
Expand All @@ -254,8 +254,31 @@ describe('ForestAdminClientWithCache', () => {
});
});

describe('close', () => {
it('should close', () => {
const eventsSubscriptionService = factories.eventsSubscription.mockAllMethods().build();
const forestAdminClient = new ForestAdminClient(
factories.forestAdminClientOptions.build(),
factories.permission.build(),
factories.renderingPermission.build(),
factories.contextVariablesInstantiator.build(),
factories.chartHandler.build(),
factories.ipWhiteList.build(),
factories.schema.build(),
factories.auth.build(),
factories.modelCustomization.build(),
eventsSubscriptionService,
factories.eventsHandler.build(),
);

forestAdminClient.close();

expect(eventsSubscriptionService.close).toHaveBeenCalledWith();
});
});

describe('onRefreshCustomizations', () => {
it('should subscribes to Server Events rendering service', async () => {
it('should subscribes the handler to the eventsHandler service', async () => {
const eventsHandlerService = factories.eventsHandler.mockAllMethods().build();
const forestAdminClient = new ForestAdminClient(
factories.forestAdminClientOptions.build(),
Expand Down

0 comments on commit 0712dea

Please sign in to comment.