Skip to content

Commit

Permalink
feat: add onServiceMessage callback option for ServiceMessage han…
Browse files Browse the repository at this point in the history
…dling
  • Loading branch information
maxokorokov committed Feb 27, 2025
1 parent 7ef343a commit 7f7e81c
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 56 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"@eslint/js": "^9.20.0",
"@types/jest": "^29.5.14",
"angular-eslint": "^19.1.0",
"core-js": "^3.40.0",
"eslint": "^9.20.1",
"fs-extra": "^11.3.0",
"husky": "^9.1.7",
Expand Down
9 changes: 7 additions & 2 deletions packages/angular/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
import { setupZonelessTestEnv } from 'jest-preset-angular/setup-env/zoneless';

setupZoneTestEnv();
setupZonelessTestEnv();

/**
* structured clone polyfill required in the JSDOM environment
*/
import 'core-js/stable/structured-clone.js';
80 changes: 59 additions & 21 deletions packages/angular/lib/src/message-peer.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TestBed } from '@angular/core/testing';
import { Injector } from '@angular/core';
import {
MESSAGE_PEER_CONFIG,
MESSAGE_PEER_CONNECT_OPTIONS,
Expand All @@ -12,19 +12,16 @@ import { MessagePeer } from '@amadeus-it-group/microfrontends';

describe('MessagePeerService', () => {
let service: MessagePeerServiceType<Message>;
let config: MessagePeerConfig;
const config: MessagePeerConfig = { id: 'test-peer' };

beforeEach(() => {
config = {
id: 'test-peer',
knownMessages: [],
};

TestBed.configureTestingModule({
service = Injector.create({
providers: [MessagePeerService, { provide: MESSAGE_PEER_CONFIG, useValue: config }],
});
}).get(MessagePeerService);
});

service = TestBed.inject(MessagePeerService);
afterEach(() => {
jest.restoreAllMocks();
});

it('should be created', () => {
Expand Down Expand Up @@ -70,9 +67,57 @@ describe('MessagePeerService', () => {
});
});

describe('MessagePeerService Interactions', () => {
const knownMessages: Message[] = [{ type: 'test', version: '1.0' }];

let s1: MessagePeerServiceType<any>;
let s2: MessagePeerServiceType<any>;

beforeEach(() => {
s1 = Injector.create({
providers: [
MessagePeerService,
{ provide: MESSAGE_PEER_CONFIG, useValue: { id: 's1', knownMessages } },
],
}).get(MessagePeerService);

s2 = Injector.create({
providers: [
MessagePeerService,
{ provide: MESSAGE_PEER_CONFIG, useValue: { id: 's2', knownMessages } },
],
}).get(MessagePeerService);
});

afterEach(() => {
jest.restoreAllMocks();
});

test('should connect and exchange messages', () => {
const messages: any[] = [];
const serviceMessages: any[] = [];
s2.messages$.subscribe(({ payload }) =>
messages.push({ type: payload.type, version: payload.version }),
);
s2.serviceMessages$.subscribe(({ payload }) =>
serviceMessages.push({ type: payload.type, version: payload.version }),
);

s1.listen('s2');
s2.connect('s1');

s1.send({ type: 'test', version: '1.0' });

expect(messages).toEqual([{ type: 'test', version: '1.0' }]);
expect(serviceMessages).toEqual([{ type: 'connect', version: '1.0' }]);
});
});

describe('MessagePeerService DI overrides', () => {
let connectSpy: jest.SpyInstance;
let listenSpy: jest.SpyInstance;
let injector: Injector;
const knownMessages = [{ type: 'test', version: '1.0' }];

const connectOptions: PeerConnectionOptions = {
window: 'mocked connect window' as any,
Expand All @@ -88,29 +133,22 @@ describe('MessagePeerService DI overrides', () => {
connectSpy = jest.spyOn(MessagePeer.prototype, 'connect').mockImplementation();
listenSpy = jest.spyOn(MessagePeer.prototype, 'listen').mockImplementation();

TestBed.configureTestingModule({
injector = Injector.create({
providers: [
MessagePeerService,
{
provide: MESSAGE_PEER_CONFIG,
useValue: {
id: 'blah',
knownMessages: [{ type: 'test', version: '1.0' }],
},
},
{ provide: MESSAGE_PEER_CONFIG, useValue: { id: 'blah', knownMessages } },
{ provide: MESSAGE_PEER_CONNECT_OPTIONS, useValue: connectOptions },
{ provide: MESSAGE_PEER_LISTEN_OPTIONS, useValue: listenOptions },
],
});
});

afterEach(() => {
connectSpy.mockRestore();
listenSpy.mockRestore();
jest.restoreAllMocks();
});

test('should use the provided DI options', () => {
const service = TestBed.inject(MessagePeerService);
const service = injector.get(MessagePeerService);

// create options
expect(service.id).toBe('blah');
Expand Down
14 changes: 14 additions & 0 deletions packages/angular/lib/src/message-peer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
PeerConnectionOptions,
PeerSendOptions,
RoutedMessage,
ServiceMessage,
} from '@amadeus-it-group/microfrontends';
import { MessagePeer } from '@amadeus-it-group/microfrontends';
import { Observable, Subject } from 'rxjs';
Expand All @@ -17,6 +18,10 @@ export interface MessagePeerServiceType<M extends Message> extends MessagePeerTy
* Observable for incoming messages
*/
get messages$(): Observable<RoutedMessage<M>>;
/**
* Observable for incoming service messages
*/
get serviceMessages$(): Observable<RoutedMessage<ServiceMessage>>;
}

/**
Expand Down Expand Up @@ -60,6 +65,7 @@ export const MESSAGE_PEER_LISTEN_OPTIONS = new InjectionToken<PeerConnectionOpti
export class MessagePeerService<M extends Message> implements MessagePeerServiceType<M> {
readonly #peer: MessagePeerType<M>;
readonly #messages$ = new Subject<RoutedMessage<M>>();
readonly #serviceMessages$ = new Subject<RoutedMessage<ServiceMessage>>();

readonly #diConnectOptions = inject(MESSAGE_PEER_CONNECT_OPTIONS, { optional: true });
readonly #diListenOptions = inject(MESSAGE_PEER_LISTEN_OPTIONS, { optional: true });
Expand All @@ -69,6 +75,7 @@ export class MessagePeerService<M extends Message> implements MessagePeerService
this.#peer = new MessagePeer<M>({
id: config.id,
onMessage: (message) => this.#messages$.next(message),
onServiceMessage: (message) => this.#serviceMessages$.next(message),
knownMessages: config.knownMessages,
});
}
Expand Down Expand Up @@ -114,6 +121,13 @@ export class MessagePeerService<M extends Message> implements MessagePeerService
return this.#messages$.asObservable();
}

/**
* @inheritDoc
*/
public get serviceMessages$(): Observable<RoutedMessage<ServiceMessage>> {
return this.#serviceMessages$.asObservable();
}

/**
* @inheritDoc
*/
Expand Down
11 changes: 7 additions & 4 deletions packages/core-e2e/cases/switch-connection/client-0.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ <h3>Messages</h3>

const knownMessages = [{ type: 'test-host', version: '1.0' }];

function onMessage(m) {
const messages = document.getElementById('messages');
messages.innerHTML += `<div class="${m.from}-${m.payload.type}">${m.from}-${m.payload.type}</div>`;
}

const client = new MessagePeer({
id: 'client-0',
onMessage: (m) => {
const messages = document.getElementById('messages');
messages.innerHTML += `<div class="${m.from}-${m.payload.type}">${m.from}-${m.payload.type}</div>`;
},
onMessage,
onServiceMessage: onMessage,
knownMessages,
});

Expand Down
11 changes: 7 additions & 4 deletions packages/core-e2e/cases/switch-connection/client-1.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ <h3>Messages</h3>

const knownMessages = [{ type: 'test-host', version: '1.0' }];

function onMessage(m) {
const messages = document.getElementById('messages');
messages.innerHTML += `<div class="${m.from}-${m.payload.type}">${m.from}-${m.payload.type}</div>`;
}

const client = new MessagePeer({
id: 'client-1',
onMessage: (m) => {
const messages = document.getElementById('messages');
messages.innerHTML += `<div class="${m.from}-${m.payload.type}">${m.from}-${m.payload.type}</div>`;
},
onMessage,
onServiceMessage: onMessage,
knownMessages,
});

Expand Down
34 changes: 18 additions & 16 deletions packages/core-e2e/cases/switch-connection/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,27 @@ <h3>Disconnected clients</h3>
{ type: 'test-client1', version: '1.0' },
];

function onMessage(m) {
if (m.payload?.type === 'disconnect') {
const disconnectM = document.getElementById('disconnected');
disconnectM.innerHTML += `<div class="${m.from}-${m.payload.type}">client disconnected: ${m.from}-${m.payload.type}</div>`;
const clientId = m.from === 'client-0' ? 'client-1' : 'client-0';
// when a client is disconnected create the other one
createClient(clientId, 'http://localhost:8092');
} else if (m.payload?.type === 'connect') {
const connectMessages = document.getElementById('connect');
connectMessages.innerHTML += `<div class="${m.from}-${m.payload.type}">${m.from}-${m.payload.type}</div>`;
} else {
const messages = document.getElementById('messages');
messages.innerHTML += `<div class="${m.from}-${m.payload.type}">${m.from}-${m.payload.type}</div>`;
}
}

// create host peer
const host = new MessagePeer({
id: 'host',
onMessage: (m) => {
console.log('ON MESSAGE', m);
if (m.payload?.type === 'disconnect') {
const disconnectM = document.getElementById('disconnected');
disconnectM.innerHTML += `<div class="${m.from}-${m.payload.type}">client disconnected: ${m.from}-${m.payload.type}</div>`;
const clientId = m.from === 'client-0' ? 'client-1' : 'client-0';
// when a client is disconnected create the other one
createClient(clientId, 'http://localhost:8092');
} else if (m.payload?.type === 'connect') {
const connectMessages = document.getElementById('connect');
connectMessages.innerHTML += `<div class="${m.from}-${m.payload.type}">${m.from}-${m.payload.type}</div>`;
} else {
const messages = document.getElementById('messages');
messages.innerHTML += `<div class="${m.from}-${m.payload.type}">${m.from}-${m.payload.type}</div>`;
}
},
onMessage,
onServiceMessage: onMessage,
knownMessages,
});

Expand Down
1 change: 0 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@
}
},
"devDependencies": {
"core-js": "^3.40.0",
"esbuild": "^0.25.0"
}
}
45 changes: 44 additions & 1 deletion packages/core/src/peer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { MessagePeerType } from './peer';
import { MessagePeer } from './peer';
import { MessageError } from './message-error';
import type { Message } from './message';
import type { Message, RoutedMessage, ServiceMessage } from './message';

/**
* Mocking window event listeners to establish connection via custom events
Expand Down Expand Up @@ -51,6 +51,7 @@ describe('Peer', () => {
id,
knownMessages: messages,
onMessage: (message: any) => onMessage({ [id]: message }),
onServiceMessage: (message: any) => onMessage({ [id]: message }),
onError: (error: any) => onError({ [id]: error }),
});
}
Expand Down Expand Up @@ -906,4 +907,46 @@ describe('Peer', () => {
]);
expectErrors(onError, []);
});

test(`should separate user messages and service messages`, () => {
const onMessage = jest.fn();
const onServiceMessage = jest.fn();

const one = new MessagePeer<Message | ServiceMessage>({ id: 'one', knownMessages });
const two = new MessagePeer<Message | ServiceMessage>({
id: 'two',
knownMessages,
onMessage: ({ payload }: RoutedMessage<Message>) =>
onMessage({ type: payload.type, version: payload.version }),
onServiceMessage: ({ payload }: RoutedMessage<ServiceMessage>) =>
onServiceMessage({ type: payload.type, version: payload.version }),
});

// initial connect
one.listen('two');
two.connect('one');

expect(onMessage.mock.calls.length).toBe(0);
expect(onServiceMessage.mock.calls.length).toBe(1); // initial connect message

// normal message
one.send({ type: 'known', version: '1.0' });

// declare messages
one.send({
type: 'declare_messages',
version: '1.0',
messages: [],
});

// disconnect message
one.disconnect('two');

expect(onMessage.mock.calls).toEqual([[{ type: 'known', version: '1.0' }]]);
expect(onServiceMessage.mock.calls).toEqual([
[{ type: 'connect', version: '1.0' }],
[{ type: 'declare_messages', version: '1.0' }],
[{ type: 'disconnect', version: '1.0' }],
]);
});
});
Loading

0 comments on commit 7f7e81c

Please sign in to comment.