@thalesrc/hermes 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +538 -0
- package/broadcast/default-channel-name.cjs +4 -0
- package/broadcast/default-channel-name.d.ts +1 -0
- package/broadcast/default-channel-name.js +3 -0
- package/broadcast/default-channel-name.js.map +1 -0
- package/broadcast/index.cjs +13 -0
- package/broadcast/index.d.ts +5 -0
- package/broadcast/index.js +7 -0
- package/broadcast/index.js.map +1 -0
- package/broadcast/message-client.cjs +26 -0
- package/broadcast/message-client.d.ts +12 -0
- package/broadcast/message-client.js +24 -0
- package/broadcast/message-client.js.map +1 -0
- package/broadcast/message-host.cjs +28 -0
- package/broadcast/message-host.d.ts +9 -0
- package/broadcast/message-host.js +26 -0
- package/broadcast/message-host.js.map +1 -0
- package/broadcast/message-service.cjs +13 -0
- package/broadcast/message-service.d.ts +8 -0
- package/broadcast/message-service.js +11 -0
- package/broadcast/message-service.js.map +1 -0
- package/chrome/default-connection-name.cjs +4 -0
- package/chrome/default-connection-name.d.ts +1 -0
- package/chrome/default-connection-name.js +3 -0
- package/chrome/default-connection-name.js.map +1 -0
- package/chrome/index.cjs +11 -0
- package/chrome/index.d.ts +4 -0
- package/chrome/index.js +6 -0
- package/chrome/index.js.map +1 -0
- package/chrome/message-client.cjs +41 -0
- package/chrome/message-client.d.ts +15 -0
- package/chrome/message-client.js +39 -0
- package/chrome/message-client.js.map +1 -0
- package/chrome/message-host.cjs +43 -0
- package/chrome/message-host.d.ts +9 -0
- package/chrome/message-host.js +41 -0
- package/chrome/message-host.js.map +1 -0
- package/iframe/channel-path-splitter.cjs +4 -0
- package/iframe/channel-path-splitter.d.ts +1 -0
- package/iframe/channel-path-splitter.js +3 -0
- package/iframe/channel-path-splitter.js.map +1 -0
- package/iframe/default-channel-name.cjs +4 -0
- package/iframe/default-channel-name.d.ts +1 -0
- package/iframe/default-channel-name.js +3 -0
- package/iframe/default-channel-name.js.map +1 -0
- package/iframe/iframe.type.cjs +2 -0
- package/iframe/iframe.type.d.ts +1 -0
- package/iframe/iframe.type.js +3 -0
- package/iframe/iframe.type.js.map +1 -0
- package/iframe/index.cjs +13 -0
- package/iframe/index.d.ts +6 -0
- package/iframe/index.js +7 -0
- package/iframe/index.js.map +1 -0
- package/iframe/message-client.cjs +50 -0
- package/iframe/message-client.d.ts +14 -0
- package/iframe/message-client.js +48 -0
- package/iframe/message-client.js.map +1 -0
- package/iframe/message-host.cjs +63 -0
- package/iframe/message-host.d.ts +11 -0
- package/iframe/message-host.js +61 -0
- package/iframe/message-host.js.map +1 -0
- package/iframe/message-service.cjs +13 -0
- package/iframe/message-service.d.ts +9 -0
- package/iframe/message-service.js +11 -0
- package/iframe/message-service.js.map +1 -0
- package/iframe/source-id-splitter.cjs +4 -0
- package/iframe/source-id-splitter.d.ts +1 -0
- package/iframe/source-id-splitter.js +3 -0
- package/iframe/source-id-splitter.js.map +1 -0
- package/iframe/upcoming-message.cjs +2 -0
- package/iframe/upcoming-message.d.ts +4 -0
- package/iframe/upcoming-message.js +3 -0
- package/iframe/upcoming-message.js.map +1 -0
- package/index.cjs +13 -0
- package/index.d.ts +7 -0
- package/index.js +7 -0
- package/index.js.map +1 -0
- package/listen.decorator.cjs +20 -0
- package/listen.decorator.d.ts +6 -0
- package/listen.decorator.js +19 -0
- package/listen.decorator.js.map +1 -0
- package/listener-storage.type.cjs +2 -0
- package/listener-storage.type.d.ts +2 -0
- package/listener-storage.type.js +3 -0
- package/listener-storage.type.js.map +1 -0
- package/message-client.cjs +10 -0
- package/message-client.d.ts +23 -0
- package/message-client.js +8 -0
- package/message-client.js.map +1 -0
- package/message-host.cjs +58 -0
- package/message-host.d.ts +26 -0
- package/message-host.js +56 -0
- package/message-host.js.map +1 -0
- package/message-response.type.cjs +2 -0
- package/message-response.type.d.ts +14 -0
- package/message-response.type.js +3 -0
- package/message-response.type.js.map +1 -0
- package/message.interface.cjs +2 -0
- package/message.interface.d.ts +6 -0
- package/message.interface.js +3 -0
- package/message.interface.js.map +1 -0
- package/package.json +158 -0
- package/request.decorator.cjs +20 -0
- package/request.decorator.d.ts +1 -0
- package/request.decorator.js +19 -0
- package/request.decorator.js.map +1 -0
- package/selectors.cjs +10 -0
- package/selectors.d.ts +7 -0
- package/selectors.js +9 -0
- package/selectors.js.map +1 -0
- package/worker/index.cjs +13 -0
- package/worker/index.d.ts +5 -0
- package/worker/index.js +7 -0
- package/worker/index.js.map +1 -0
- package/worker/message-client.cjs +174 -0
- package/worker/message-client.d.ts +117 -0
- package/worker/message-client.js +172 -0
- package/worker/message-client.js.map +1 -0
- package/worker/message-host.cjs +41 -0
- package/worker/message-host.d.ts +9 -0
- package/worker/message-host.js +39 -0
- package/worker/message-host.js.map +1 -0
- package/worker/message-service.cjs +12 -0
- package/worker/message-service.d.ts +8 -0
- package/worker/message-service.js +10 -0
- package/worker/message-service.js.map +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Thalesrc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# @thalesrc/hermes
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@thalesrc/hermes)
|
|
4
|
+
[](https://www.npmjs.com/package/@thalesrc/hermes)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Javascript messaging library for cross-context communication
|
|
9
|
+
|
|
10
|
+
**Part of the [Thalesrc](https://github.com/thalesrc/thalesrc) monorepo**
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @thalesrc/hermes
|
|
16
|
+
# or
|
|
17
|
+
yarn add @thalesrc/hermes
|
|
18
|
+
# or
|
|
19
|
+
pnpm add @thalesrc/hermes
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Overview
|
|
23
|
+
|
|
24
|
+
Hermes provides a unified, decorator-based API for cross-context messaging in JavaScript applications. Built on RxJS, it supports:
|
|
25
|
+
|
|
26
|
+
- 🖼️ **Iframe Communication** - Parent-child window messaging
|
|
27
|
+
- 🧩 **Chrome Extensions** - Background scripts, content scripts, and popups
|
|
28
|
+
- 👷 **Web Workers** - Main thread and worker communication
|
|
29
|
+
- 📡 **Broadcast Channel** - Tab-to-tab messaging
|
|
30
|
+
|
|
31
|
+
## Core Concepts
|
|
32
|
+
|
|
33
|
+
### MessageClient & MessageHost
|
|
34
|
+
|
|
35
|
+
The library uses two main abstractions:
|
|
36
|
+
|
|
37
|
+
- **`MessageClient`**: Sends requests and receives responses (uses `@Request` decorator)
|
|
38
|
+
- **`MessageHost`**: Listens for requests and sends responses (uses `@Listen` decorator)
|
|
39
|
+
- **`MessageService`**: Combines both client and host (bidirectional communication)
|
|
40
|
+
|
|
41
|
+
### Decorators
|
|
42
|
+
|
|
43
|
+
- **`@Request(path)`**: Marks a method as a message sender
|
|
44
|
+
- **`@Listen(path)`**: Marks a method as a message listener
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Iframe Communication
|
|
49
|
+
|
|
50
|
+
Send and receive messages between iframes and parent windows.
|
|
51
|
+
|
|
52
|
+
#### Client-Only (Iframe)
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { IframeMessageClient, Request } from '@thalesrc/hermes/iframe';
|
|
56
|
+
import { Observable } from 'rxjs';
|
|
57
|
+
|
|
58
|
+
class IframeClient extends IframeMessageClient {
|
|
59
|
+
@Request('greeting')
|
|
60
|
+
sayHello(name: string): Observable<string> {
|
|
61
|
+
return null; // Implementation handled by decorator
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Default: sends messages to parent window
|
|
66
|
+
const client = new IframeClient();
|
|
67
|
+
|
|
68
|
+
// Optional: specify channel name
|
|
69
|
+
const clientWithChannel = new IframeClient('my-channel');
|
|
70
|
+
|
|
71
|
+
// Optional: target specific iframe (from parent window)
|
|
72
|
+
const iframe = document.querySelector('iframe');
|
|
73
|
+
const clientToIframe = new IframeClient('my-channel', iframe);
|
|
74
|
+
|
|
75
|
+
// Optional: use a function to get iframe dynamically
|
|
76
|
+
const clientWithDynamicIframe = new IframeClient('my-channel', () =>
|
|
77
|
+
document.querySelector('iframe#dynamic')
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
client.sayHello('John').subscribe(response => {
|
|
81
|
+
console.log(response); // 'Hello John!'
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### Host-Only (Parent Window)
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { IframeMessageHost, Listen, UpcomingMessage } from '@thalesrc/hermes/iframe';
|
|
89
|
+
import { of } from 'rxjs';
|
|
90
|
+
|
|
91
|
+
class ParentHost extends IframeMessageHost {
|
|
92
|
+
@Listen('greeting')
|
|
93
|
+
handleGreeting({ data }: UpcomingMessage<string>): Observable<string> {
|
|
94
|
+
return of(`Hello ${data}!`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Default: listens to all iframes
|
|
99
|
+
const host = new ParentHost();
|
|
100
|
+
|
|
101
|
+
// Optional: specify channel name
|
|
102
|
+
const hostWithChannel = new ParentHost('my-channel');
|
|
103
|
+
|
|
104
|
+
// Optional: listen only to specific iframe
|
|
105
|
+
const iframe = document.querySelector('iframe');
|
|
106
|
+
const hostForSpecificIframe = new ParentHost('my-channel', iframe);
|
|
107
|
+
|
|
108
|
+
// Optional: use a function to get iframe dynamically
|
|
109
|
+
const hostWithDynamicIframe = new ParentHost('my-channel', () =>
|
|
110
|
+
document.querySelector('iframe#dynamic')
|
|
111
|
+
);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Bidirectional Communication (MessageService)
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { IframeMessageService, Request, Listen } from '@thalesrc/hermes/iframe';
|
|
118
|
+
import { of, Observable } from 'rxjs';
|
|
119
|
+
|
|
120
|
+
class IframeBidirectional extends IframeMessageService {
|
|
121
|
+
// Send messages
|
|
122
|
+
@Request('getData')
|
|
123
|
+
requestData(): Observable<any> {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Receive messages
|
|
128
|
+
@Listen('update')
|
|
129
|
+
handleUpdate(data: any): Observable<string> {
|
|
130
|
+
console.log('Received update:', data);
|
|
131
|
+
return of('Update processed');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Default: communicates with parent window
|
|
136
|
+
const service = new IframeBidirectional();
|
|
137
|
+
|
|
138
|
+
// Optional: specify channel name and target frame
|
|
139
|
+
const iframe = document.querySelector('iframe');
|
|
140
|
+
const serviceWithTarget = new IframeBidirectional('my-channel', iframe);
|
|
141
|
+
|
|
142
|
+
// From within iframe (communicates with parent)
|
|
143
|
+
const iframeService = new IframeBidirectional('my-channel');
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
### Chrome Extensions
|
|
149
|
+
|
|
150
|
+
Communicate across extension contexts (background, content scripts, popups).
|
|
151
|
+
|
|
152
|
+
#### Content Script
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { ChromeMessageClient, Request } from '@thalesrc/hermes/chrome';
|
|
156
|
+
import { Observable } from 'rxjs';
|
|
157
|
+
|
|
158
|
+
class ContentScript extends ChromeMessageClient {
|
|
159
|
+
@Request('fetchData')
|
|
160
|
+
getData(query: string): Observable<any> {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
@Request('saveSettings')
|
|
165
|
+
saveSettings(settings: object): Observable<boolean> {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const contentScript = new ContentScript();
|
|
171
|
+
|
|
172
|
+
contentScript.getData('user').subscribe(data => {
|
|
173
|
+
console.log('Received:', data);
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Background Script
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { ChromeMessageHost, Listen } from '@thalesrc/hermes/chrome';
|
|
181
|
+
import { of, Observable } from 'rxjs';
|
|
182
|
+
|
|
183
|
+
class BackgroundScript extends ChromeMessageHost {
|
|
184
|
+
@Listen('fetchData')
|
|
185
|
+
handleFetchData(query: string): Observable<any> {
|
|
186
|
+
// Fetch from API or storage
|
|
187
|
+
return of({ name: 'John', age: 30 });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@Listen('saveSettings')
|
|
191
|
+
handleSaveSettings(settings: object): Observable<boolean> {
|
|
192
|
+
// Save to chrome.storage
|
|
193
|
+
return of(true);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const background = new BackgroundScript();
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### Web Workers
|
|
203
|
+
|
|
204
|
+
Communicate between main thread and web workers.
|
|
205
|
+
|
|
206
|
+
#### Main Thread
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { WorkerMessageService, Request, Listen } from '@thalesrc/hermes/worker';
|
|
210
|
+
import { of, Observable } from 'rxjs';
|
|
211
|
+
|
|
212
|
+
class MainThread extends WorkerMessageService {
|
|
213
|
+
@Request('processData')
|
|
214
|
+
sendDataToWorker(data: number[]): Observable<number> {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@Listen('progress')
|
|
219
|
+
handleProgress(percent: number): Observable<void> {
|
|
220
|
+
console.log(`Progress: ${percent}%`);
|
|
221
|
+
return of(void 0);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Must provide Worker instance in main thread
|
|
226
|
+
const worker = new Worker('./worker.js');
|
|
227
|
+
const mainService = new MainThread(worker);
|
|
228
|
+
|
|
229
|
+
mainService.sendDataToWorker([1, 2, 3, 4, 5]).subscribe(result => {
|
|
230
|
+
console.log('Worker result:', result);
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
##### Flexible Worker Initialization
|
|
235
|
+
|
|
236
|
+
The worker parameter supports multiple initialization patterns for different use cases:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// Direct Worker instance
|
|
240
|
+
const service1 = new MainThread(new Worker('./worker.js'));
|
|
241
|
+
|
|
242
|
+
// Promise that resolves to a Worker (for async initialization)
|
|
243
|
+
const workerPromise = import('./worker.js').then(m => new Worker(m.default));
|
|
244
|
+
const service2 = new MainThread(workerPromise);
|
|
245
|
+
|
|
246
|
+
// Function that returns a Worker (for lazy initialization)
|
|
247
|
+
const service3 = new MainThread(() => new Worker('./worker.js'));
|
|
248
|
+
|
|
249
|
+
// Function that returns a Promise<Worker> (for async lazy initialization)
|
|
250
|
+
const service4 = new MainThread(async () => {
|
|
251
|
+
const module = await import('./worker.js');
|
|
252
|
+
return new Worker(module.default);
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
##### Dynamic Worker Management with `initialize()`
|
|
257
|
+
|
|
258
|
+
The `initialize()` method allows you to switch workers at runtime or re-establish connections:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
class MainThread extends WorkerMessageService {
|
|
262
|
+
// ... decorators and methods
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Start without a worker
|
|
266
|
+
const service = new MainThread();
|
|
267
|
+
|
|
268
|
+
// Later, connect to a worker dynamically
|
|
269
|
+
service.initialize(new Worker('./worker.js'));
|
|
270
|
+
|
|
271
|
+
// Switch to a different worker
|
|
272
|
+
service.initialize(new Worker('./different-worker.js'));
|
|
273
|
+
|
|
274
|
+
// Re-establish connection after worker error
|
|
275
|
+
const worker = new Worker('./worker.js');
|
|
276
|
+
worker.onerror = (error) => {
|
|
277
|
+
console.error('Worker error, reinitializing...', error);
|
|
278
|
+
service.initialize(new Worker('./worker.js'));
|
|
279
|
+
};
|
|
280
|
+
service.initialize(worker);
|
|
281
|
+
|
|
282
|
+
// Conditional worker initialization
|
|
283
|
+
function getWorker() {
|
|
284
|
+
return navigator.hardwareConcurrency > 2
|
|
285
|
+
? new Worker('./heavy-worker.js')
|
|
286
|
+
: new Worker('./light-worker.js');
|
|
287
|
+
}
|
|
288
|
+
service.initialize(getWorker);
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### Worker Thread
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { WorkerMessageService, Request, Listen } from '@thalesrc/hermes/worker';
|
|
295
|
+
import { of, Observable } from 'rxjs';
|
|
296
|
+
|
|
297
|
+
class WorkerThread extends WorkerMessageService {
|
|
298
|
+
@Listen('processData')
|
|
299
|
+
handleProcessData(data: number[]): Observable<number> {
|
|
300
|
+
// Heavy computation
|
|
301
|
+
const result = data.reduce((sum, n) => sum + n, 0);
|
|
302
|
+
return of(result);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@Request('progress')
|
|
306
|
+
reportProgress(percent: number): Observable<void> {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Inside worker: no argument needed (uses self)
|
|
312
|
+
const workerService = new WorkerThread();
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
### Broadcast Channel
|
|
318
|
+
|
|
319
|
+
Communicate between different tabs/windows of the same origin.
|
|
320
|
+
|
|
321
|
+
#### Tab 1
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { BroadcastMessageService, Request, Listen } from '@thalesrc/hermes/broadcast';
|
|
325
|
+
import { of, Observable } from 'rxjs';
|
|
326
|
+
|
|
327
|
+
class Tab1 extends BroadcastMessageService {
|
|
328
|
+
@Request('sync')
|
|
329
|
+
requestSync(data: any): Observable<string> {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
@Listen('notification')
|
|
334
|
+
handleNotification(message: string): Observable<void> {
|
|
335
|
+
console.log('Notification:', message);
|
|
336
|
+
return of(void 0);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const tab1 = new Tab1('my-app-channel');
|
|
341
|
+
|
|
342
|
+
tab1.requestSync({ user: 'John' }).subscribe(response => {
|
|
343
|
+
console.log(response); // 'Sync completed'
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### Tab 2
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { BroadcastMessageService, Request, Listen } from '@thalesrc/hermes/broadcast';
|
|
351
|
+
import { of, Observable } from 'rxjs';
|
|
352
|
+
|
|
353
|
+
class Tab2 extends BroadcastMessageService {
|
|
354
|
+
@Listen('sync')
|
|
355
|
+
handleSync(data: any): Observable<string> {
|
|
356
|
+
console.log('Syncing data:', data);
|
|
357
|
+
return of('Sync completed');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@Request('notification')
|
|
361
|
+
sendNotification(message: string): Observable<void> {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const tab2 = new Tab2('my-app-channel');
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Advanced Features
|
|
372
|
+
|
|
373
|
+
### Streaming Responses
|
|
374
|
+
|
|
375
|
+
Return multiple values over time using RxJS operators:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { Listen } from '@thalesrc/hermes/iframe';
|
|
379
|
+
import { interval } from 'rxjs';
|
|
380
|
+
import { map, take } from 'rxjs/operators';
|
|
381
|
+
|
|
382
|
+
class StreamingHost extends IframeMessageHost {
|
|
383
|
+
@Listen('countdown')
|
|
384
|
+
handleCountdown(start: number): Observable<number> {
|
|
385
|
+
return interval(1000).pipe(
|
|
386
|
+
map(i => start - i),
|
|
387
|
+
take(start + 1)
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
The client receives each value as it's emitted:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
client.countdown(5).subscribe(
|
|
397
|
+
value => console.log(value), // 5, 4, 3, 2, 1, 0
|
|
398
|
+
error => console.error(error),
|
|
399
|
+
() => console.log('Complete!')
|
|
400
|
+
);
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Error Handling
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
import { Listen } from '@thalesrc/hermes/iframe';
|
|
407
|
+
import { throwError } from 'rxjs';
|
|
408
|
+
|
|
409
|
+
class ErrorHost extends IframeMessageHost {
|
|
410
|
+
@Listen('riskyOperation')
|
|
411
|
+
handleRiskyOperation(data: any): Observable<any> {
|
|
412
|
+
if (!data.valid) {
|
|
413
|
+
return throwError(() => new Error('Invalid data'));
|
|
414
|
+
}
|
|
415
|
+
return of({ success: true });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Constructor Parameters
|
|
421
|
+
|
|
422
|
+
#### Iframe
|
|
423
|
+
|
|
424
|
+
**`IframeMessageClient` / `IframeMessageHost` / `IframeMessageService`**
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
constructor(channelName?: string, targetFrame?: HTMLIFrameElement | (() => HTMLIFrameElement))
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
- **`channelName`** (optional): Channel identifier for namespacing messages. Default: `'hermes-iframe-message'`
|
|
431
|
+
- **`targetFrame`** (optional): Specific iframe to communicate with. Can be:
|
|
432
|
+
- `HTMLIFrameElement`: Direct reference to iframe element
|
|
433
|
+
- `() => HTMLIFrameElement`: Function returning iframe (useful for dynamic iframes)
|
|
434
|
+
- Omit to communicate with parent window (from iframe) or all iframes (from parent)
|
|
435
|
+
|
|
436
|
+
**Examples:**
|
|
437
|
+
```typescript
|
|
438
|
+
// From iframe: communicate with parent
|
|
439
|
+
const client = new IframeMessageClient();
|
|
440
|
+
|
|
441
|
+
// From parent: communicate with specific iframe
|
|
442
|
+
const iframe = document.querySelector('iframe');
|
|
443
|
+
const host = new IframeMessageHost('my-channel', iframe);
|
|
444
|
+
|
|
445
|
+
// Dynamic iframe reference
|
|
446
|
+
const service = new IframeMessageService('my-channel', () =>
|
|
447
|
+
document.querySelector('iframe[data-active="true"]')
|
|
448
|
+
);
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
#### Worker
|
|
452
|
+
|
|
453
|
+
**`WorkerMessageClient` / `WorkerMessageHost` / `WorkerMessageService`**
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
constructor(worker?: Worker)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
- **`worker`** (optional): Worker instance for main thread communication
|
|
460
|
+
- **In main thread**: Must provide `Worker` instance
|
|
461
|
+
- **In worker thread**: Omit parameter (uses `self` automatically)
|
|
462
|
+
|
|
463
|
+
**Examples:**
|
|
464
|
+
```typescript
|
|
465
|
+
// Main thread: must provide worker
|
|
466
|
+
const worker = new Worker('./worker.js');
|
|
467
|
+
const service = new WorkerMessageService(worker);
|
|
468
|
+
|
|
469
|
+
// Inside worker: no parameter needed
|
|
470
|
+
const service = new WorkerMessageService();
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
#### Broadcast
|
|
474
|
+
|
|
475
|
+
**`BroadcastMessageClient` / `BroadcastMessageHost` / `BroadcastMessageService`**
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
constructor(channelName?: string)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
- **`channelName`** (optional): Broadcast channel name. Default: `'hermes-broadcast-message'`
|
|
482
|
+
|
|
483
|
+
**Example:**
|
|
484
|
+
```typescript
|
|
485
|
+
const service = new BroadcastMessageService('app-sync-channel');
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### Chrome
|
|
489
|
+
|
|
490
|
+
**`ChromeMessageClient` / `ChromeMessageHost`**
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
constructor(connectionName?: string)
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
- **`connectionName`** (optional): Connection identifier. Default: `'hermes-chrome-message'`
|
|
497
|
+
|
|
498
|
+
**Example:**
|
|
499
|
+
```typescript
|
|
500
|
+
const client = new ChromeMessageClient('extension-port');
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
## API Reference
|
|
504
|
+
|
|
505
|
+
### Classes
|
|
506
|
+
|
|
507
|
+
- **`MessageClient`** - Base class for message senders
|
|
508
|
+
- **`MessageHost`** - Base class for message receivers
|
|
509
|
+
- **`IframeMessageClient`** - Iframe client implementation
|
|
510
|
+
- **`IframeMessageHost`** - Iframe host implementation
|
|
511
|
+
- **`IframeMessageService`** - Bidirectional iframe communication
|
|
512
|
+
- **`ChromeMessageClient`** - Chrome extension client
|
|
513
|
+
- **`ChromeMessageHost`** - Chrome extension host
|
|
514
|
+
- **`WorkerMessageService`** - Bidirectional worker communication
|
|
515
|
+
- **`BroadcastMessageClient`** - Broadcast channel client
|
|
516
|
+
- **`BroadcastMessageHost`** - Broadcast channel host
|
|
517
|
+
- **`BroadcastMessageService`** - Bidirectional broadcast communication
|
|
518
|
+
|
|
519
|
+
### Decorators
|
|
520
|
+
|
|
521
|
+
- **`@Request(path: string)`** - Decorator for sending messages
|
|
522
|
+
- **`@Listen(path: string)`** - Decorator for receiving messages
|
|
523
|
+
|
|
524
|
+
### Types
|
|
525
|
+
|
|
526
|
+
- **`Message`** - Message payload structure
|
|
527
|
+
- **`MessageResponse`** - Response payload structure
|
|
528
|
+
- **`UpcomingMessage<T>`** - Incoming message with sender info (iframe)
|
|
529
|
+
|
|
530
|
+
## Requirements
|
|
531
|
+
|
|
532
|
+
- RxJS 7.x or higher
|
|
533
|
+
- TypeScript 4.x or higher (for decorator support)
|
|
534
|
+
- Modern browser with ES2015+ support
|
|
535
|
+
|
|
536
|
+
## License
|
|
537
|
+
|
|
538
|
+
MIT © [Thalesrc](https://github.com/thalesrc)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEFAULT_CHANNEL_NAME = "HERMES_DEFAULT_CHANNEL";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../libs/hermes/src/broadcast/default-channel-name.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG,wBAAwB,CAAC","file":"default-channel-name.js","sourcesContent":["export const DEFAULT_CHANNEL_NAME = 'HERMES_DEFAULT_CHANNEL';\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Listen = exports.Request = exports.BroadcastMessageService = exports.BroadcastMessageHost = exports.BroadcastMessageClient = void 0;
|
|
4
|
+
var message_client_1 = require("./message-client");
|
|
5
|
+
Object.defineProperty(exports, "BroadcastMessageClient", { enumerable: true, get: function () { return message_client_1.BroadcastMessageClient; } });
|
|
6
|
+
var message_host_1 = require("./message-host");
|
|
7
|
+
Object.defineProperty(exports, "BroadcastMessageHost", { enumerable: true, get: function () { return message_host_1.BroadcastMessageHost; } });
|
|
8
|
+
var message_service_1 = require("./message-service");
|
|
9
|
+
Object.defineProperty(exports, "BroadcastMessageService", { enumerable: true, get: function () { return message_service_1.BroadcastMessageService; } });
|
|
10
|
+
var request_decorator_1 = require("../request.decorator");
|
|
11
|
+
Object.defineProperty(exports, "Request", { enumerable: true, get: function () { return request_decorator_1.Request; } });
|
|
12
|
+
var listen_decorator_1 = require("../listen.decorator");
|
|
13
|
+
Object.defineProperty(exports, "Listen", { enumerable: true, get: function () { return listen_decorator_1.Listen; } });
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { BroadcastMessageClient } from './message-client';
|
|
2
|
+
export { BroadcastMessageHost } from './message-host';
|
|
3
|
+
export { BroadcastMessageService } from './message-service';
|
|
4
|
+
export { Request } from '../request.decorator';
|
|
5
|
+
export { Listen } from '../listen.decorator';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { BroadcastMessageClient } from './message-client';
|
|
2
|
+
export { BroadcastMessageHost } from './message-host';
|
|
3
|
+
export { BroadcastMessageService } from './message-service';
|
|
4
|
+
export { Request } from '../request.decorator';
|
|
5
|
+
export { Listen } from '../listen.decorator';
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../libs/hermes/src/broadcast/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC","file":"index.js","sourcesContent":["export { BroadcastMessageClient } from './message-client';\nexport { BroadcastMessageHost } from './message-host';\nexport { BroadcastMessageService } from './message-service';\nexport { Request } from '../request.decorator';\nexport { Listen } from '../listen.decorator';\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BroadcastMessageClient = void 0;
|
|
4
|
+
const rxjs_1 = require("rxjs");
|
|
5
|
+
const message_client_1 = require("../message-client");
|
|
6
|
+
const selectors_1 = require("../selectors");
|
|
7
|
+
const default_channel_name_1 = require("./default-channel-name");
|
|
8
|
+
class BroadcastMessageClient extends message_client_1.MessageClient {
|
|
9
|
+
[selectors_1.RESPONSES$] = new rxjs_1.Subject();
|
|
10
|
+
#channel;
|
|
11
|
+
constructor(channelName = default_channel_name_1.DEFAULT_CHANNEL_NAME) {
|
|
12
|
+
super();
|
|
13
|
+
this.#channel = new BroadcastChannel(channelName);
|
|
14
|
+
this.#channel.addEventListener('message', this.#handler);
|
|
15
|
+
}
|
|
16
|
+
[selectors_1.SEND](message) {
|
|
17
|
+
this.#channel.postMessage(message);
|
|
18
|
+
}
|
|
19
|
+
#handler = (event) => {
|
|
20
|
+
this[selectors_1.RESPONSES$].next(event.data);
|
|
21
|
+
};
|
|
22
|
+
[selectors_1.GET_NEW_ID]() {
|
|
23
|
+
return crypto.randomUUID();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.BroadcastMessageClient = BroadcastMessageClient;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Subject } from "rxjs";
|
|
2
|
+
import { MessageClient } from "../message-client";
|
|
3
|
+
import { MessageResponse } from "../message-response.type";
|
|
4
|
+
import { Message } from "../message.interface";
|
|
5
|
+
import { GET_NEW_ID, RESPONSES$, SEND } from "../selectors";
|
|
6
|
+
export declare class BroadcastMessageClient extends MessageClient {
|
|
7
|
+
#private;
|
|
8
|
+
protected [RESPONSES$]: Subject<MessageResponse>;
|
|
9
|
+
constructor(channelName?: string);
|
|
10
|
+
protected [SEND]<T>(message: Message<T>): void;
|
|
11
|
+
protected [GET_NEW_ID](): string;
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Subject } from "rxjs";
|
|
2
|
+
import { MessageClient } from "../message-client";
|
|
3
|
+
import { GET_NEW_ID, RESPONSES$, SEND } from "../selectors";
|
|
4
|
+
import { DEFAULT_CHANNEL_NAME } from "./default-channel-name";
|
|
5
|
+
export class BroadcastMessageClient extends MessageClient {
|
|
6
|
+
[RESPONSES$] = new Subject();
|
|
7
|
+
#channel;
|
|
8
|
+
constructor(channelName = DEFAULT_CHANNEL_NAME) {
|
|
9
|
+
super();
|
|
10
|
+
this.#channel = new BroadcastChannel(channelName);
|
|
11
|
+
this.#channel.addEventListener('message', this.#handler);
|
|
12
|
+
}
|
|
13
|
+
[SEND](message) {
|
|
14
|
+
this.#channel.postMessage(message);
|
|
15
|
+
}
|
|
16
|
+
#handler = (event) => {
|
|
17
|
+
this[RESPONSES$].next(event.data);
|
|
18
|
+
};
|
|
19
|
+
[GET_NEW_ID]() {
|
|
20
|
+
return crypto.randomUUID();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=message-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../libs/hermes/src/broadcast/message-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGlD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAM9D,MAAM,OAAO,sBAAuB,SAAQ,aAAa;IAC7C,CAAC,UAAU,CAAC,GAAG,IAAI,OAAO,EAAmB,CAAC;IAExD,QAAQ,CAAmB;IAE3B,YAAY,WAAW,GAAG,oBAAoB;QAC5C,KAAK,EAAE,CAAC;QAER,IAAI,CAAC,QAAQ,GAAG,IAAI,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAElD,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3D,CAAC;IAES,CAAC,IAAI,CAAC,CAAI,OAAmB;QACrC,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,QAAQ,GAAG,CAAC,KAAoC,EAAE,EAAE;QAClD,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC,CAAA;IAES,CAAC,UAAU,CAAC;QACpB,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IAC7B,CAAC;CACF","file":"message-client.js","sourcesContent":["import { Subject } from \"rxjs\";\nimport { MessageClient } from \"../message-client\";\nimport { MessageResponse } from \"../message-response.type\";\nimport { Message } from \"../message.interface\";\nimport { GET_NEW_ID, RESPONSES$, SEND } from \"../selectors\";\nimport { DEFAULT_CHANNEL_NAME } from \"./default-channel-name\";\n\ninterface MessageEvent<T> {\n data: T;\n}\n\nexport class BroadcastMessageClient extends MessageClient {\n protected [RESPONSES$] = new Subject<MessageResponse>();\n\n #channel: BroadcastChannel;\n\n constructor(channelName = DEFAULT_CHANNEL_NAME) {\n super();\n\n this.#channel = new BroadcastChannel(channelName);\n\n this.#channel.addEventListener('message', this.#handler);\n }\n\n protected [SEND]<T>(message: Message<T>) {\n this.#channel.postMessage(message);\n }\n\n #handler = (event: MessageEvent<MessageResponse>) => {\n this[RESPONSES$].next(event.data);\n }\n\n protected [GET_NEW_ID](): string {\n return crypto.randomUUID();\n }\n}"]}
|