+
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
- uses: actions/checkout@v3
- uses: oven-sh/setup-bun@v2
- run: bun install
- name: Install Playwright Browsers
run: bun x playwright install --with-deps
- run: |
bun run --cwd packages/fake-browser test:coverage
bun run --cwd packages/isolated-element test:coverage
Expand Down
Binary file modified bun.lockb
Binary file not shown.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@
"@algolia/client-search": "^4.17.0",
"@types/prettier": "^2.7.2",
"@types/webextension-polyfill": "^0.9.1",
"@vitest/coverage-v8": "^2.0.5",
"@vitest/browser": "^2.1.1",
"@vitest/coverage-v8": "^2.1.1",
"@webext-core/fake-browser": "workspace:*",
"chokidar": "^3.5.3",
"linkedom": "^0.14.26",
"lint-staged": "^15.2.9",
"listr2": "^6.4.2",
"playwright": "^1.47.0",
"prettier": "^3.3.3",
"simple-git-hooks": "^2.11.1",
"ts-morph": "^23.0.0",
"tsup": "^6.4.0",
"tsx": "^4.17.0",
"turbo": "^1.6.3",
"typescript": "^5.5.4",
"vitest": "^2.0.5",
"vitest": "^2.1.1",
"webextension-polyfill": "^0.10.0"
},
"simple-git-hooks": {
Expand Down
20 changes: 20 additions & 0 deletions packages/messaging-demo/src/entrypoints/duckduckgo-injected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default defineUnlistedScript(async () => {
console.log('[duckduckgo-injected.ts] Injected script loaded');

duckduckgoMessaging.onMessage('ping', event => {
console.log('[duckduckgo-injected.ts] Received', event);
return 'pong';
});

duckduckgoMessaging.onMessage('ping2', event => {
console.log('[duckduckgo-injected.ts] Received2', event);
return 'pong2';
});

duckduckgoMessaging.sendMessage('fromInjected', undefined).then(res => {
console.log('[duckduckgo-injected.ts] Response:', res);
});

const res2 = await duckduckgoMessaging.sendMessage('fromInjected2', undefined);
console.log('[duckduckgo-injected.ts] Response2:', res2);
});
28 changes: 28 additions & 0 deletions packages/messaging-demo/src/entrypoints/duckduckgo.content.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export default defineContentScript({
matches: ['*://*.duckduckgo.com/*'],

main(ctx) {
console.log('[duckduckgo.content.ts] Content script loaded');

duckduckgoMessaging.onMessage('fromInjected', event => {
console.log('[duckduckgo.content.ts] Received:', event);
return 'hello injected';
});

duckduckgoMessaging.onMessage('fromInjected2', event => {
console.log('[duckduckgo.content.ts] Received:', event);
return 'hello injected2';
});

const script = document.createElement('script');
script.src = browser.runtime.getURL('/duckduckgo-injected.js');
script.onload = async () => {
const res = await duckduckgoMessaging.sendMessage('ping', undefined);
console.log('[duckduckgo.content.ts] Response:', res);

const res2 = await duckduckgoMessaging.sendMessage('ping2', undefined);
console.log('[duckduckgo.content.ts] Response2:', res2);
};
document.head.appendChild(script);
},
});
16 changes: 14 additions & 2 deletions packages/messaging-demo/src/entrypoints/google-injected.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
export default defineUnlistedScript(() => {
export default defineUnlistedScript(async () => {
console.log('[google-injected.ts] Injected script loaded');

googleMessaging.onMessage('ping', event => {
console.log('[google-injected.ts] revieved', event);
console.log('[google-injected.ts] Received', event);
return 'pong';
});

googleMessaging.onMessage('ping2', event => {
console.log('[google-injected.ts] Received2', event);
return 'pong2';
});

googleMessaging.sendMessage('fromInjected', undefined).then(res => {
console.log('[google-injected.ts] Response:', res);
});

const res2 = await googleMessaging.sendMessage('fromInjected2', undefined);
console.log('[google-injected.ts] Response2:', res2);
});
13 changes: 13 additions & 0 deletions packages/messaging-demo/src/entrypoints/google.content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,24 @@ export default defineContentScript({
main(ctx) {
console.log('[google.content.ts] Content script loaded');

googleMessaging.onMessage('fromInjected', event => {
console.log('[google.content.ts] Received:', event);
return 'hello injected';
});

googleMessaging.onMessage('fromInjected2', event => {
console.log('[google.content.ts] Received:', event);
return 'hello injected2';
});

const script = document.createElement('script');
script.src = browser.runtime.getURL('/google-injected.js');
script.onload = async () => {
const res = await googleMessaging.sendMessage('ping', undefined);
console.log('[google.content.ts] Response:', res);

const res2 = await googleMessaging.sendMessage('ping2', undefined);
console.log('[google.content.ts] Response2:', res2);
};
document.head.appendChild(script);
},
Expand Down
12 changes: 12 additions & 0 deletions packages/messaging-demo/src/utils/duckduckgo-messaging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineCustomEventMessaging } from '@webext-core/messaging/page';

export interface DuckduckgoMessagingProtocol {
ping(): string;
ping2(): string;
fromInjected(): string;
fromInjected2(): string;
}

export const duckduckgoMessaging = defineCustomEventMessaging<DuckduckgoMessagingProtocol>({
namespace: '@webext-core/messaging-demo/duckduckgo',
});
3 changes: 3 additions & 0 deletions packages/messaging-demo/src/utils/google-messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { defineWindowMessaging } from '@webext-core/messaging/page';

export interface GoogleMessagingProtocol {
ping(): string;
ping2(): string;
fromInjected(): string;
fromInjected2(): string;
}

export const googleMessaging = defineWindowMessaging<GoogleMessagingProtocol>({
Expand Down
5 changes: 5 additions & 0 deletions packages/messaging-demo/wxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export default defineConfig({
resources: ['google-injected.js'],
matches: ['*://*.google.com/*'],
},
{
resources: ['duckduckgo-injected.js'],
matches: ['*://*.duckduckgo.com/*'],
},
],
},
runner: { startUrls: ['https://google.com/', 'https://duckduckgo.com/'] },
});
11 changes: 7 additions & 4 deletions packages/messaging/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,16 @@
},
"scripts": {
"build": "buildc -- tsup src/index.ts src/page.ts --clean --out-dir lib --dts --format esm,cjs,iife --global-name webExtCoreMessaging",
"test": "buildc --deps-only -- vitest -r src",
"test:coverage": "buildc --deps-only -- vitest run -r src --coverage",
"test": "buildc --deps-only -- bun run test:node && bun run test:browser",
"test:node": "buildc --deps-only -- vitest -r src --config ../vitest.config.node.ts",
"test:browser": "buildc --deps-only -- vitest -r src --config ../vitest.config.browser.ts",
"test:coverage": "buildc --deps-only -- bun run test:node --coverage && bun run test:browser --coverage",
"check": "buildc --deps-only -- tsc --noEmit"
},
"dependencies": {
"webextension-polyfill": "^0.10.0",
"serialize-error": "^11.0.0"
"serialize-error": "^11.0.0",
"uid": "^2.0.2",
"webextension-polyfill": "^0.10.0"
},
"devDependencies": {
"@types/webextension-polyfill": "^0.9.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { defineWindowMessaging, defineCustomEventMessaging } from './page';
import { GenericMessenger } from './generic';
import { NamespaceMessagingConfig } from './types';

vi.mock('webextension-polyfill');
import { defineWindowMessaging, defineCustomEventMessaging } from '../../page';
import { GenericMessenger } from '../../generic';
import { NamespaceMessagingConfig } from '../../types';

describe.each<
[
Expand Down Expand Up @@ -116,4 +113,41 @@ describe.each<
error,
);
});

it('should be messaging for the same message type between different instances', async () => {
interface MessageSchema {
test(data: string): string;
}
const messengerA1 = defineTestMessaging<MessageSchema>({ namespace: 'a' });
const messengerA2 = defineTestMessaging<MessageSchema>({ namespace: 'a' });
const messengerB1 = defineTestMessaging<MessageSchema>({ namespace: 'b' });
const messengerB2 = defineTestMessaging<MessageSchema>({ namespace: 'b' });

messengerA1.onMessage('test', message => {
expect(message.data).toBe('ping-from-A2');
return 'pong-from-A1';
});
messengerA2.onMessage('test', message => {
expect(message.data).toBe('ping-from-A1');
return 'pong-from-A2';
});
messengerB1.onMessage('test', message => {
expect(message.data).toBe('ping-from-B2');
return 'pong-from-B1';
});
messengerB2.onMessage('test', message => {
expect(message.data).toBe('ping-from-B1');
return 'pong-from-B2';
});

const resA1 = messengerA1.sendMessage('test', 'ping-from-A1');
const resA2 = messengerA2.sendMessage('test', 'ping-from-A2');
const resB1 = messengerB1.sendMessage('test', 'ping-from-B1');
const resB2 = messengerB2.sendMessage('test', 'ping-from-B2');

expect(resA1).resolves.toBe('pong-from-A2');
expect(resA2).resolves.toBe('pong-from-A1');
expect(resB1).resolves.toBe('pong-from-B2');
expect(resB2).resolves.toBe('pong-from-B1');
});
});
42 changes: 31 additions & 11 deletions packages/messaging/src/custom-event.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { uid } from 'uid';
import { GenericMessenger, defineGenericMessanging } from './generic';
import { NamespaceMessagingConfig } from './types';

