@dynamic-labs/message-transport 4.0.0-alpha.0 → 4.0.0-alpha.10

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 (53) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/package.cjs +8 -0
  3. package/package.js +4 -0
  4. package/package.json +9 -11
  5. package/src/index.cjs +14 -1
  6. package/src/index.d.ts +3 -2
  7. package/src/index.js +9 -0
  8. package/src/messageTransport/decorators/makeWaitForInitEvent/makeWaitForInitEvent.d.ts +4 -9
  9. package/src/messageTransport/decorators/makeWaitForUnblock/makeWaitForUnblock.cjs +23 -15
  10. package/src/messageTransport/decorators/makeWaitForUnblock/makeWaitForUnblock.d.ts +16 -8
  11. package/src/messageTransport/decorators/makeWaitForUnblock/makeWaitForUnblock.js +23 -15
  12. package/src/messageTypes/AccountAbstractionMessages.d.ts +15 -0
  13. package/src/messageTypes/AuthModuleMessages.cjs +14 -0
  14. package/src/messageTypes/AuthModuleMessages.d.ts +12 -1
  15. package/src/messageTypes/AuthModuleMessages.js +10 -0
  16. package/src/messageTypes/EmbeddedWalletsModuleMessages.cjs +10 -0
  17. package/src/messageTypes/EmbeddedWalletsModuleMessages.d.ts +9 -1
  18. package/src/messageTypes/EmbeddedWalletsModuleMessages.js +6 -0
  19. package/src/messageTypes/ExternalAuthMessages.d.ts +3 -2
  20. package/src/messageTypes/OtpMessages.cjs +14 -0
  21. package/src/messageTypes/OtpMessages.d.ts +10 -0
  22. package/src/messageTypes/OtpMessages.js +9 -0
  23. package/src/messageTypes/ProjectSettingsMessages.d.ts +6 -0
  24. package/src/messageTypes/SecureStorageMessages.d.ts +5 -0
  25. package/src/messageTypes/UserInterfaceModuleMessages.cjs +12 -0
  26. package/src/messageTypes/UserInterfaceModuleMessages.d.ts +6 -0
  27. package/src/messageTypes/UserInterfaceModuleMessages.js +8 -0
  28. package/src/messageTypes/WalletsModuleMessages.cjs +12 -0
  29. package/src/messageTypes/WalletsModuleMessages.d.ts +29 -0
  30. package/src/messageTypes/WalletsModuleMessages.js +8 -0
  31. package/src/messageTypes/ZeroDevExtensionMessages.d.ts +10 -0
  32. package/src/messageTypes/index.d.ts +4 -0
  33. package/src/requestChannel/createRequestChannelMessageSender/createRequestChannelMessageSender.cjs +43 -0
  34. package/src/requestChannel/createRequestChannelMessageSender/createRequestChannelMessageSender.d.ts +18 -0
  35. package/src/requestChannel/createRequestChannelMessageSender/createRequestChannelMessageSender.js +39 -0
  36. package/src/requestChannel/createRequestChannelMessageSender/index.d.ts +1 -0
  37. package/src/requestChannel/index.d.ts +1 -0
  38. package/src/requestChannel/requestChannel.cjs +58 -31
  39. package/src/requestChannel/requestChannel.d.ts +3 -36
  40. package/src/requestChannel/requestChannel.js +55 -26
  41. package/src/requestChannel/types.d.ts +55 -0
  42. package/src/requestChannel/utils/index.d.ts +1 -0
  43. package/src/requestChannel/utils/utils.cjs +33 -0
  44. package/src/requestChannel/utils/utils.d.ts +16 -0
  45. package/src/requestChannel/utils/utils.js +25 -0
  46. package/src/store/createEventEmitterForMessages/createEventEmitterForMessages.cjs +4 -5
  47. package/src/store/createEventEmitterForMessages/createEventEmitterForMessages.d.ts +5 -8
  48. package/src/store/createEventEmitterForMessages/createEventEmitterForMessages.js +4 -5
  49. package/src/store/types.d.ts +3 -11
  50. package/src/types.d.ts +5 -0
  51. package/src/utils/logger.cjs +10 -0
  52. package/src/utils/logger.d.ts +2 -0
  53. package/src/utils/logger.js +6 -0
