@adobe/uix-core 0.6.5-1 → 0.7.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/dist/__helpers__/jest.messagechannel.d.cts +2 -0
- package/dist/__helpers__/jest.messagechannel.d.cts.map +1 -0
- package/dist/__mocks__/mock-finalization-registry.d.ts +11 -0
- package/dist/__mocks__/mock-finalization-registry.d.ts.map +1 -0
- package/dist/__mocks__/mock-weak-ref.d.ts +7 -0
- package/dist/__mocks__/mock-weak-ref.d.ts.map +1 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/cross-realm-object.d.ts +44 -0
- package/dist/cross-realm-object.d.ts.map +1 -0
- package/dist/debuglog.d.ts +11 -0
- package/dist/debuglog.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +817 -7
- package/dist/index.js.map +1 -1
- package/dist/message-wrapper.d.ts +9 -0
- package/dist/message-wrapper.d.ts.map +1 -0
- package/dist/object-simulator.d.ts +28 -0
- package/dist/object-simulator.d.ts.map +1 -0
- package/dist/object-simulator.test.d.ts +2 -0
- package/dist/object-simulator.test.d.ts.map +1 -0
- package/dist/object-walker.d.ts +29 -0
- package/dist/object-walker.d.ts.map +1 -0
- package/dist/promises/index.d.ts +3 -0
- package/dist/promises/index.d.ts.map +1 -0
- package/dist/promises/promise-wrappers.test.d.ts +2 -0
- package/dist/promises/promise-wrappers.test.d.ts.map +1 -0
- package/dist/promises/timed.d.ts +15 -0
- package/dist/promises/timed.d.ts.map +1 -0
- package/dist/promises/wait.d.ts +7 -0
- package/dist/promises/wait.d.ts.map +1 -0
- package/dist/remote-subject.d.ts +70 -0
- package/dist/remote-subject.d.ts.map +1 -0
- package/dist/rpc/call-receiver.d.ts +4 -0
- package/dist/rpc/call-receiver.d.ts.map +1 -0
- package/dist/rpc/call-receiver.test.d.ts +2 -0
- package/dist/rpc/call-receiver.test.d.ts.map +1 -0
- package/dist/rpc/call-sender.d.ts +4 -0
- package/dist/rpc/call-sender.d.ts.map +1 -0
- package/dist/rpc/call-sender.test.d.ts +2 -0
- package/dist/rpc/call-sender.test.d.ts.map +1 -0
- package/dist/rpc/index.d.ts +3 -0
- package/dist/rpc/index.d.ts.map +1 -0
- package/dist/tickets.d.ts +34 -0
- package/dist/tickets.d.ts.map +1 -0
- package/dist/tunnel/index.d.ts +2 -0
- package/dist/tunnel/index.d.ts.map +1 -0
- package/dist/tunnel/tunnel-message.d.ts +19 -0
- package/dist/tunnel/tunnel-message.d.ts.map +1 -0
- package/dist/tunnel/tunnel.d.ts +58 -0
- package/dist/tunnel/tunnel.d.ts.map +1 -0
- package/dist/tunnel/tunnel.test.d.ts +2 -0
- package/dist/tunnel/tunnel.test.d.ts.map +1 -0
- package/dist/types.d.ts +1 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/value-assertions.d.ts +10 -0
- package/dist/value-assertions.d.ts.map +1 -0
- package/package.json +4 -1
- package/src/__helpers__/jest.messagechannel.cjs +3 -0
- package/src/__mocks__/mock-finalization-registry.ts +13 -0
- package/src/__mocks__/mock-weak-ref.ts +10 -0
- package/src/constants.ts +6 -0
- package/src/cross-realm-object.ts +117 -0
- package/src/debuglog.ts +1 -1
- package/src/index.ts +4 -1
- package/src/message-wrapper.ts +35 -0
- package/src/object-simulator.test.ts +135 -0
- package/src/object-simulator.ts +136 -0
- package/src/object-walker.ts +92 -0
- package/src/promises/index.ts +2 -0
- package/src/promises/promise-wrappers.test.ts +63 -0
- package/src/promises/timed.ts +41 -0
- package/src/promises/wait.ts +10 -0
- package/src/remote-subject.ts +185 -0
- package/src/rpc/call-receiver.test.ts +90 -0
- package/src/rpc/call-receiver.ts +29 -0
- package/src/rpc/call-sender.test.ts +73 -0
- package/src/rpc/call-sender.ts +72 -0
- package/src/rpc/index.ts +2 -0
- package/src/tickets.ts +71 -0
- package/src/tunnel/index.ts +1 -0
- package/src/tunnel/tunnel-message.ts +75 -0
- package/src/tunnel/tunnel.test.ts +196 -0
- package/src/tunnel/tunnel.ts +311 -0
- package/src/types.ts +3 -5
- package/src/value-assertions.ts +48 -0
- package/tsconfig.json +2 -6
- package/dist/timeout-promise.d.ts +0 -12
- package/dist/timeout-promise.d.ts.map +0 -1
- package/src/timeout-promise.ts +0 -36
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import EventEmitter from "eventemitter3";
|
|
2
|
+
/** @alpha */
|
|
3
|
+
export interface TunnelConfig {
|
|
4
|
+
/**
|
|
5
|
+
* To ensure secure communication, target origin must be specified, so the
|
|
6
|
+
* tunnel can't connect to an unauthorized domain. Can be '*' to disable
|
|
7
|
+
* origin checks, but this is discouraged!
|
|
8
|
+
*/
|
|
9
|
+
targetOrigin: string;
|
|
10
|
+
/**
|
|
11
|
+
* A Promise for a tunnel will reject if not connected within timeout (ms).
|
|
12
|
+
* @defaultValue 4000
|
|
13
|
+
*/
|
|
14
|
+
timeout: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An EventEmitter across two documents. It emits events on the remote document
|
|
18
|
+
* and takes subscribers from the local document.
|
|
19
|
+
* @alpha
|
|
20
|
+
*/
|
|
21
|
+
export declare class Tunnel extends EventEmitter {
|
|
22
|
+
private _messagePort;
|
|
23
|
+
config: TunnelConfig;
|
|
24
|
+
constructor(config: TunnelConfig);
|
|
25
|
+
/**
|
|
26
|
+
* Create a Tunnel that connects to the page running in the provided iframe.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* Returns a Tunnel that listens for connection requests from the page in the
|
|
30
|
+
* provided iframe, which it will send periodically until timeout if that page
|
|
31
|
+
* has called {@link Tunnel.toParent}. If it receives one, the Tunnel will accept the
|
|
32
|
+
* connection and send an exclusive MessagePort to the xrobject on the other
|
|
33
|
+
* end. The tunnel may reconnect if the iframe reloads, in which case it will
|
|
34
|
+
* emit another "connected" event.
|
|
35
|
+
*
|
|
36
|
+
* @alpha
|
|
37
|
+
*/
|
|
38
|
+
static toIframe(target: HTMLIFrameElement, options: Partial<TunnelConfig>): Tunnel;
|
|
39
|
+
/**
|
|
40
|
+
* Create a Tunnel that connects to the page running in the parent window.
|
|
41
|
+
*
|
|
42
|
+
* @remarks
|
|
43
|
+
* Returns a Tunnel that starts sending connection requests to the parent
|
|
44
|
+
* window, sending them periodically until the window responds with an accept
|
|
45
|
+
* message or the timeout passes. The parent window will accept the request if
|
|
46
|
+
* it calls {@link Tunnel.toIframe}.
|
|
47
|
+
*
|
|
48
|
+
* @alpha
|
|
49
|
+
*/
|
|
50
|
+
static toParent(source: WindowProxy, opts: Partial<TunnelConfig>): Tunnel;
|
|
51
|
+
connect(remote: MessagePort): void;
|
|
52
|
+
destroy(): void;
|
|
53
|
+
emit(type: string | symbol, payload?: unknown): boolean;
|
|
54
|
+
emitLocal: (type: string | symbol, payload?: unknown) => any;
|
|
55
|
+
private static _normalizeConfig;
|
|
56
|
+
private _emitFromMessage;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=tunnel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAiCzC,aAAa;AACb,MAAM,WAAW,YAAY;IAG3B;;;;OAIG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;CAGjB;AAuBD;;;;GAIG;AACH,qBAAa,MAAO,SAAQ,YAAY;IAGtC,OAAO,CAAC,YAAY,CAAc;IAElC,MAAM,EAAE,YAAY,CAAC;gBAMT,MAAM,EAAE,YAAY;IAShC;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,QAAQ,CACb,MAAM,EAAE,iBAAiB,EACzB,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,GAC7B,MAAM;IAyDT;;;;;;;;;;OAUG;IACH,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,MAAM;IAwDzE,OAAO,CAAC,MAAM,EAAE,WAAW;IAW3B,OAAO,IAAI,IAAI;IAUf,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO;IAQvD,SAAS,SAAU,MAAM,GAAG,MAAM,YAAY,OAAO,SAEnD;IAMF,OAAO,CAAC,MAAM,CAAC,gBAAgB;IA8B/B,OAAO,CAAC,gBAAgB,CAEtB;CAGH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.test.d.ts","sourceRoot":"","sources":["../../src/tunnel/tunnel.test.ts"],"names":[],"mappings":""}
|
package/dist/types.d.ts
CHANGED
|
@@ -133,10 +133,7 @@ export interface HostConnection<T = unknown> {
|
|
|
133
133
|
export interface GuestConnection {
|
|
134
134
|
id: string;
|
|
135
135
|
url: URL;
|
|
136
|
-
attachUI(frame: HTMLIFrameElement, privateMethods?: RemoteHostApis):
|
|
137
|
-
promise: Promise<unknown>;
|
|
138
|
-
destroy: Function;
|
|
139
|
-
};
|
|
136
|
+
attachUI(frame: HTMLIFrameElement, privateMethods?: RemoteHostApis): Promise<unknown>;
|
|
140
137
|
load(): Promise<unknown>;
|
|
141
138
|
error?: Error;
|
|
142
139
|
hasCapabilities(capabilities: unknown): boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export declare type Primitive = string | number | boolean;
|
|
3
|
+
export declare function isPlainObject<T>(value: unknown): value is T & object;
|
|
4
|
+
export declare function isPrimitive(value: unknown): value is Primitive;
|
|
5
|
+
export declare function isIterable<T>(value: unknown): value is T[];
|
|
6
|
+
export declare function isFunction(value: unknown): value is CallableFunction;
|
|
7
|
+
export declare function hasProp(value: unknown, prop: string): boolean;
|
|
8
|
+
export declare function isTunnelSource(value: unknown): value is Window | ServiceWorker;
|
|
9
|
+
export declare function isIframe(value: unknown): value is HTMLIFrameElement;
|
|
10
|
+
//# sourceMappingURL=value-assertions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"value-assertions.d.ts","sourceRoot":"","sources":["../src/value-assertions.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,oBAAY,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAElD,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,CAAC,GAAG,MAAM,CAMpE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAM9D;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,CAAC,EAAE,CAE1D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAEpE;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAEnD;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,MAAM,GAAG,aAAa,CAMjC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAMnE"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
type CleanupHandler = (heldValue: any) => void;
|
|
2
|
+
const register = jest.fn();
|
|
3
|
+
const unregister = jest.fn();
|
|
4
|
+
export class FakeFinalizationRegistry {
|
|
5
|
+
static mock: FakeFinalizationRegistry;
|
|
6
|
+
register = register;
|
|
7
|
+
unregister = unregister;
|
|
8
|
+
cleanupHandler: jest.MockedFunction<CleanupHandler>;
|
|
9
|
+
constructor(handler: CleanupHandler) {
|
|
10
|
+
this.cleanupHandler = jest.fn(handler);
|
|
11
|
+
FakeFinalizationRegistry.mock = this;
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { WrappedMessage } from "./message-wrapper";
|
|
2
|
+
import { wrap } from "./message-wrapper";
|
|
3
|
+
import { ObjectSimulator } from "./object-simulator";
|
|
4
|
+
import type { Asynced } from "./object-walker";
|
|
5
|
+
import { timeoutPromise } from "./promises/timed";
|
|
6
|
+
import { receiveCalls } from "./rpc";
|
|
7
|
+
import type { InitTicket } from "./tickets";
|
|
8
|
+
import { INIT_TICKET } from "./tickets";
|
|
9
|
+
import type { TunnelConfig } from "./tunnel";
|
|
10
|
+
import { Tunnel } from "./tunnel";
|
|
11
|
+
|
|
12
|
+
const INIT_MESSAGE: WrappedMessage<InitTicket> = wrap(INIT_TICKET);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Representation of an object on the other side of an iframe/window divide
|
|
16
|
+
* between JS runtimes.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* At first, xrobject simply returned the proxy to the remote object and did
|
|
20
|
+
* not expose any of the underlying event handling. However, there was no way
|
|
21
|
+
* for a consumer to handle the case where the remote iframe reloaded, which
|
|
22
|
+
* would invalidate all of the simulated objects.
|
|
23
|
+
*
|
|
24
|
+
* This new manager object exposes the {@link Tunnel} object so that consumers
|
|
25
|
+
* can subscribe to the "api" event.
|
|
26
|
+
* @alpha
|
|
27
|
+
*/
|
|
28
|
+
export interface CrossRealmObject<ExpectedApi> {
|
|
29
|
+
/**
|
|
30
|
+
* The event emitter that transmits RPC events between remotes. Can be used to
|
|
31
|
+
* listen to "api" events, which re-emit the initial remote API after an
|
|
32
|
+
* unexpected reload. Can also be used to manually destroy the xrobject.
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
tunnel: Tunnel;
|
|
36
|
+
/**
|
|
37
|
+
* Accessor for the simulated object. Putting the object behind an accessor is
|
|
38
|
+
* a way (we hope) to subtly discourage hanging on to a reference to the
|
|
39
|
+
* object, which will invalidate without the holder of the reference knowing.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
getRemoteApi(): Asynced<ExpectedApi>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function setupApiExchange<T>(
|
|
46
|
+
tunnel: Tunnel,
|
|
47
|
+
apiToSend: unknown
|
|
48
|
+
): Promise<CrossRealmObject<T>> {
|
|
49
|
+
let done = false;
|
|
50
|
+
let remoteApi!: Asynced<T>;
|
|
51
|
+
const xrObject: CrossRealmObject<T> = {
|
|
52
|
+
tunnel,
|
|
53
|
+
getRemoteApi(): Asynced<T> {
|
|
54
|
+
return remoteApi;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
return timeoutPromise(
|
|
58
|
+
"Initial API exchange",
|
|
59
|
+
new Promise((resolve, reject) => {
|
|
60
|
+
const simulator = ObjectSimulator.create(tunnel, FinalizationRegistry);
|
|
61
|
+
|
|
62
|
+
const sendApi = simulator.makeSender(INIT_MESSAGE);
|
|
63
|
+
const apiCallback = (api: Asynced<T>) => {
|
|
64
|
+
remoteApi = api;
|
|
65
|
+
if (!done) {
|
|
66
|
+
done = true;
|
|
67
|
+
resolve(xrObject);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
tunnel.on("api", apiCallback);
|
|
71
|
+
|
|
72
|
+
const unsubscribe = receiveCalls(
|
|
73
|
+
(api: Asynced<T>) => tunnel.emitLocal("api", api),
|
|
74
|
+
INIT_TICKET,
|
|
75
|
+
new WeakRef(simulator.subject)
|
|
76
|
+
);
|
|
77
|
+
const destroy = (e: Error) => {
|
|
78
|
+
unsubscribe();
|
|
79
|
+
if (!done) {
|
|
80
|
+
done = true;
|
|
81
|
+
reject(e);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
tunnel.on("destroyed", destroy);
|
|
85
|
+
tunnel.on("connected", () =>
|
|
86
|
+
(sendApi as Function)(apiToSend).catch(destroy)
|
|
87
|
+
);
|
|
88
|
+
}),
|
|
89
|
+
tunnel.config.timeout,
|
|
90
|
+
() => tunnel.destroy()
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Create a CrossRealmObject in an iframe, simulating objects from the parent window.
|
|
96
|
+
* @alpha
|
|
97
|
+
*/
|
|
98
|
+
export async function connectParentWindow<Expected>(
|
|
99
|
+
tunnelOptions: Partial<TunnelConfig>,
|
|
100
|
+
apiToSend: unknown
|
|
101
|
+
): Promise<CrossRealmObject<Expected>> {
|
|
102
|
+
const tunnel = Tunnel.toParent(window.parent, tunnelOptions);
|
|
103
|
+
return setupApiExchange<Expected>(tunnel, apiToSend);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a CrossRealmObject simulating objects from the provided iframe runtime.
|
|
108
|
+
* @alpha
|
|
109
|
+
*/
|
|
110
|
+
export async function connectIframe<Expected>(
|
|
111
|
+
frame: HTMLIFrameElement,
|
|
112
|
+
tunnelOptions: Partial<TunnelConfig>,
|
|
113
|
+
apiToSend: unknown
|
|
114
|
+
): Promise<CrossRealmObject<Expected>> {
|
|
115
|
+
const tunnel = Tunnel.toIframe(frame, tunnelOptions);
|
|
116
|
+
return setupApiExchange<Expected>(tunnel, apiToSend);
|
|
117
|
+
}
|
package/src/debuglog.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -19,5 +19,8 @@ export * from "./debug-emitter";
|
|
|
19
19
|
export * from "./debuglog";
|
|
20
20
|
export * from "./emitter";
|
|
21
21
|
export * from "./namespace-proxy";
|
|
22
|
-
export * from "./timeout-promise";
|
|
23
22
|
export * from "./types";
|
|
23
|
+
export * from "./cross-realm-object";
|
|
24
|
+
export * from "./promises";
|
|
25
|
+
export * from "./tunnel";
|
|
26
|
+
export type { Asynced } from "./object-walker";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NS_ROOT } from "./constants";
|
|
2
|
+
import { isPlainObject } from "./value-assertions";
|
|
3
|
+
|
|
4
|
+
/** @internal */
|
|
5
|
+
export type WrappedMessage<Message extends object> = { [NS_ROOT]: Message };
|
|
6
|
+
|
|
7
|
+
export function wrap<Message extends object = object>(
|
|
8
|
+
message: Message
|
|
9
|
+
): WrappedMessage<Message> {
|
|
10
|
+
return { [NS_ROOT]: message };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function unwrap<Message extends object>(
|
|
14
|
+
wrappedMessage: WrappedMessage<Message>
|
|
15
|
+
): Message {
|
|
16
|
+
return wrappedMessage[NS_ROOT];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isWrapped<Message extends object = object>(
|
|
20
|
+
item: unknown
|
|
21
|
+
): item is WrappedMessage<Message> {
|
|
22
|
+
if (!isPlainObject(item)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const keys = Object.keys(item);
|
|
26
|
+
const hasRoot = keys.includes(NS_ROOT);
|
|
27
|
+
if (hasRoot && keys.length != 1) {
|
|
28
|
+
console.error(
|
|
29
|
+
`malformed tunnel message, should have one prop "${NS_ROOT}" at root`,
|
|
30
|
+
item
|
|
31
|
+
);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return hasRoot;
|
|
35
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { NS_ROOT } from "./constants";
|
|
2
|
+
import { EventEmitter } from "eventemitter3";
|
|
3
|
+
import { ObjectSimulator } from "./object-simulator";
|
|
4
|
+
import { FakeFinalizationRegistry } from "./__mocks__/mock-finalization-registry";
|
|
5
|
+
import { wait } from "./promises/wait";
|
|
6
|
+
import { DefMessage } from "./object-walker";
|
|
7
|
+
|
|
8
|
+
describe("function simulator exchanges functions and tickets", () => {
|
|
9
|
+
let objectSimulator: ObjectSimulator;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.spyOn(console, "error").mockImplementation(() => {});
|
|
12
|
+
const emitter = new EventEmitter();
|
|
13
|
+
objectSimulator = ObjectSimulator.create(emitter, FakeFinalizationRegistry);
|
|
14
|
+
});
|
|
15
|
+
it("turns an object with functions into an object with tickets", async () => {
|
|
16
|
+
const invokeIt = (blorp: CallableFunction) => blorp();
|
|
17
|
+
const gnorf = {
|
|
18
|
+
slorf: {
|
|
19
|
+
blorf: (x: number) => x + 1,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const toBeTicketed = {
|
|
23
|
+
list: [
|
|
24
|
+
{
|
|
25
|
+
what: 8,
|
|
26
|
+
doa: invokeIt,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
gnorf,
|
|
30
|
+
harbl: 3,
|
|
31
|
+
};
|
|
32
|
+
const ticketed = objectSimulator.simulate(toBeTicketed);
|
|
33
|
+
expect(ticketed).toMatchInlineSnapshot(`
|
|
34
|
+
{
|
|
35
|
+
"gnorf": {
|
|
36
|
+
"slorf": {
|
|
37
|
+
"blorf": {
|
|
38
|
+
"_\$pg": {
|
|
39
|
+
"fnId": "blorf_2",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
"harbl": 3,
|
|
45
|
+
"list": [
|
|
46
|
+
{
|
|
47
|
+
"doa": {
|
|
48
|
+
"_\$pg": {
|
|
49
|
+
"fnId": "invokeIt_1",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
"what": 8,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
}
|
|
56
|
+
`);
|
|
57
|
+
const unticketed = objectSimulator.materialize(ticketed);
|
|
58
|
+
expect(unticketed).toMatchInlineSnapshot(`
|
|
59
|
+
{
|
|
60
|
+
"gnorf": {
|
|
61
|
+
"slorf": {
|
|
62
|
+
"blorf": [Function],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"harbl": 3,
|
|
66
|
+
"list": [
|
|
67
|
+
{
|
|
68
|
+
"doa": [Function],
|
|
69
|
+
"what": 8,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
`);
|
|
74
|
+
const remoteInvokeIt = unticketed.list[0].doa;
|
|
75
|
+
await expect(remoteInvokeIt(() => "oh noes")).resolves.toBe("oh noes");
|
|
76
|
+
await expect(unticketed.gnorf.slorf.blorf(9)).resolves.toBe(10);
|
|
77
|
+
});
|
|
78
|
+
it("dies when an object has an unrecognizable value", () => {
|
|
79
|
+
expect(() =>
|
|
80
|
+
objectSimulator.simulate({
|
|
81
|
+
lol: Symbol("lol"),
|
|
82
|
+
})
|
|
83
|
+
).toThrowError("Bad value");
|
|
84
|
+
});
|
|
85
|
+
it("passes through tickets when unexpected", () => {
|
|
86
|
+
const hasTicket = {
|
|
87
|
+
[NS_ROOT]: {
|
|
88
|
+
some: "ticket",
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
const doTicket = () => objectSimulator.simulate({ hasTicket });
|
|
92
|
+
expect(doTicket).not.toThrowError();
|
|
93
|
+
expect(doTicket()).toMatchObject({ hasTicket });
|
|
94
|
+
});
|
|
95
|
+
it("strips unserializable props, but throws on unserializable values", () => {
|
|
96
|
+
expect(() =>
|
|
97
|
+
objectSimulator.simulate({
|
|
98
|
+
lol: Symbol("lol"),
|
|
99
|
+
})
|
|
100
|
+
).toThrowError("Bad value");
|
|
101
|
+
expect(() =>
|
|
102
|
+
objectSimulator.simulate({
|
|
103
|
+
[Symbol("lol")]: "lol",
|
|
104
|
+
})
|
|
105
|
+
).not.toThrowError();
|
|
106
|
+
});
|
|
107
|
+
it("can handle root functions", async () => {
|
|
108
|
+
let called = false;
|
|
109
|
+
const ticketedLoneFn = objectSimulator.simulate(() => {
|
|
110
|
+
called = true;
|
|
111
|
+
});
|
|
112
|
+
expect(ticketedLoneFn).toMatchInlineSnapshot(`
|
|
113
|
+
{
|
|
114
|
+
"_$pg": {
|
|
115
|
+
"fnId": "<anonymous>_1",
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
`);
|
|
119
|
+
const loneFn = objectSimulator.materialize(ticketedLoneFn);
|
|
120
|
+
await expect(loneFn()).resolves.not.toThrowError();
|
|
121
|
+
expect(called).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
it("notifies remote when FinalizationRegistry calls cleanup handler", async () => {
|
|
124
|
+
const willBeGCed = objectSimulator.simulate(() => {}) as DefMessage;
|
|
125
|
+
objectSimulator.materialize(willBeGCed);
|
|
126
|
+
const { subject } = objectSimulator;
|
|
127
|
+
const fakeTicket = willBeGCed[NS_ROOT];
|
|
128
|
+
const gcHandler = jest.fn();
|
|
129
|
+
subject.onOutOfScope(fakeTicket, gcHandler);
|
|
130
|
+
const lastCleanupHandler = FakeFinalizationRegistry.mock.cleanupHandler;
|
|
131
|
+
lastCleanupHandler(fakeTicket.fnId);
|
|
132
|
+
await wait(100);
|
|
133
|
+
expect(gcHandler).toHaveBeenCalled();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { isWrapped, unwrap, wrap } from "./message-wrapper";
|
|
2
|
+
import EventEmitter from "eventemitter3";
|
|
3
|
+
import type { DefMessage, Materialized, Simulated } from "./object-walker";
|
|
4
|
+
import { NOT_TRANSFORMED, transformRecursive } from "./object-walker";
|
|
5
|
+
import { makeCallSender, receiveCalls } from "./rpc";
|
|
6
|
+
import type { Simulator } from "./remote-subject";
|
|
7
|
+
import { RemoteSubject } from "./remote-subject";
|
|
8
|
+
import type { DefTicket } from "./tickets";
|
|
9
|
+
import { hasProp } from "./value-assertions";
|
|
10
|
+
|
|
11
|
+
function isDefMessage(value: unknown): value is DefMessage {
|
|
12
|
+
return isWrapped(value) && hasProp(unwrap(value), "fnId");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const bindAll = <T>(inst: T, methods: (keyof T)[]) => {
|
|
16
|
+
for (const methodName of methods) {
|
|
17
|
+
const method = inst[methodName];
|
|
18
|
+
if (typeof method === "function") {
|
|
19
|
+
inst[methodName] = method.bind(inst);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
interface CleanupNotifier {
|
|
25
|
+
// #region Public Methods
|
|
26
|
+
|
|
27
|
+
register(obj: any, heldValue: string, ref?: any): void;
|
|
28
|
+
unregister(ref: any): void;
|
|
29
|
+
|
|
30
|
+
// #endregion Public Methods
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface CleanupNotifierConstructor {
|
|
34
|
+
new (callback: (heldValue: unknown) => void): CleanupNotifier;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ObjectSimulator implements Simulator {
|
|
38
|
+
// #region Properties
|
|
39
|
+
|
|
40
|
+
private cleanupNotifier: CleanupNotifier;
|
|
41
|
+
private fnCounter = 0;
|
|
42
|
+
private receiverTicketCache: WeakMap<CallableFunction, DefTicket> =
|
|
43
|
+
new WeakMap();
|
|
44
|
+
private senderCache: WeakMap<DefTicket, CallableFunction> = new WeakMap();
|
|
45
|
+
|
|
46
|
+
subject: RemoteSubject;
|
|
47
|
+
|
|
48
|
+
// #endregion Properties
|
|
49
|
+
|
|
50
|
+
// #region Constructors
|
|
51
|
+
|
|
52
|
+
constructor(subject: RemoteSubject, cleanupNotifier: CleanupNotifier) {
|
|
53
|
+
this.cleanupNotifier = cleanupNotifier;
|
|
54
|
+
this.subject = subject;
|
|
55
|
+
|
|
56
|
+
bindAll(this, ["makeSender", "makeReceiver", "simulate", "materialize"]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// #endregion Constructors
|
|
60
|
+
|
|
61
|
+
// #region Public Static Methods
|
|
62
|
+
|
|
63
|
+
static create(
|
|
64
|
+
emitter: EventEmitter,
|
|
65
|
+
Cleanup: CleanupNotifierConstructor
|
|
66
|
+
): ObjectSimulator {
|
|
67
|
+
let simulator: Simulator;
|
|
68
|
+
// proxy simulator, so as not to have cyclic dependency
|
|
69
|
+
const simulatorInterface: Simulator = {
|
|
70
|
+
simulate: (x) => simulator.simulate(x),
|
|
71
|
+
materialize: (x) => simulator.materialize(x),
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const subject = new RemoteSubject(emitter, simulatorInterface);
|
|
75
|
+
|
|
76
|
+
const cleanupNotifier = new Cleanup((fnId: string) => {
|
|
77
|
+
return subject.notifyCleanup({ fnId });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
simulator = new ObjectSimulator(subject, cleanupNotifier);
|
|
81
|
+
|
|
82
|
+
return simulator as ObjectSimulator;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// #endregion Public Static Methods
|
|
86
|
+
|
|
87
|
+
// #region Public Methods
|
|
88
|
+
|
|
89
|
+
makeReceiver(fn: CallableFunction) {
|
|
90
|
+
if (typeof fn !== "function") {
|
|
91
|
+
return NOT_TRANSFORMED;
|
|
92
|
+
}
|
|
93
|
+
let fnTicket = this.receiverTicketCache.get(fn);
|
|
94
|
+
if (!fnTicket) {
|
|
95
|
+
fnTicket = {
|
|
96
|
+
fnId: `${fn.name || "<anonymous>"}_${++this.fnCounter}`,
|
|
97
|
+
};
|
|
98
|
+
const cleanup = receiveCalls(fn, fnTicket, new WeakRef(this.subject));
|
|
99
|
+
this.subject.onOutOfScope(fnTicket, cleanup);
|
|
100
|
+
this.receiverTicketCache.set(fn, fnTicket);
|
|
101
|
+
}
|
|
102
|
+
return wrap(fnTicket);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
makeSender(message: unknown) {
|
|
106
|
+
if (!isDefMessage(message)) {
|
|
107
|
+
return NOT_TRANSFORMED;
|
|
108
|
+
}
|
|
109
|
+
const ticket = unwrap(message);
|
|
110
|
+
/* istanbul ignore else: preopt */
|
|
111
|
+
if (!this.senderCache.has(ticket)) {
|
|
112
|
+
const sender = makeCallSender(ticket, new WeakRef(this.subject));
|
|
113
|
+
this.cleanupNotifier.register(sender, ticket.fnId, sender);
|
|
114
|
+
this.senderCache.set(ticket, sender);
|
|
115
|
+
return sender;
|
|
116
|
+
} else {
|
|
117
|
+
return this.senderCache.get(ticket) as CallableFunction;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
materialize<T>(simulated: T) {
|
|
122
|
+
return transformRecursive<CallableFunction>(
|
|
123
|
+
this.makeSender,
|
|
124
|
+
simulated
|
|
125
|
+
) as Materialized<T>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
simulate<T>(localObject: T) {
|
|
129
|
+
return transformRecursive<DefMessage>(
|
|
130
|
+
this.makeReceiver,
|
|
131
|
+
localObject
|
|
132
|
+
) as Simulated<T>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// #endregion Public Methods
|
|
136
|
+
}
|