Expand Down Expand Up @@ -51,48 +52,67 @@ export type CustomEventMessenger<TProtocolMap extends Record<string, any>> = Gen
* websiteMessenger.onMessage("initInjectedScript", (...) => {
* // ...
* })
*
* * @link Spec diagrams
* https://mermaid.live/edit#pako:eNqVlG9v2jAQxr-KZamvGv7YyYBYVSTGWgmpwAs6VZqQJpMc1BOxM8fZoIjvPieBACUVLC8SO3ru7vc4l9viUEWAGU7hdwYyhG-CLzWPZxLZa67WaKCkAWka01CLxDzMdSsobkKmhtsAUuy2SPIY0oSHwNBCKbQrMyRcGxGKhEuDbAT5qSTiKVJyBGnKl_CJKgUZ5br8eaa0-yPaUP6C0EDUeoX5VBi4hKP_A0dvgqM3wvWlMm-gi-Nb15ybe4k25_oTNPcmNPcKWrm8uzvWRF_Ld2NlAKk_oA_FnCodQ4PJaPR9PBz0X4aTMXp6nrwW6NP-6BH1p-j58enlkNsCoXKVX9WnbATB_f6AWcHWMpsE2AwnQi7JDNeFNNb7fmFILKXSlu-vLILP1HnOUkv3uCdqDemlOAgaVRWWS2phqjoXluipJVJriX6wRE8s0auWSGWJXLdEjpZovSV6ZqlcHjtBoVDFcSZFyG0LrIQ85D9rCfqhJcbnYUIWHRGJxQK0HRbHBrPJsINj0DEXkR0z2zz5DNs_I4YZZnYZwYJnK5ND7qyUZ0ZNNzLEzOgMHKxVtnzDbMFXqd1lSWTr7WdU9db2P2ZbvMasQYjX9AnteaRN3HbP7XgO3mBG3U6z533p-W637bue5-4c_K6UTUGbpOtT36edLvXaLu06GCJhlB6Vc7EYj0WJH4U-p9r9Aynbsqc
*/
export function defineCustomEventMessaging<
TProtocolMap extends Record<string, any> = Record<string, any>,
>(config: CustomEventMessagingConfig): CustomEventMessenger<TProtocolMap> {
const namespace = config.namespace;
const instanceId = uid();

const removeAdditionalListeners: Array<() => void> = [];

const sendCustomMessage = (event: CustomEvent) =>
const sendCustomMessage = (requestEvent: CustomEvent) =>
new Promise(res => {
const responseListener = (e: Event) => {
const { detail } = e as CustomEvent;
res(detail);
if (
detail.namespace === namespace &&
detail.instanceId !== instanceId &&
detail.message.type === requestEvent.detail.message.type
) {
res(detail.response);
}
Comment on lines -65 to +77
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a mistake that unlike windowMessage, customEventMessage has been unguarded so far? Is there a particular reason I don't know about?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a bug, but could be related to how window.postMessage sends the message to all frames in the window.

};
removeAdditionalListeners.push(() =>
window.removeEventListener(RESPONSE_EVENT, responseListener),
);
window.addEventListener(RESPONSE_EVENT, responseListener, { once: true });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, too, I don't understand why it has been once: true so far.

window.dispatchEvent(event);
window.addEventListener(RESPONSE_EVENT, responseListener);
window.dispatchEvent(requestEvent);
});

const messenger = defineGenericMessanging<TProtocolMap, CustomEventMessage, []>({
...config,

sendMessage(message) {
const event = new CustomEvent(REQUEST_EVENT, { detail: { message, namespace } });
return sendCustomMessage(event);
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,
Comment on lines +90 to +93
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

});
return sendCustomMessage(requestEvent);
},

addRootListener(processMessage) {
const listener = async (e: Event) => {
const requestListener = async (e: Event) => {
const { detail, ...event } = e as CustomEvent;
if (detail.namespace !== namespace) return;
if (detail.namespace !== namespace || detail.instanceId === instanceId) return;

const message = { ...detail.message, event };
const response = await processMessage(message);

const responseEvent = new CustomEvent(RESPONSE_EVENT, { detail: response });
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,
});
window.dispatchEvent(responseEvent);
};

window.addEventListener(REQUEST_EVENT, listener);
return () => window.removeEventListener(REQUEST_EVENT, listener);
window.addEventListener(REQUEST_EVENT, requestListener);
return () => window.removeEventListener(REQUEST_EVENT, requestListener);
},
});

