@kookapp/web-bridge 0.0.1

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.
Files changed (50) hide show
  1. package/README.md +144 -0
  2. package/dist/child/ChildBridge.d.ts +40 -0
  3. package/dist/child/ChildBridge.d.ts.map +1 -0
  4. package/dist/child/ChildBridge.js +124 -0
  5. package/dist/child/index.d.ts +4 -0
  6. package/dist/child/index.d.ts.map +1 -0
  7. package/dist/child/index.js +2 -0
  8. package/dist/child/utils.d.ts +7 -0
  9. package/dist/child/utils.d.ts.map +1 -0
  10. package/dist/child/utils.js +22 -0
  11. package/dist/constants.d.ts +29 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/constants.js +28 -0
  14. package/dist/errors/BridgeError.d.ts +21 -0
  15. package/dist/errors/BridgeError.d.ts.map +1 -0
  16. package/dist/errors/BridgeError.js +35 -0
  17. package/dist/errors/errorFactory.d.ts +14 -0
  18. package/dist/errors/errorFactory.d.ts.map +1 -0
  19. package/dist/errors/errorFactory.js +13 -0
  20. package/dist/index.d.ts +17 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +17 -0
  23. package/dist/parent/IframeChannel.d.ts +44 -0
  24. package/dist/parent/IframeChannel.d.ts.map +1 -0
  25. package/dist/parent/IframeChannel.js +96 -0
  26. package/dist/parent/ParentBridge.d.ts +41 -0
  27. package/dist/parent/ParentBridge.d.ts.map +1 -0
  28. package/dist/parent/ParentBridge.js +101 -0
  29. package/dist/parent/index.d.ts +6 -0
  30. package/dist/parent/index.d.ts.map +1 -0
  31. package/dist/parent/index.js +3 -0
  32. package/dist/parent/utils.d.ts +6 -0
  33. package/dist/parent/utils.d.ts.map +1 -0
  34. package/dist/parent/utils.js +23 -0
  35. package/dist/shared/EventEmitter.d.ts +13 -0
  36. package/dist/shared/EventEmitter.d.ts.map +1 -0
  37. package/dist/shared/EventEmitter.js +47 -0
  38. package/dist/shared/IdGenerator.d.ts +11 -0
  39. package/dist/shared/IdGenerator.d.ts.map +1 -0
  40. package/dist/shared/IdGenerator.js +16 -0
  41. package/dist/shared/MessageHandler.d.ts +75 -0
  42. package/dist/shared/MessageHandler.d.ts.map +1 -0
  43. package/dist/shared/MessageHandler.js +197 -0
  44. package/dist/shared/OriginValidator.d.ts +14 -0
  45. package/dist/shared/OriginValidator.d.ts.map +1 -0
  46. package/dist/shared/OriginValidator.js +53 -0
  47. package/dist/types.d.ts +73 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +4 -0
  50. package/package.json +31 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * IframeChannel for managing communication with a specific iframe
