Skip to content

Commit

Permalink
fix(messaging): Add verification process for the window messaging (#79)
Browse files Browse the repository at this point in the history
Co-authored-by: Aaron <[email protected]>
  • Loading branch information
1natsu172 and aklinker1 authored Nov 13, 2024
1 parent eb5e45a commit 1752124
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 5 deletions.
33 changes: 33 additions & 0 deletions packages/messaging/src/__tests__/browser/page.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,39 @@ describe.each<
);
});

it('should throw an error if the responder responds with non-serializable data', async () => {
interface MessageSchema {
test(data: string): { getName: () => string };
}
const messenger1 = defineTestMessaging<MessageSchema>();
const messenger2 = defineTestMessaging<MessageSchema>();
const invalidFn = () => 'testUser1';
const onMessage = vi.fn().mockReturnValue({ getName: invalidFn });

messenger2.onMessage('test', onMessage);

await expect(messenger1.sendMessage('test', 'data', ...sendArgs)).rejects.toThrowError(
`Failed to execute 'structuredClone' on 'Window': ${invalidFn.toString()} could not be cloned.`,
);
});

it('should throw an error if the sender sends non-serializable data', async () => {
interface MessageSchema {
test(data: { getName: () => string }): boolean;
}
const messenger1 = defineTestMessaging<MessageSchema>();
const messenger2 = defineTestMessaging<MessageSchema>();
const invalidFn = () => 'testUser1';

messenger2.onMessage('test', () => true);

await expect(
messenger1.sendMessage('test', { getName: invalidFn }, ...sendArgs),
).rejects.toThrowError(
`Failed to execute 'structuredClone' on 'Window': ${invalidFn.toString()} could not be cloned.`,
);
});

it('should be messaging for the same message type between different instances', async () => {
interface MessageSchema {
test(data: string): string;
Expand Down
10 changes: 6 additions & 4 deletions packages/messaging/src/custom-event.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { uid } from 'uid';
import { GenericMessenger, defineGenericMessanging } from './generic';
import { NamespaceMessagingConfig } from './types';
import { prepareCustomEventDict } from './utils';

const REQUEST_EVENT = '@webext-core/messaging/custom-events';
const RESPONSE_EVENT = '@webext-core/messaging/custom-events/response';
Expand Down Expand Up @@ -89,8 +90,7 @@ export function defineCustomEventMessaging<
sendMessage(message) {
const reqDetail = { message, namespace, instanceId };
const requestEvent = new CustomEvent(REQUEST_EVENT, {
// @ts-expect-error not exist cloneInto types because implemented only in Firefox.
detail: typeof cloneInto !== 'undefined' ? cloneInto(reqDetail, window) : reqDetail,
detail: prepareCustomEventDict(reqDetail),
});
return sendCustomMessage(requestEvent);
},
Expand All @@ -105,15 +105,17 @@ export function defineCustomEventMessaging<

const resDetail = { response, message, instanceId, namespace };
const responseEvent = new CustomEvent(RESPONSE_EVENT, {
// @ts-expect-error not exist cloneInto types because implemented only in Firefox.
detail: typeof cloneInto !== 'undefined' ? cloneInto(resDetail, window) : resDetail,
detail: prepareCustomEventDict(resDetail),
});
window.dispatchEvent(responseEvent);
};

window.addEventListener(REQUEST_EVENT, requestListener);
return () => window.removeEventListener(REQUEST_EVENT, requestListener);
},
verifyMessageData(data) {
return structuredClone(data);
},
});

return {
Expand Down
7 changes: 6 additions & 1 deletion packages/messaging/src/generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface GenericMessagingConfig<
message: Message<TProtocolMap, any> & TMessageExtension,
) => void | Promise<{ res: any } | { err: unknown }>,
): RemoveListenerCallback;
verifyMessageData?<T>(data: T): MaybePromise<T>;
}

/**
Expand Down Expand Up @@ -109,12 +110,13 @@ export function defineGenericMessanging<
data: TProtocolMap[TType],
...args: TSendMessageArgs
): Promise<any> {
const message: Message<TProtocolMap, TType> = {
const _message: Message<TProtocolMap, TType> = {
id: getNextId(),
type: type,
data,
timestamp: Date.now(),
};
const message = (await config.verifyMessageData?.(_message)) ?? _message;
config.logger?.debug(`[messaging] sendMessage {id=${message.id}} ─ᐅ`, message, ...args);

const response = await config.sendMessage(message, ...args);
Expand Down Expand Up @@ -150,6 +152,9 @@ export function defineGenericMessanging<

const res = listener(message);
return Promise.resolve(res)
.then(res => {
return config.verifyMessageData?.(res) ?? res;
})
.then(res => {
config?.logger?.debug(`[messaging] onMessage {id=${message.id}} ─ᐅ`, { res });
return { res };
Expand Down
10 changes: 10 additions & 0 deletions packages/messaging/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* @description In firefox, when dispatching events externally from web-extension, it's necessary to clone all properties of the dictionary. ref: https://github.com/aklinker1/webext-core/pull/70#discussion_r1775031410
*/
export function prepareCustomEventDict<T>(
data: T,
options: { targetScope?: object } = { targetScope: window ?? undefined },
): T {
// @ts-expect-error not exist cloneInto types because implemented only in Firefox.
return typeof cloneInto !== 'undefined' ? cloneInto(data, options.targetScope) : data;
}
3 changes: 3 additions & 0 deletions packages/messaging/src/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ export function defineWindowMessaging<
window.addEventListener('message', listener);
return () => window.removeEventListener('message', listener);
},
verifyMessageData(data) {
return structuredClone(data);
},
});

return {
Expand Down

0 comments on commit 1752124

Please sign in to comment.