@@ -0,0 +1,12 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ const userInterfaceEventNames = [
7
+ 'authFlowCancelled',
8
+ 'authFlowClosed',
9
+ 'authFlowOpened',
10
+ ];
11
+
12
+ exports.userInterfaceEventNames = userInterfaceEventNames;
@@ -1,8 +1,14 @@
1
1
  export type UserInterfaceModuleMessages = {
2
2
  openUserProfile: () => void;
3
+ hideUserProfile: () => void;
3
4
  openAuthFlow: () => void;
5
+ hideAuthFlow: () => void;
4
6
  /** Opens the secret key export for embedded wallets */
5
7
  revealEmbeddedWalletKey: (params: {
6
8
  type: 'recovery-phrase' | 'private-key';
7
9
  }) => void;
10
+ authFlowCancelled: () => void;
11
+ authFlowClosed: () => void;
12
+ authFlowOpened: () => void;
8
13
  };
14
+ export declare const userInterfaceEventNames: ("authFlowCancelled" | "authFlowClosed" | "authFlowOpened")[];
@@ -0,0 +1,8 @@
1
+ 'use client'
2
+ const userInterfaceEventNames = [
3
+ 'authFlowCancelled',
4
+ 'authFlowClosed',
5
+ 'authFlowOpened',
6
+ ];
7
+
8
+ export { userInterfaceEventNames };
@@ -0,0 +1,12 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ const userWalletsEventNames = [
7
+ 'messageSigned',
8
+ 'walletAdded',
9
+ 'walletRemoved',
10
+ ];
11
+
12
+ exports.userWalletsEventNames = userWalletsEventNames;
@@ -27,4 +27,33 @@ export type WalletsModuleMessages = {
27
27
  setPrimary: (params: {
28
28
  walletId: string;
29
29
  }) => Promise<void>;
30
+ onWalletEvent: (params: WalletEventArguments & {
31
+ walletId: string;
32
+ }) => void;
33
+ handleConnectedWallet: (wallet: Partial<BaseWallet>) => Promise<boolean>;
34
+ messageSigned: (params: {
35
+ messageToSign: string;
36
+ signedMessage: string;
37
+ }) => void;
38
+ walletAdded: (params: {
39
+ wallet: BaseWallet;
40
+ userWallets: BaseWallet[];
41
+ }) => void;
42
+ walletRemoved: (params: {
43
+ wallet: BaseWallet;
44
+ userWallets: BaseWallet[];
45
+ }) => void;
46
+ };
47
+ export type WalletEvents = {
48
+ chainChange: (props: {
49
+ chain: string;
50
+ }) => void;
30
51
  };
52
+ type WalletEventArguments = {
53
+ [E in keyof WalletEvents]: {
54
+ event: E;
55
+ eventParams: Parameters<WalletEvents[E]>;
56
+ };
57
+ }[keyof WalletEvents];
58
+ export declare const userWalletsEventNames: ("messageSigned" | "walletAdded" | "walletRemoved")[];
59
+ export {};
@@ -0,0 +1,8 @@
1
+ 'use client'
2
+ const userWalletsEventNames = [
3
+ 'messageSigned',
4
+ 'walletAdded',
5
+ 'walletRemoved',
6
+ ];
7
+
8
+ export { userWalletsEventNames };
@@ -0,0 +1,10 @@
1
+ export type KernelAccountSettings = {
2
+ entryPointAddress: string;
3
+ chainId: number;
4
+ ecdsaProviderType: string | null;
5
+ kernelVersion: string;
6
+ projectId: string;
7
+ };
8
+ export type ZeroDevExtensionMessages = {
9
+ getKernelAccountSettings: () => Promise<KernelAccountSettings>;
10
+ };
@@ -1,3 +1,4 @@
1
+ export * from './AccountAbstractionMessages';
1
2
  export * from './AuthModuleMessages';
2
3
  export * from './ConsoleMessages';
3
4
  export * from './EmbeddedWalletsModuleMessages';