3
+ */
4
+ import { MessageHandler } from '@/shared/MessageHandler';
5
+ export class IframeChannel extends MessageHandler {
6
+ constructor(iframe, allowedOrigins, timeout = 5000, debug = false) {
7
+ super(allowedOrigins, timeout, debug);
8
+ this.connected = false;
9
+ this.iframe = iframe;
10
+ this.iframeOrigin = this.extractOrigin(iframe.src);
11
+ // Setup message listener for this channel
12
+ this.setupMessageListener((event) => {
13
+ // Only accept messages from the specific iframe
14
+ if (event.source === this.iframe.contentWindow) {
15
+ this.handleMessage(event.data, event.origin);
16
+ if (!this.connected) {
17
+ this.connected = true;
18
+ this.eventEmitter.emit('ready');
19
+ }
20
+ }
21
+ });
22
+ }
23
+ /**
24
+ * Extract origin from URL
25
+ */
26
+ extractOrigin(url) {
27
+ try {
28
+ return new URL(url).origin;
29
+ }
30
+ catch {
31
+ // If iframe.src is not a valid URL, return current location origin
32
+ return window.location.origin;
33
+ }
34
+ }
35
+ /**
36
+ * Call a handler in the iframe
37
+ */
38
+ async callHandler(handlerName, data, callback) {
39
+ if (!this.iframe.contentWindow) {
40
+ throw new Error('iframe contentWindow is not available');
41
+ }
42
+ const promise = this.callHandlerWithTimeout(this.iframeOrigin, handlerName, data);
43
+ if (callback) {
44
+ promise.then(callback).catch(error => console.error(error));
45
+ }
46
+ return promise;
47
+ }
48
+ /**
49
+ * Send message to iframe
50
+ */
51
+ sendMessage(targetOrigin, message) {
52
+ if (!this.iframe.contentWindow) {
53
+ if (this.debug) {
54
+ console.warn('[WebBridge] iframe contentWindow is not available');
55
+ }
56
+ return;
57
+ }
58
+ // Add version info
59
+ message._version = '1.0.0';
60
+ this.iframe.contentWindow.postMessage(message, targetOrigin);
61
+ if (this.debug) {
62
+ console.log(`[WebBridge] Sent message to iframe:`, message);
63
+ }
64
+ }
65
+ /**
66
+ * Get target origin (for this channel, it's the iframe origin)
67
+ */
68
+ getTargetOrigin() {
69
+ return this.iframeOrigin;
70
+ }
71
+ /**
72
+ * Get the iframe element
73
+ */
74
+ getIframe() {
75
+ return this.iframe;
76
+ }
77
+ /**
78
+ * Check if connected
79
+ */
80
+ isConnected() {
81
+ return this.connected && !!this.iframe.contentWindow;
82
+ }
83
+ /**
84
+ * Set connected status
85
+ */
86
+ setConnected(connected) {
87
+ this.connected = connected;
88
+ }
89
+ /**
90
+ * Destroy the channel
91
+ */
92
+ destroy() {
93
+ super.destroy();
94
+ this.connected = false;
95
+ }
96
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * ParentBridge for managing iframe communication from parent
3
+ */
4
+ import { ParentBridgeConfig, BridgeMessage } from '@/types';
5
+ import { MessageHandler } from '@/shared/MessageHandler';
6
+ import { IframeChannel } from '@/parent/IframeChannel';
7
+ export declare class ParentBridge extends MessageHandler {
8
+ private channels;
9
+ constructor(config: ParentBridgeConfig);
10
+ /**
11
+ * Create a channel for an iframe
12
+ */
13
+ createChannel(iframe: HTMLIFrameElement, options?: {
14
+ origin?: string;
15
+ }): IframeChannel;
16
+ /**
17
+ * Get channel for an iframe
18
+ */
19
+ getChannel(iframe: HTMLIFrameElement): IframeChannel | undefined;
20
+ /**
21
+ * Remove channel for an iframe
22
+ */
23
+ removeChannel(iframe: HTMLIFrameElement): void;
24
+ /**
25
+ * Send message (overrides parent method)
26
+ */
27
+ protected sendMessage(targetOrigin: string, message: BridgeMessage): void;
28
+ /**
29
+ * Get target origin
30
+ */
31
+ protected getTargetOrigin(): string;
32
+ /**
33
+ * Check if any channel is connected
34
+ */
35
+ isConnected(): boolean;
36
+ /**
37
+ * Destroy the bridge and all channels
38
+ */
39
+ destroy(): void;
40
+ }
41
+ //# sourceMappingURL=ParentBridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ParentBridge.d.ts","sourceRoot":"","sources":["../../src/parent/ParentBridge.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAGtD,qBAAa,YAAa,SAAQ,cAAc;IAC9C,OAAO,CAAC,QAAQ,CAAmD;gBAEvD,MAAM,EAAE,kBAAkB;IAqBtC;;OAEG;IACH,aAAa,CACX,MAAM,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5B,aAAa;IAuBhB;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,iBAAiB,GAAG,aAAa,GAAG,SAAS;IAIhE;;OAEG;IACH,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAY9C;;OAEG;IACH,SAAS,CAAC,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI;IAezE;;OAEG;IACH,SAAS,CAAC,eAAe,IAAI,MAAM;IAKnC;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,OAAO,IAAI,IAAI;CAWhB"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * ParentBridge for managing iframe communication from parent
3
+ */
4
+ import { MessageHandler } from '@/shared/MessageHandler';
5
+ import { IframeChannel } from '@/parent/IframeChannel';
6
+ import { DEFAULT_TIMEOUT } from '@/constants';
7
+ export class ParentBridge extends MessageHandler {
8
+ constructor(config) {
9
+ const timeout = config.timeout || DEFAULT_TIMEOUT;
10
+ super(config.allowedOrigins, timeout, config.debug);
11
+ this.channels = new Map();
12
+ // Setup global message listener for parent
13
+ this.setupMessageListener((event) => {
14
+ // Only handle messages from iframes that are registered
15
+ const channel = Array.from(this.channels.values()).find(ch => ch.getIframe().contentWindow === event.source);
16
+ if (channel) {
17
+ this.handleMessage(event.data, event.origin);
18
+ }
19
+ });
20
+ if (this.debug) {
21
+ console.log('[WebBridge] ParentBridge initialized');
22
+ }
23
+ }
24
+ /**
25
+ * Create a channel for an iframe
26
+ */
27
+ createChannel(iframe, options) {
28
+ // If channel already exists, return it
29
+ if (this.channels.has(iframe)) {
30
+ return this.channels.get(iframe);
31
+ }
32
+ // Create new channel
33
+ const channel = new IframeChannel(iframe, this.originValidator.getAllowedOrigins(), this.timeout, this.debug);
34
+ this.channels.set(iframe, channel);
35
+ if (this.debug) {
36
+ console.log(`[WebBridge] Channel created for iframe`);
37
+ }
38
+ return channel;
39
+ }
40
+ /**
41
+ * Get channel for an iframe
42
+ */
43
+ getChannel(iframe) {
44
+ return this.channels.get(iframe);
45
+ }
46
+ /**
47
+ * Remove channel for an iframe
48
+ */
49
+ removeChannel(iframe) {
50
+ const channel = this.channels.get(iframe);
51
+ if (channel) {
52
+ channel.destroy();
53
+ this.channels.delete(iframe);
54
+ if (this.debug) {
55
+ console.log('[WebBridge] Channel removed');
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Send message (overrides parent method)
61
+ */
62
+ sendMessage(targetOrigin, message) {
63
+ // Find the channel with matching origin
64
+ for (const channel of this.channels.values()) {
65
+ if (channel.getIframe().contentWindow) {
66
+ // Add version info
67
+ message._version = '1.0.0';
68
+ channel.getIframe().contentWindow.postMessage(message, targetOrigin);
69
+ if (this.debug) {
70
+ console.log('[WebBridge] Sent message from parent:', message);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ /**
76
+ * Get target origin
77
+ */
78
+ getTargetOrigin() {
79
+ // For parent bridge, return current window origin
80
+ return window.location.origin;
81
+ }
82
+ /**
83
+ * Check if any channel is connected
84
+ */
85
+ isConnected() {
86
+ return Array.from(this.channels.values()).some(ch => ch.isConnected());
87
+ }
88
+ /**
89
+ * Destroy the bridge and all channels
90
+ */
91
+ destroy() {
92
+ for (const channel of this.channels.values()) {
93
+ channel.destroy();
94
+ }
95
+ this.channels.clear();
96
+ super.destroy();
97
+ if (this.debug) {
98
+ console.log('[WebBridge] ParentBridge destroyed');
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,6 @@
1
+ export { ParentBridge } from '@/parent/ParentBridge';
2
+ export { IframeChannel } from '@/parent/IframeChannel';
3
+ export type { IframeChannelInterface } from '@/types';
4
+ export type { ParentBridgeConfig } from '@/types';
5
+ export * from '@/parent/utils';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/parent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACtD,YAAY,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AACrD,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AACjD,cAAc,gBAAgB,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { ParentBridge } from '@/parent/ParentBridge';
2
+ export { IframeChannel } from '@/parent/IframeChannel';
3
+ export * from '@/parent/utils';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Utility functions for parent bridge
3
+ */
4
+ export declare function validateIframe(iframe: HTMLIFrameElement): boolean;
5
+ export declare function getIframeOrigin(iframe: HTMLIFrameElement): string;
6
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/parent/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,cAAc,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAKjE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CASjE"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Utility functions for parent bridge
3
+ */
4
+ export function validateIframe(iframe) {
5
+ if (!iframe)
6
+ return false;
7
+ if (!(iframe instanceof HTMLIFrameElement))
8
+ return false;
9
+ if (!iframe.contentWindow)
10
+ return false;
11
+ return true;
12
+ }
13
+ export function getIframeOrigin(iframe) {
14
+ try {
15
+ if (!iframe.src) {
16
+ return window.location.origin;
17
+ }
18
+ return new URL(iframe.src).origin;
19
+ }
20
+ catch {
21
+ return window.location.origin;
22
+ }
23
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Simple EventEmitter for bridge events
3
+ */
4
+ import { BridgeEventType } from '@/types';
5
+ export declare class EventEmitter {
6
+ private listeners;
7
+ on(type: BridgeEventType, callback: (data?: any) => void): void;
8
+ off(type: BridgeEventType, callback: (data?: any) => void): void;
9
+ emit(type: BridgeEventType, data?: any): void;
10
+ removeAllListeners(type?: BridgeEventType): void;
11
+ hasListener(type: BridgeEventType): boolean;
12
+ }
13
+ //# sourceMappingURL=EventEmitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EventEmitter.d.ts","sourceRoot":"","sources":["../../src/shared/EventEmitter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAEzC,qBAAa,YAAY;IACvB,OAAO,CAAC,SAAS,CAA+D;IAEhF,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAO/D,GAAG,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAUhE,IAAI,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAa7C,kBAAkB,CAAC,IAAI,CAAC,EAAE,eAAe,GAAG,IAAI;IAQhD,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO;CAG5C"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Simple EventEmitter for bridge events
3
+ */
4
+ export class EventEmitter {
5
+ constructor() {
6
+ this.listeners = new Map();
7
+ }
8
+ on(type, callback) {
9
+ if (!this.listeners.has(type)) {
10
+ this.listeners.set(type, []);
11
+ }
12
+ this.listeners.get(type).push(callback);
13
+ }
14
+ off(type, callback) {
15
+ if (!this.listeners.has(type))
16
+ return;
17
+ const callbacks = this.listeners.get(type);
18
+ const index = callbacks.indexOf(callback);
19
+ if (index >= 0) {
20
+ callbacks.splice(index, 1);
21
+ }
22
+ }
23
+ emit(type, data) {
24
+ if (!this.listeners.has(type))
25
+ return;
26
+ const callbacks = this.listeners.get(type) || [];
27
+ callbacks.forEach(callback => {
28
+ try {
29
+ callback(data);
30
+ }
31
+ catch (error) {
32
+ console.error(`EventEmitter error in ${type} listener:`, error);
33
+ }
34
+ });
35
+ }
36
+ removeAllListeners(type) {
37
+ if (type) {
38
+ this.listeners.delete(type);
39
+ }
40
+ else {
41
+ this.listeners.clear();
42
+ }
43
+ }
44
+ hasListener(type) {
45
+ return this.listeners.has(type) && (this.listeners.get(type)?.length ?? 0) > 0;
46
+ }
47
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ID generator for unique message IDs
3
+ */
4
+ export declare class IdGenerator {
5
+ private counter;
6
+ private prefix;
7
+ constructor(prefix?: string);
8
+ generate(): string;
9
+ reset(): void;
10
+ }
11
+ //# sourceMappingURL=IdGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IdGenerator.d.ts","sourceRoot":"","sources":["../../src/shared/IdGenerator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAY;IAC3B,OAAO,CAAC,MAAM,CAAQ;gBAEV,MAAM,GAAE,MAAc;IAIlC,QAAQ,IAAI,MAAM;IAKlB,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * ID generator for unique message IDs
3
+ */
4
+ export class IdGenerator {
5
+ constructor(prefix = 'msg') {
6
+ this.counter = 0;
7
+ this.prefix = prefix;
8
+ }
9
+ generate() {
10
+ this.counter++;
11
+ return `${this.prefix}_${Date.now()}_${this.counter}_${Math.random().toString(36).substr(2, 9)}`;
12
+ }
13
+ reset() {
14
+ this.counter = 0;
15
+ }
16
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Base class for handling postMessage communication
3
+ */
4
+ import { BridgeMessage, HandlerRegistry, PendingRequest } from '@/types';
5
+ import { EventEmitter } from '@/shared/EventEmitter';
6
+ import { OriginValidator } from '@/shared/OriginValidator';
7
+ import { IdGenerator } from '@/shared/IdGenerator';
8
+ export declare abstract class MessageHandler {
9
+ protected handlers: HandlerRegistry;
10
+ protected pendingRequests: Map<string, PendingRequest>;
11
+ protected eventEmitter: EventEmitter;
12
+ protected originValidator: OriginValidator;
13
+ protected idGenerator: IdGenerator;
14
+ protected timeout: number;
15
+ protected debug: boolean;
16
+ protected messageListener: ((event: MessageEvent) => void) | null;
17
+ constructor(allowedOrigins: string[], timeout?: number, debug?: boolean);
18
+ /**
19
+ * Register a handler
20
+ */
21
+ registerHandler(handlerName: string, handler: (data?: any, callback?: Function) => void): void;
22
+ /**
23
+ * Unregister a handler
24
+ */
25
+ unregisterHandler(handlerName: string): void;
26
+ /**
27
+ * Call a handler on the other side with timeout
28
+ */
29
+ protected callHandlerWithTimeout(targetOrigin: string, handlerName: string, data?: any): Promise<any>;
30
+ /**
31
+ * Send a message to the other side (must be implemented by subclass)
32
+ */
33
+ protected abstract sendMessage(targetOrigin: string, message: BridgeMessage): void;
34
+ /**
35
+ * Get the target origin for sending messages (must be implemented by subclass)
36
+ */
37
+ protected abstract getTargetOrigin(): string;
38
+ /**
39
+ * Handle incoming message
40
+ */
41
+ protected handleMessage(message: BridgeMessage, origin: string): void;
42
+ /**
43
+ * Handle incoming call message
44
+ */
45
+ private handleCall;
46
+ /**
47
+ * Handle incoming response message
48
+ */
49
+ private handleResponse;
50
+ /**
51
+ * Setup message listener
52
+ */
53
+ protected setupMessageListener(handler: (event: MessageEvent) => void): void;
54
+ /**
55
+ * Remove message listener
56
+ */
57
+ protected removeMessageListener(): void;
58
+ /**
59
+ * Check if connected
60
+ */
61
+ abstract isConnected(): boolean;
62
+ /**
63
+ * Add event listener
64
+ */
65
+ on(type: 'ready' | 'disconnect' | 'error', callback: (data?: any) => void): void;
66
+ /**
67
+ * Remove event listener
68
+ */
69
+ off(type: 'ready' | 'disconnect' | 'error', callback: (data?: any) => void): void;
70
+ /**
71
+ * Clean up
72
+ */
73
+ destroy(): void;
74
+ }
75
+ //# sourceMappingURL=MessageHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageHandler.d.ts","sourceRoot":"","sources":["../../src/shared/MessageHandler.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAIlD,8BAAsB,cAAc;IAClC,SAAS,CAAC,QAAQ,EAAE,eAAe,CAAY;IAC/C,SAAS,CAAC,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAY;IAClE,SAAS,CAAC,YAAY,EAAE,YAAY,CAAqB;IACzD,SAAS,CAAC,eAAe,EAAE,eAAe,CAAA;IAC1C,SAAS,CAAC,WAAW,EAAE,WAAW,CAAoB;IACtD,SAAS,CAAC,OAAO,EAAE,MAAM,CAAA;IACzB,SAAS,CAAC,KAAK,EAAE,OAAO,CAAA;IACxB,SAAS,CAAC,eAAe,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,IAAI,CAAO;gBAE5D,cAAc,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,MAAwB,EAAE,KAAK,GAAE,OAAe;IAM/F;;OAEG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,QAAQ,KAAK,IAAI,GAAG,IAAI;IAO9F;;OAEG;IACH,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAO5C;;OAEG;IACH,SAAS,CAAC,sBAAsB,CAC9B,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,GAAG,GACT,OAAO,CAAC,GAAG,CAAC;IA2Bf;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,IAAI;IAElF;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,eAAe,IAAI,MAAM;IAE5C;;OAEG;IACH,SAAS,CAAC,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAgCrE;;OAEG;YACW,UAAU;IAyCxB;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,SAAS,CAAC,oBAAoB,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI;IAK5E;;OAEG;IACH,SAAS,CAAC,qBAAqB,IAAI,IAAI;IAOvC;;OAEG;IACH,QAAQ,CAAC,WAAW,IAAI,OAAO;IAE/B;;OAEG;IACH,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIhF;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIjF;;OAEG;IACH,OAAO,IAAI,IAAI;CAOhB"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Base class for handling postMessage communication
3
+ */
4
+ import { EventEmitter } from '@/shared/EventEmitter';
5
+ import { OriginValidator } from '@/shared/OriginValidator';
6
+ import { IdGenerator } from '@/shared/IdGenerator';
7
+ import { errorFactory } from '@/errors/errorFactory';
8
+ import { DEFAULT_TIMEOUT } from '@/constants';
9
+ export class MessageHandler {
10
+ constructor(allowedOrigins, timeout = DEFAULT_TIMEOUT, debug = false) {
11
+ this.handlers = new Map();
12
+ this.pendingRequests = new Map();
13
+ this.eventEmitter = new EventEmitter();
14
+ this.idGenerator = new IdGenerator();
15
+ this.messageListener = null;
16
+ this.originValidator = new OriginValidator(allowedOrigins, debug);
17
+ this.timeout = timeout;
18
+ this.debug = debug;
19
+ }
20
+ /**
21
+ * Register a handler
22
+ */
23
+ registerHandler(handlerName, handler) {
24
+ this.handlers.set(handlerName, handler);
25
+ if (this.debug) {
26
+ console.log(`[WebBridge] Handler registered: ${handlerName}`);
27
+ }
28
+ }
29
+ /**
30
+ * Unregister a handler
31
+ */
32
+ unregisterHandler(handlerName) {
33
+ this.handlers.delete(handlerName);
34
+ if (this.debug) {
35
+ console.log(`[WebBridge] Handler unregistered: ${handlerName}`);
36
+ }
37
+ }
38
+ /**
39
+ * Call a handler on the other side with timeout
40
+ */
41
+ callHandlerWithTimeout(targetOrigin, handlerName, data) {
42
+ return new Promise((resolve, reject) => {
43
+ const messageId = this.idGenerator.generate();
44
+ const timeoutHandle = setTimeout(() => {
45
+ this.pendingRequests.delete(messageId);
46
+ reject(errorFactory.timeout(this.timeout));
47
+ }, this.timeout);
48
+ this.pendingRequests.set(messageId, {
49
+ id: messageId,
50
+ timeout: timeoutHandle,
51
+ resolve,
52
+ reject
53
+ });
54
+ const message = {
55
+ type: 'call',
56
+ id: messageId,
57
+ handlerName,
58
+ data
59
+ };
60
+ this.sendMessage(targetOrigin, message);
61
+ });
62
+ }
63
+ /**
64
+ * Handle incoming message
65
+ */
66
+ handleMessage(message, origin) {
67
+ // Validate origin
68
+ if (!this.originValidator.isOriginAllowed(origin)) {
69
+ if (this.debug) {
70
+ console.warn(`[WebBridge] Message from disallowed origin: ${origin}`);
71
+ }
72
+ return;
73
+ }
74
+ try {
75
+ switch (message.type) {
76
+ case 'call':
77
+ this.handleCall(message, origin);
78
+ break;
79
+ case 'response':
80
+ this.handleResponse(message);
81
+ break;
82
+ case 'ready':
83
+ this.eventEmitter.emit('ready');
84
+ break;
85
+ default:
86
+ if (this.debug) {
87
+ console.warn(`[WebBridge] Unknown message type: ${message.type}`);
88
+ }
89
+ }
90
+ }
91
+ catch (error) {
92
+ if (this.debug) {
93
+ console.error('[WebBridge] Error handling message:', error);
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * Handle incoming call message
99
+ */
100
+ async handleCall(message, origin) {
101
+ const { id, handlerName, data } = message;
102
+ const handler = this.handlers.get(handlerName);
103
+ if (!handler) {
104
+ this.sendMessage(origin, {
105
+ type: 'response',
106
+ id,
107
+ error: `Handler "${handlerName}" not found`
108
+ });
109
+ return;
110
+ }
111
+ try {
112
+ // Execute handler
113
+ const result = await Promise.resolve(handler(data, (response) => {
114
+ this.sendMessage(origin, {
115
+ type: 'response',
116
+ id,
117
+ data: response
118
+ });
119
+ }));
120
+ // If handler returns a value directly (not using callback)
121
+ if (result !== undefined) {
122
+ this.sendMessage(origin, {
123
+ type: 'response',
124
+ id,
125
+ data: result
126
+ });
127
+ }
128
+ }
129
+ catch (error) {
130
+ const errorMessage = error instanceof Error ? error.message : String(error);
131
+ this.sendMessage(origin, {
132
+ type: 'response',
133
+ id,
134
+ error: errorMessage
135
+ });
136
+ }
137
+ }
138
+ /**
139
+ * Handle incoming response message
140
+ */
141
+ handleResponse(message) {
142
+ const { id, data, error } = message;
143
+ const pending = this.pendingRequests.get(id);
144
+ if (!pending) {
145
+ if (this.debug) {
146
+ console.warn(`[WebBridge] Received response for unknown request: ${id}`);
147
+ }
148
+ return;
149
+ }
150
+ clearTimeout(pending.timeout);
151
+ this.pendingRequests.delete(id);
152
+ if (error) {
153
+ pending.reject(new Error(error));
154
+ }
155
+ else {
156
+ pending.resolve(data);
157
+ }
158
+ }
159
+ /**
160
+ * Setup message listener
161
+ */
162
+ setupMessageListener(handler) {
163
+ window.addEventListener('message', handler);
164
+ this.messageListener = handler;
165
+ }
166
+ /**
167
+ * Remove message listener
168
+ */
169
+ removeMessageListener() {
170
+ if (this.messageListener) {
171
+ window.removeEventListener('message', this.messageListener);
172
+ this.messageListener = null;
173
+ }
174
+ }
175
+ /**
176
+ * Add event listener
177
+ */
178
+ on(type, callback) {
179
+ this.eventEmitter.on(type, callback);
180
+ }
181
+ /**
182
+ * Remove event listener
183
+ */
184
+ off(type, callback) {
185
+ this.eventEmitter.off(type, callback);
186
+ }
187
+ /**
188
+ * Clean up
189
+ */
190
+ destroy() {
191
+ this.handlers.clear();
192
+ this.pendingRequests.forEach(({ timeout }) => clearTimeout(timeout));
193
+ this.pendingRequests.clear();
194
+ this.eventEmitter.removeAllListeners();
195
+ this.removeMessageListener();
196
+ }
197
+ }