Skip to content

Commit a2e5659

Browse files
author
Shane Osbourne
committed
Added messaging library as an example to document
1 parent 45c1914 commit a2e5659

File tree

8 files changed

+867
-2
lines changed

8 files changed

+867
-2
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
},
3939
"type": "module",
4040
"workspaces": [
41-
"packages/special-pages"
41+
"packages/special-pages",
42+
"packages/messaging"
4243
],
4344
"dependencies": {
4445
"seedrandom": "^3.0.5",

packages/messaging/index.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**
2+
* @module Messaging
3+
*
4+
* @description
5+
*
6+
* An abstraction for communications between JavaScript and host platforms.
7+
*
8+
* 1) First you construct your platform-specific configuration (eg: {@link WebkitMessagingConfig})
9+
* 2) Then use that to get an instance of the Messaging utility which allows
10+
* you to send and receive data in a unified way
11+
* 3) Each platform implements {@link MessagingTransport} along with its own Configuration
12+
* - For example, to learn what configuration is required for Webkit, see: {@link "Webkit Messaging".WebkitMessagingConfig}
13+
* - Or, to learn about how messages are sent and received in Webkit, see {@link "Webkit Messaging".WebkitMessagingTransport}
14+
*
15+
* @example Webkit Messaging
16+
*
17+
* ```js
18+
* import { Messaging, WebkitMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js"
19+
*
20+
* // This config would be injected into the UserScript
21+
* const injectedConfig = {
22+
* hasModernWebkitAPI: true,
23+
* webkitMessageHandlerNames: ["foo", "bar", "baz"],
24+
* secret: "dax",
25+
* };
26+
*
27+
* // Then use that config to construct platform-specific configuration
28+
* const config = new WebkitMessagingConfig(injectedConfig);
29+
*
30+
* // finally, get an instance of Messaging and start sending messages in a unified way 🚀
31+
* const messaging = new Messaging(config);
32+
* messaging.notify("hello world!", {foo: "bar"})
33+
*
34+
* ```
35+
*
36+
* @example Windows Messaging
37+
*
38+
* ```js
39+
* import { Messaging, WindowsMessagingConfig } from "@duckduckgo/content-scope-scripts/lib/messaging.js"
40+
*
41+
* // Messaging on Windows is namespaced, so you can create multiple messaging instances
42+
* const autofillConfig = new WindowsMessagingConfig({ featureName: "Autofill" });
43+
* const debugConfig = new WindowsMessagingConfig({ featureName: "Debugging" });
44+
*
45+
* const autofillMessaging = new Messaging(autofillConfig);
46+
* const debugMessaging = new Messaging(debugConfig);
47+
*
48+
* // Now send messages to both features as needed 🚀
49+
* autofillMessaging.notify("storeFormData", { "username": "dax" })
50+
* debugMessaging.notify("pageLoad", { time: window.performance.now() })
51+
* ```
52+
*/
53+
import { WindowsMessagingConfig, WindowsMessagingTransport } from './lib/windows.js'
54+
import { WebkitMessagingConfig, WebkitMessagingTransport } from './lib/webkit.js'
55+
56+
/**
57+
* @implements {MessagingTransport}
58+
*/
59+
export class Messaging {
60+
/**
61+
* @param {WebkitMessagingConfig | WindowsMessagingConfig} config
62+
*/
63+
constructor (config) {
64+
this.transport = getTransport(config)
65+
}
66+
67+
/**
68+
* Send a 'fire-and-forget' message.
69+
* @throws {MissingHandler}
70+
*
71+
* @example
72+
*
73+
* ```ts
74+
* const messaging = new Messaging(config)
75+
* messaging.notify("foo", {bar: "baz"})
76+
* ```
77+
* @param {string} name
78+
* @param {Record<string, any>} [data]
79+
*/
80+
notify (name, data = {}) {
81+
this.transport.notify(name, data)
82+
}
83+
84+
/**
85+
* Send a request, and wait for a response
86+
* @throws {MissingHandler}
87+
*
88+
* @example
89+
* ```
90+
* const messaging = new Messaging(config)
91+
* const response = await messaging.request("foo", {bar: "baz"})
92+
* ```
93+
*
94+
* @param {string} name
95+
* @param {Record<string, any>} [data]
96+
* @return {Promise<any>}
97+
*/
98+
request (name, data = {}) {
99+
return this.transport.request(name, data)
100+
}
101+
102+
/**
103+
* @param {string} name
104+
* @param {(value: unknown) => void} callback
105+
* @return {() => void}
106+
*/
107+
subscribe (name, callback) {
108+
return this.transport.subscribe(name, callback)
109+
}
110+
}
111+
112+
/**
113+
* @interface
114+
*/
115+
export class MessagingTransport {
116+
/**
117+
* @param {string} name
118+
* @param {Record<string, any>} [data]
119+
* @returns {void}
120+
*/
121+
// @ts-ignore - ignoring a no-unused ts error, this is only an interface.
122+
notify (name, data = {}) {
123+
throw new Error("must implement 'notify'")
124+
}
125+
126+
/**
127+
* @param {string} name
128+
* @param {Record<string, any>} [data]
129+
* @param {{signal?: AbortSignal}} [options]
130+
* @return {Promise<any>}
131+
*/
132+
// @ts-ignore - ignoring a no-unused ts error, this is only an interface.
133+
request (name, data = {}, options = {}) {
134+
throw new Error('must implement')
135+
}
136+
137+
/**
138+
* @param {string} name
139+
* @param {(value: unknown) => void} callback
140+
* @return {() => void}
141+
*/
142+
// @ts-ignore - ignoring a no-unused ts error, this is only an interface.
143+
subscribe (name, callback) {
144+
throw new Error('must implement')
145+
}
146+
}
147+
148+
/**
149+
* @param {WebkitMessagingConfig | WindowsMessagingConfig} config
150+
* @returns {MessagingTransport}
151+
*/
152+
function getTransport (config) {
153+
if (config instanceof WebkitMessagingConfig) {
154+
return new WebkitMessagingTransport(config)
155+
}
156+
if (config instanceof WindowsMessagingConfig) {
157+
return new WindowsMessagingTransport(config)
158+
}
159+
throw new Error('unreachable')
160+
}
161+
162+
/**
163+
* Thrown when a handler cannot be found
164+
*/
165+
export class MissingHandler extends Error {
166+
/**
167+
* @param {string} message
168+
* @param {string} handlerName
169+
*/
170+
constructor (message, handlerName) {
171+
super(message)
172+
this.handlerName = handlerName
173+
}
174+
}
175+
176+
/**
177+
* Some re-exports for convenience
178+
*/
179+
export { WebkitMessagingConfig, WindowsMessagingConfig }
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { WindowsMessagingConfig } from '../windows.js'
2+
import { Messaging } from '../../index.js'
3+
4+
/**
5+
* These 3 required methods that get assigned by the Native side.
6+
*/
7+
// @ts-ignore
8+
const windowsInteropPostMessage = window.chrome.webview.postMessage
9+
// @ts-ignore
10+
const windowsInteropAddEventListener = window.chrome.webview.addEventListener
11+
// @ts-ignore
12+
const windowsInteropRemoveEventListener = window.chrome.webview.removeEventListener
13+
14+
/**
15+
* With those methods available in the same lexical scope, we can then create
16+
* our WindowsMessagingConfig
17+
*/
18+
const config = new WindowsMessagingConfig({
19+
featureName: 'ExamplePage',
20+
methods: {
21+
postMessage: windowsInteropPostMessage,
22+
addEventListener: windowsInteropAddEventListener,
23+
removeEventListener: windowsInteropRemoveEventListener,
24+
},
25+
})
26+
27+
/**
28+
* And then send notifications!
29+
*/
30+
const messaging = new Messaging(config)
31+
messaging.notify('helloWorld')
32+
33+
/**
34+
* Or request some data
35+
*/
36+
messaging.request('getData', { foo: 'bar' }).then(console.log).catch(console.error)
37+
38+
/**
39+
* Or subscribe for push messages
40+
*/
41+
const unsubscribe = messaging.subscribe('getData', (data) => console.log(data))
42+
43+
// later
44+
unsubscribe()
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
interface UnstableWebkit {
2+
messageHandlers: Record<
3+
string,
4+
{
5+
postMessage?: (...args: unknown[]) => void
6+
}
7+
>
8+
}
9+
10+
interface Window {
11+
webkit: UnstableWebkit
12+
}
13+
14+
declare let windowsInteropPostMessage: any
15+
declare let windowsInteropAddEventListener: any
16+
declare let windowsInteropRemoveEventListener: any

0 commit comments

Comments
 (0)