@@ -8,9 +9,12 @@ export * from './NetworksModuleMessages';
8
9
  export * from './OtpMessages';
9
10
  export * from './PasskeyMessages';
10
11
  export * from './PlatformServiceMessages';
12
+ export * from './ProjectSettingsMessages';
11
13
  export * from './SdkModuleMessages';
14
+ export * from './SecureStorageMessages';
12
15
  export * from './SocialAuthModuleMessages';
13
16
  export * from './SolanaMessages';
14
17
  export * from './UserInterfaceModuleMessages';
15
18
  export * from './WalletsModuleMessages';
16
19
  export * from './WebViewVisibilityMessages';
20
+ export * from './ZeroDevExtensionMessages';
@@ -0,0 +1,43 @@
1
+ 'use client'
2
+ 'use strict';
3
+
4
+ Object.defineProperty(exports, '__esModule', { value: true });
5
+
6
+ var utils = require('../utils/utils.cjs');
7
+
8
+ const createRequestChannelMessageSender = ({ requestType, messageSessionId, timeoutMap, onReceiveAck, messageTransport, onTimeout, params, }) => {
9
+ const ackMessageType = utils.getAckMessageType(requestType);
10
+ const message = {
11
+ args: params,
12
+ messageSessionId,
13
+ type: requestType,
14
+ };
15
+ // We must listen for acks
16
+ const handleAckMessage = ({ messageSessionId: incomingSessionId, type: incomingType, }) => {
17
+ if (incomingSessionId !== messageSessionId ||
18
+ incomingType !== ackMessageType)
19
+ return;
20
+ clearTimeout(timeoutMap[messageSessionId]);
21
+ delete timeoutMap[messageSessionId];
22
+ onReceiveAck === null || onReceiveAck === void 0 ? void 0 : onReceiveAck();
23
+ };
24
+ const sendMessage = () => {
25
+ const startTimer = () => {
26
+ const timeoutTimer = setTimeout(onTimeout, utils.TIMEOUT_DURATION);
27
+ timeoutMap[messageSessionId] = timeoutTimer;
28
+ };
29
+ // If this message transport is currently blocked, we must only start
30
+ // the timeout once the message is actually emitted
31
+ if ('isBlocked' in messageTransport && messageTransport.isBlocked) {
32
+ messageTransport.emit(message, { onEmit: startTimer });
33
+ return;
34
+ }
35
+ // Now we emit the event to set off the request
36
+ messageTransport.emit(message);
37
+ // We start the timer immediately since the message was not blocked
38
+ startTimer();
39
+ };
40
+ return { handleAckMessage, sendMessage };
41
+ };
42
+
43
+ exports.createRequestChannelMessageSender = createRequestChannelMessageSender;
@@ -0,0 +1,18 @@
1
+ /// <reference types="node" />
2
+ import { MessageTransportData, WithBlock } from '../../messageTransport';
3
+ import { MessageTransportWithDefaultOrigin } from '../../messageTransport/decorators/applyDefaultMessageOrigin/applyDefaultMessageOrigin';
4
+ import { RequestTypes } from '../types';
5
+ type RequestChannelMessageSenderParams<T extends RequestTypes, K extends Extract<keyof T, string>> = {
6
+ requestType: K;
7
+ messageSessionId: string;
8
+ timeoutMap: Record<string, NodeJS.Timer>;
9
+ onReceiveAck?: VoidFunction;
10
+ onTimeout: VoidFunction;
11
+ messageTransport: MessageTransportWithDefaultOrigin | WithBlock<MessageTransportWithDefaultOrigin>;
12
+ params: Parameters<T[K]>;
13
+ };
14
+ export declare const createRequestChannelMessageSender: <T extends RequestTypes, K extends Extract<keyof T, string>>({ requestType, messageSessionId, timeoutMap, onReceiveAck, messageTransport, onTimeout, params, }: RequestChannelMessageSenderParams<T, K>) => {
15
+ handleAckMessage: ({ messageSessionId: incomingSessionId, type: incomingType, }: MessageTransportData) => void;
16
+ sendMessage: () => void;
17
+ };
18
+ export {};
@@ -0,0 +1,39 @@
1
+ 'use client'
2
+ import { getAckMessageType, TIMEOUT_DURATION } from '../utils/utils.js';
3
+
4
+ const createRequestChannelMessageSender = ({ requestType, messageSessionId, timeoutMap, onReceiveAck, messageTransport, onTimeout, params, }) => {
5
+ const ackMessageType = getAckMessageType(requestType);
6
+ const message = {
7
+ args: params,
8
+ messageSessionId,
9
+ type: requestType,
10
+ };
11
+ // We must listen for acks
12
+ const handleAckMessage = ({ messageSessionId: incomingSessionId, type: incomingType, }) => {
13
+ if (incomingSessionId !== messageSessionId ||
14
+ incomingType !== ackMessageType)
15
+ return;
16
+ clearTimeout(timeoutMap[messageSessionId]);
17
+ delete timeoutMap[messageSessionId];
18
+ onReceiveAck === null || onReceiveAck === void 0 ? void 0 : onReceiveAck();
19
+ };
20
+ const sendMessage = () => {
21
+ const startTimer = () => {
22
+ const timeoutTimer = setTimeout(onTimeout, TIMEOUT_DURATION);
23
+ timeoutMap[messageSessionId] = timeoutTimer;
24
+ };
25
+ // If this message transport is currently blocked, we must only start
26
+ // the timeout once the message is actually emitted
27
+ if ('isBlocked' in messageTransport && messageTransport.isBlocked) {
28
+ messageTransport.emit(message, { onEmit: startTimer });
29
+ return;
30
+ }
31
+ // Now we emit the event to set off the request
32
+ messageTransport.emit(message);
33
+ // We start the timer immediately since the message was not blocked
34
+ startTimer();
35
+ };
36
+ return { handleAckMessage, sendMessage };
37
+ };
38
+
39
+ export { createRequestChannelMessageSender };
@@ -0,0 +1 @@
1
+ export * from './createRequestChannelMessageSender';
@@ -1 +1,2 @@
1
1
  export * from './requestChannel';