Expand Down
26 changes: 22 additions & 4 deletions packages/messaging/src/window.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { uid } from 'uid';
import { GenericMessenger, defineGenericMessanging } from './generic';
import { NamespaceMessagingConfig, Message } from './types';

Expand Down Expand Up @@ -45,18 +46,27 @@ export type WindowMessenger<TProtocolMap extends Record<string, any>> = GenericM
* websiteMessenger.onMessage("initInjectedScript", (...) => {
* // ...
* })
*
* @link Spec diagrams
* https://mermaid.live/edit#pako:eNqVlG9v2jAQxr-KZamvGv7YyYBYVSTGWgmpwAs6VZqQJpMc1BOxM8fZoIjvPieBACUVLC8SO3ru7vc4l9viUEWAGU7hdwYyhG-CLzWPZxLZa67WaKCkAWka01CLxDzMdSsobkKmhtsAUuy2SPIY0oSHwNBCKbQrMyRcGxGKhEuDbAT5qSTiKVJyBGnKl_CJKgUZ5br8eaa0-yPaUP6C0EDUeoX5VBi4hKP_A0dvgqM3wvWlMm-gi-Nb15ybe4k25_oTNPcmNPcKWrm8uzvWRF_Ld2NlAKk_oA_FnCodQ4PJaPR9PBz0X4aTMXp6nrwW6NP-6BH1p-j58enlkNsCoXKVX9WnbATB_f6AWcHWMpsE2AwnQi7JDNeFNNb7fmFILKXSlu-vLILP1HnOUkv3uCdqDemlOAgaVRWWS2phqjoXluipJVJriX6wRE8s0auWSGWJXLdEjpZovSV6ZqlcHjtBoVDFcSZFyG0LrIQ85D9rCfqhJcbnYUIWHRGJxQK0HRbHBrPJsINj0DEXkR0z2zz5DNs_I4YZZnYZwYJnK5ND7qyUZ0ZNNzLEzOgMHKxVtnzDbMFXqd1lSWTr7WdU9db2P2ZbvMasQYjX9AnteaRN3HbP7XgO3mBG3U6z533p-W637bue5-4c_K6UTUGbpOtT36edLvXaLu06GCJhlB6Vc7EYj0WJH4U-p9r9Aynbsqc
*/
export function defineWindowMessaging<
TProtocolMap extends Record<string, any> = Record<string, any>,
>(config: WindowMessagingConfig): WindowMessenger<TProtocolMap> {
const namespace = config.namespace;
const instanceId = uid();

let removeAdditionalListeners: Array<() => void> = [];

const sendWindowMessage = (message: Message<TProtocolMap, any>, targetOrigin?: string) =>
new Promise(res => {
const responseListener = (event: MessageEvent) => {
if (event.data.type === RESPONSE_TYPE) {
if (
event.data.type === RESPONSE_TYPE &&
event.data.namespace === namespace &&
event.data.instanceId !== instanceId &&
event.data.message.type === message.type
) {
res(event.data.response);
removeResponseListener();
}
Expand All @@ -65,7 +75,7 @@ export function defineWindowMessaging<
removeAdditionalListeners.push(removeResponseListener);
window.addEventListener('message', responseListener);
window.postMessage(
{ type: REQUEST_TYPE, message, senderOrigin: location.origin, namespace },
{ type: REQUEST_TYPE, message, senderOrigin: location.origin, namespace, instanceId },
targetOrigin ?? '*',
);
});
Expand All @@ -79,10 +89,18 @@ export function defineWindowMessaging<

addRootListener(processMessage) {
const listener = async (event: MessageEvent) => {
if (event.data.type !== REQUEST_TYPE || event.data.namespace !== namespace) return;
if (
event.data.type !== REQUEST_TYPE ||
event.data.namespace !== namespace ||
event.data.instanceId === instanceId
)
return;

const response = await processMessage(event.data.message);
window.postMessage({ type: RESPONSE_TYPE, response }, event.data.senderOrigin);
window.postMessage(
{ type: RESPONSE_TYPE, response, instanceId, message: event.data.message, namespace },
event.data.senderOrigin,
);
};

window.addEventListener('message', listener);
Expand Down
Loading
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载