2
+ export * from './types';
@@ -4,14 +4,12 @@
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
6
  var _tslib = require('../../_virtual/_tslib.cjs');
7
+ var isSerializedError = require('../utils/isSerializedError/isSerializedError.cjs');
7
8
  var parseErrorFromTransport = require('../utils/parseErrorFromTransport/parseErrorFromTransport.cjs');
8
9
  var serializeErrorForTransport = require('../utils/serializeErrorForTransport/serializeErrorForTransport.cjs');
9
- var isSerializedError = require('../utils/isSerializedError/isSerializedError.cjs');
10
+ var createRequestChannelMessageSender = require('./createRequestChannelMessageSender/createRequestChannelMessageSender.cjs');
11
+ var utils = require('./utils/utils.cjs');
10
12
 
11
- /** Given a request event name, returns the event name for its resolve */
12
- const getResolveMessageType = (type) => `${type}__resolve`;
13
- /** Given a request event name, returns the event name for its reject */
14
- const getRejectMessageType = (type) => `${type}__reject`;
15
13
  /**
16
14
  * Allows handling and submitting requests to and from a webview.
17
15
  * Requests are messages that (can) expect some response.
@@ -37,22 +35,43 @@ const createRequestChannel = (messageTransport) => {
37
35
  const idPrefix = Math.random().toString();
38
36
  /** Always returns a different string */
39
37
  const getUniqueId = () => `${idPrefix}-${uniqueIdCounter++}`;
38
+ /** Maps a request's session ID to its time out timer */
39
+ const timeoutMap = {};
40
40
  return {
41
- emit: (requestName, ...params) => {
41
+ emit: (requestType, ...params) => new Promise((resolve, reject) => {
42
42
  // Generate the unique id for this message exchange session
43
43
  // Although we won't listen for a response, it must still be unique
44
44
  // to avoid tangling with other requests.
45
45
  const messageSessionId = getUniqueId();
46
- messageTransport.emit({
47
- args: params,
46
+ const { handleAckMessage, sendMessage } = createRequestChannelMessageSender.createRequestChannelMessageSender({
48
47
  messageSessionId,
49
- type: requestName,
48
+ messageTransport,
49
+ onReceiveAck: () => {
50
+ cleanupMessageHandler();
51
+ resolve();
52
+ },
53
+ onTimeout: () => {
54
+ cleanupMessageHandler();
55
+ reject(utils.createNoHandlerError(requestType));
56
+ },
57
+ params,
58
+ requestType,
59
+ timeoutMap,
50
60
  });
51
- },
61
+ const cleanupMessageHandler = () => messageTransport.off(handleAckMessage);
62
+ messageTransport.on(handleAckMessage);
63
+ sendMessage();
64
+ }),
52
65
  handle: (requestType, handler) => {
53
66
  const messageHandler = (_a) => _tslib.__awaiter(void 0, [_a], void 0, function* ({ args, messageSessionId, type: incomingType, }) {
54
67
  if (requestType !== incomingType)
55
68
  return;
69
+ // Emit an Ack since the message will time out unless some handler acks it in time
70
+ messageTransport.emit({
71
+ args: [],
72
+ messageSessionId,
73
+ type: utils.getAckMessageType(requestType),
74
+ });
56
75
  const result = handler(...args);
57
76
  // If the handler doesn't return a promise,
58
77
  // that means we don't need to respond.
@@ -63,14 +82,14 @@ const createRequestChannel = (messageTransport) => {
63
82
  messageTransport.emit({
64
83
  args: [response],
65
84
  messageSessionId,
66
- type: getResolveMessageType(requestType),
85
+ type: utils.getResolveMessageType(requestType),
67
86
  });
68
87
  }
69
88
  catch (error) {
70
89
  messageTransport.emit({
71
90
  args: [serializeErrorForTransport.serializeErrorForTransport(error)],
72
91
  messageSessionId,
73
- type: getRejectMessageType(requestType),
92
+ type: utils.getRejectMessageType(requestType),
74
93
  });
75
94
  }
76
95
  });
@@ -79,43 +98,51 @@ const createRequestChannel = (messageTransport) => {
79
98
  messageTransport.off(messageHandler);
80
99
  };
81
100
  },
82
- request: (requestName, ...params) => new Promise((resolve, reject) => {
83
- const requestType = requestName;
101
+ request: (requestType, ...params) => new Promise((resolve, reject) => {
84
102
  // Generate the unique id for this message exchange session
103
+ // Although we won't listen for a response, it must still be unique
104
+ // to avoid tangling with other requests.
85
105
  const messageSessionId = getUniqueId();
86
- const resolveMessageType = getResolveMessageType(requestType);
87
- const rejectMessageType = getRejectMessageType(requestType);
88
- // Before actually emitting the event, we start listening to the
89
- // response events
90
- const handleMessage = ({ args: [result], messageSessionId: incomingSessionId, type: incomingType, }) => {
91
- if (incomingSessionId !== messageSessionId)
106
+ const resolveMessageType = utils.getResolveMessageType(requestType);
107
+ const rejectMessageType = utils.getRejectMessageType(requestType);
108
+ const { handleAckMessage, sendMessage } = createRequestChannelMessageSender.createRequestChannelMessageSender({
109
+ messageSessionId,
110
+ messageTransport,
111
+ onTimeout: () => {
112
+ cleanupMessageHandler();
113
+ reject(utils.createNoHandlerError(requestType));
114
+ },
115
+ params,
116
+ requestType,
117
+ timeoutMap,
118
+ });
119
+ // We also need to listen to resolve and reject messages, besides acks
120
+ const handleMessage = (message) => {
121
+ if (message.messageSessionId !== messageSessionId)
92
122
  return;
123
+ const { args: [result], type: incomingType, } = message;
93
124
  if (incomingType === resolveMessageType) {
125
+ cleanupMessageHandler();
94
126
  resolve(result);
95
- cleanup();
127
+ return;
96
128
  }
97
129
  if (incomingType === rejectMessageType) {
130
+ cleanupMessageHandler();
98
131
  if (isSerializedError.isSerializedError(result)) {
99
132
  reject(parseErrorFromTransport.parseErrorFromTransport(result));
100
133
  }
101
134
  else {
102
135
  reject(result);
103
136
  }
104
- cleanup();
137
+ return;
105
138
  }
139
+ handleAckMessage(message);
106
140
  };
107
- const cleanup = () => messageTransport.off(handleMessage);
141
+ const cleanupMessageHandler = () => messageTransport.off(handleMessage);
108
142
  messageTransport.on(handleMessage);
109
- // Now we emit the event to set off the request
110
- messageTransport.emit({
111
- args: params,
112
- messageSessionId,
113
- type: requestType,
114
- });
143
+ sendMessage();
115
144
  }),
116
145
  };
117
146
  };
118
147
 
119
148
  exports.createRequestChannel = createRequestChannel;
120
- exports.getRejectMessageType = getRejectMessageType;
121
- exports.getResolveMessageType = getResolveMessageType;
@@ -1,38 +1,6 @@
1
+ import { WithBlock } from '../messageTransport';
1
2
  import { MessageTransportWithDefaultOrigin } from '../messageTransport/decorators/applyDefaultMessageOrigin/applyDefaultMessageOrigin';
2
- /**
3
- * An object that defines request types, and whether they expect a response
4
- */
5
- export type RequestTypes = Record<string, (...params: any[]) => Promise<any> | void>;
6
- /**
7
- * Only the request types from T that return promises
8
- * i.e. those that expect a response
9
- */
10
- type TypesExpectingResponse<T extends RequestTypes> = {
11
- [K in keyof T]: ReturnType<T[K]> extends Promise<any> ? K : never;
12
- }[keyof T];
13
- export type RequestChannel<T extends RequestTypes> = {
14
- /**
15
- * Listens to incoming requests of this type, and calls the handler when they arrive.
16
- * If the type of this request expects some response, the handler must return a promise
17
- * that resolves to this response.
18
- * @returns an unsubscribe function.
19
- */
20
- handle: <K extends keyof T>(requestType: K, handler: T[K]) => VoidFunction;
21
- /**
22
- * Triggers handlers for this request type, with the given params.
23
- * Doesn't care whether this request will have a response or not, so returns void.
24
- */
25
- emit: <K extends keyof T>(requestName: K, ...params: Parameters<T[K]>) => void;
26
- /**
27
- * Triggers handlers for this request type, with the given params.
28
- * @returns a promise that resolves/rejects when a handler fulfills this request.
29
- */
30
- request: <K extends TypesExpectingResponse<T>>(requestName: K, ...params: Parameters<T[K]>) => ReturnType<T[K]>;
31
- };
32
- /** Given a request event name, returns the event name for its resolve */
33
- export declare const getResolveMessageType: (type: string) => string;
34
- /** Given a request event name, returns the event name for its reject */
35
- export declare const getRejectMessageType: (type: string) => string;
3
+ import { RequestChannel, RequestTypes } from './types';
36
4
  /**
37
5
  * Allows handling and submitting requests to and from a webview.
38
6
  * Requests are messages that (can) expect some response.
@@ -51,5 +19,4 @@ export declare const getRejectMessageType: (type: string) => string;
51
19
  *
52
20
  * This is an abstraction built on top of the MessageTransport interface.
53
21
  */
54
- export declare const createRequestChannel: <T extends RequestTypes = never>(messageTransport: MessageTransportWithDefaultOrigin) => RequestChannel<T>;
55
- export {};
22
+ export declare const createRequestChannel: <T extends RequestTypes = never>(messageTransport: MessageTransportWithDefaultOrigin | WithBlock<MessageTransportWithDefaultOrigin>) => RequestChannel<T>;
@@ -1,13 +1,11 @@
1
1
  'use client'
2
2
  import { __awaiter } from '../../_virtual/_tslib.js';
3
+ import { isSerializedError } from '../utils/isSerializedError/isSerializedError.js';
3
4
  import { parseErrorFromTransport } from '../utils/parseErrorFromTransport/parseErrorFromTransport.js';
4
5
  import { serializeErrorForTransport } from '../utils/serializeErrorForTransport/serializeErrorForTransport.js';
5
- import { isSerializedError } from '../utils/isSerializedError/isSerializedError.js';
6
+ import { createRequestChannelMessageSender } from './createRequestChannelMessageSender/createRequestChannelMessageSender.js';
7
+ import { createNoHandlerError, getAckMessageType, getResolveMessageType, getRejectMessageType } from './utils/utils.js';
6
8
 
7
- /** Given a request event name, returns the event name for its resolve */
8
- const getResolveMessageType = (type) => `${type}__resolve`;
9
- /** Given a request event name, returns the event name for its reject */
10
- const getRejectMessageType = (type) => `${type}__reject`;
11
9
  /**
12
10
  * Allows handling and submitting requests to and from a webview.
13
11
  * Requests are messages that (can) expect some response.
@@ -33,22 +31,43 @@ const createRequestChannel = (messageTransport) => {
33
31
  const idPrefix = Math.random().toString();
34
32
  /** Always returns a different string */
35
33
  const getUniqueId = () => `${idPrefix}-${uniqueIdCounter++}`;
34
+ /** Maps a request's session ID to its time out timer */
35
+ const timeoutMap = {};
36
36
  return {
37
- emit: (requestName, ...params) => {
37
+ emit: (requestType, ...params) => new Promise((resolve, reject) => {
38
38
  // Generate the unique id for this message exchange session
39
39
  // Although we won't listen for a response, it must still be unique
40
40
  // to avoid tangling with other requests.
41
41
  const messageSessionId = getUniqueId();
42
- messageTransport.emit({
43
- args: params,
42
+ const { handleAckMessage, sendMessage } = createRequestChannelMessageSender({
44
43
  messageSessionId,
45
- type: requestName,
44
+ messageTransport,
45
+ onReceiveAck: () => {
46
+ cleanupMessageHandler();
47
+ resolve();
48
+ },
49
+ onTimeout: () => {
50
+ cleanupMessageHandler();
51
+ reject(createNoHandlerError(requestType));
52
+ },
53
+ params,
54
+ requestType,
55
+ timeoutMap,
46
56
  });
47
- },
57
+ const cleanupMessageHandler = () => messageTransport.off(handleAckMessage);
58
+ messageTransport.on(handleAckMessage);
59
+ sendMessage();
60
+ }),
48
61
  handle: (requestType, handler) => {
49
62
  const messageHandler = (_a) => __awaiter(void 0, [_a], void 0, function* ({ args, messageSessionId, type: incomingType, }) {
50
63
  if (requestType !== incomingType)
51
64
  return;
65
+ // Emit an Ack since the message will time out unless some handler acks it in time
66
+ messageTransport.emit({
67
+ args: [],
68
+ messageSessionId,
69
+ type: getAckMessageType(requestType),
70
+ });
52
71
  const result = handler(...args);
53
72
  // If the handler doesn't return a promise,
54
73
  // that means we don't need to respond.
@@ -75,41 +94,51 @@ const createRequestChannel = (messageTransport) => {
75
94
  messageTransport.off(messageHandler);
76
95
  };
77
96
  },
78
- request: (requestName, ...params) => new Promise((resolve, reject) => {
79
- const requestType = requestName;
97
+ request: (requestType, ...params) => new Promise((resolve, reject) => {
80
98
  // Generate the unique id for this message exchange session
99
+ // Although we won't listen for a response, it must still be unique
100
+ // to avoid tangling with other requests.
81
101
  const messageSessionId = getUniqueId();
82
102
  const resolveMessageType = getResolveMessageType(requestType);
83
103
  const rejectMessageType = getRejectMessageType(requestType);
84
- // Before actually emitting the event, we start listening to the
85
- // response events
86
- const handleMessage = ({ args: [result], messageSessionId: incomingSessionId, type: incomingType, }) => {
87
- if (incomingSessionId !== messageSessionId)
104
+ const { handleAckMessage, sendMessage } = createRequestChannelMessageSender({
105
+ messageSessionId,
106
+ messageTransport,
107
+ onTimeout: () => {
108
+ cleanupMessageHandler();
109
+ reject(createNoHandlerError(requestType));
110
+ },
111
+ params,
112
+ requestType,
113
+ timeoutMap,
114
+ });
115
+ // We also need to listen to resolve and reject messages, besides acks
116
+ const handleMessage = (message) => {
117
+ if (message.messageSessionId !== messageSessionId)
88
118
  return;
119
+ const { args: [result], type: incomingType, } = message;
89
120
  if (incomingType === resolveMessageType) {
121
+ cleanupMessageHandler();
90
122
  resolve(result);
91
- cleanup();
123
+ return;
92
124
  }
93
125
  if (incomingType === rejectMessageType) {
126
+ cleanupMessageHandler();
94
127
  if (isSerializedError(result)) {
95
128
  reject(parseErrorFromTransport(result));
96
129
  }
97
130
  else {
98
131
  reject(result);
99
132
  }
100
- cleanup();
133
+ return;
101
134
  }
135
+ handleAckMessage(message);
102
136
  };
103
- const cleanup = () => messageTransport.off(handleMessage);
137
+ const cleanupMessageHandler = () => messageTransport.off(handleMessage);
104
138
  messageTransport.on(handleMessage);
105
- // Now we emit the event to set off the request
106
- messageTransport.emit({
107
- args: params,
108
- messageSessionId,
109
- type: requestType,
110
- });
139
+ sendMessage();
111
140
  }),
112
141
  };
113
142
  };
114
143
 
115
- export { createRequestChannel, getRejectMessageType, getResolveMessageType };
144
+ export { createRequestChannel };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * An object that defines request types, and whether they expect a response
3
+ */
4
+ export type RequestTypes = Record<string, (...params: any[]) => Promise<any> | void>;
5
+ /**
6
+ * Only the request types from T that return promises
7
+ * i.e. those that expect a response
8
+ */
9
+ type TypesExpectingResponse<T extends RequestTypes> = Extract<{
10
+ [K in keyof T]: ReturnType<T[K]> extends Promise<any> ? K : never;
11
+ }[keyof T], string>;
12
+ export type RequestChannel<T extends RequestTypes> = {
13
+ /**
14
+ * Listens to incoming requests of this type, and calls the handler when they arrive.
15
+ * If the type of this request expects some response, the handler must return a promise
16
+ * that resolves to this response.
17
+ *
18
+ * @returns an unsubscribe function.
19
+ */
20
+ handle: <K extends Extract<keyof T, string>>(requestType: K, handler: T[K]) => VoidFunction;
21
+ /**
22
+ * Triggers handlers for this request type, with the given params.
23
+ * Doesn't expect a response. Resolves as soon as the message is acknowledged by
24
+ * any handler.
25
+ *
26
+ * If no handlers emit an ack for this message, this will reject.
27
+ *
28
+ * Lifetime of an "emit" message:
29
+ * 1. "Emit" message is sent.
30
+ * 2. A handler receives the message.
31
+ * 3. The handler immediately emits an ack message for it, acknowledging it.
32
+ * 4. This method's promise resolves.
33
+ */
34
+ emit: <K extends Extract<keyof T, string>>(requestName: K, ...params: Parameters<T[K]>) => Promise<void>;
35
+ /**
36
+ * Triggers handlers for this request type, with the given params.
37
+ * As opposed to emit, this expects a response message. A handler must still
38
+ * acknowledge this message, but the promise will only resolve when the handler
39
+ * emits back a response message. The promise resolves with whatever data was sent
40
+ * in the response.
41
+ *
42
+ * If no handlers emit an ack for this message, or if a handler responds
43
+ * with a failure, this will reject.
44
+ *
45
+ * Lifetime of a "request" message:
46
+ * 1. "Request" message is sent.
47
+ * 2. A handler receives the message.
48
+ * 3. The handler immediately emits an ack message for it, acknowledging it.
49
+ * 4. The handler performs the requested action.
50
+ * 5. The handler emits a response message.
51
+ * 6. This method's promise resolves with the response's data.
52
+ */
53
+ request: <K extends TypesExpectingResponse<T>>(requestName: K, ...params: Parameters<T[K]>) => ReturnType<T[K]>;
54
+ };
55
+ export {};
@@ -0,0 +1 @@
1
+ export * from './utils';