@react-native-harness/bridge 1.3.0 → 1.4.0-rc.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.
- package/dist/__tests__/client.test.d.ts +2 -0
- package/dist/__tests__/client.test.d.ts.map +1 -0
- package/dist/__tests__/client.test.js +81 -0
- package/dist/__tests__/heartbeat.test.d.ts +2 -0
- package/dist/__tests__/heartbeat.test.d.ts.map +1 -0
- package/dist/__tests__/heartbeat.test.js +37 -0
- package/dist/__tests__/protocol.test.d.ts +2 -0
- package/dist/__tests__/protocol.test.d.ts.map +1 -0
- package/dist/__tests__/protocol.test.js +37 -0
- package/dist/__tests__/rpc-peer.test.d.ts +2 -0
- package/dist/__tests__/rpc-peer.test.d.ts.map +1 -0
- package/dist/__tests__/rpc-peer.test.js +127 -0
- package/dist/client.d.ts +7 -14
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +96 -42
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +23 -0
- package/dist/heartbeat.d.ts +13 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +49 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/protocol.d.ts +51 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +132 -0
- package/dist/rpc-peer.d.ts +28 -0
- package/dist/rpc-peer.d.ts.map +1 -0
- package/dist/rpc-peer.js +146 -0
- package/dist/server.d.ts +1 -20
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +122 -71
- package/dist/shared/test-collector.d.ts +2 -0
- package/dist/shared/test-collector.d.ts.map +1 -1
- package/dist/shared/test-runner.d.ts +13 -0
- package/dist/shared/test-runner.d.ts.map +1 -1
- package/dist/shared.d.ts +0 -2
- package/dist/shared.d.ts.map +1 -1
- package/dist/transport.d.ts +22 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +22 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/websocket-client-transport.d.ts +3 -0
- package/dist/websocket-client-transport.d.ts.map +1 -0
- package/dist/websocket-client-transport.js +45 -0
- package/dist/websocket-server-transport.d.ts +4 -0
- package/dist/websocket-server-transport.d.ts.map +1 -0
- package/dist/websocket-server-transport.js +43 -0
- package/eslint.config.mjs +5 -1
- package/package.json +6 -5
- package/src/__tests__/client.test.ts +99 -0
- package/src/__tests__/heartbeat.test.ts +47 -0
- package/src/__tests__/protocol.test.ts +51 -0
- package/src/__tests__/rpc-peer.test.ts +181 -0
- package/src/client.ts +146 -57
- package/src/errors.ts +32 -0
- package/src/heartbeat.ts +67 -0
- package/src/index.ts +1 -0
- package/src/protocol.ts +233 -0
- package/src/rpc-peer.ts +222 -0
- package/src/server.ts +179 -114
- package/src/shared/test-collector.ts +3 -0
- package/src/shared/test-runner.ts +14 -0
- package/src/shared.ts +0 -2
- package/src/transport.ts +47 -0
- package/src/websocket-client-transport.ts +85 -0
- package/src/websocket-server-transport.ts +54 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { BridgeEvents, DeviceDescriptor } from './shared.js';
|
|
2
|
+
export type SerializedBridgeError = {
|
|
3
|
+
name: string;
|
|
4
|
+
message: string;
|
|
5
|
+
stack?: string;
|
|
6
|
+
cause?: unknown;
|
|
7
|
+
};
|
|
8
|
+
export type BridgeInvokeMessage = {
|
|
9
|
+
type: 'invoke';
|
|
10
|
+
id: number;
|
|
11
|
+
method: string;
|
|
12
|
+
args: unknown[];
|
|
13
|
+
};
|
|
14
|
+
export type BridgeReturnMessage = {
|
|
15
|
+
type: 'return';
|
|
16
|
+
id: number;
|
|
17
|
+
ok: true;
|
|
18
|
+
value?: unknown;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'return';
|
|
21
|
+
id: number;
|
|
22
|
+
ok: false;
|
|
23
|
+
error: SerializedBridgeError;
|
|
24
|
+
};
|
|
25
|
+
export type BridgeEventMessage<Event extends {
|
|
26
|
+
type: string;
|
|
27
|
+
} = BridgeEvents> = {
|
|
28
|
+
type: 'event';
|
|
29
|
+
event: Event;
|
|
30
|
+
};
|
|
31
|
+
export type BridgeReadyMessage = {
|
|
32
|
+
type: 'ready';
|
|
33
|
+
device: DeviceDescriptor;
|
|
34
|
+
};
|
|
35
|
+
export type BridgePingMessage = {
|
|
36
|
+
type: 'ping';
|
|
37
|
+
id: number;
|
|
38
|
+
};
|
|
39
|
+
export type BridgePongMessage = {
|
|
40
|
+
type: 'pong';
|
|
41
|
+
id: number;
|
|
42
|
+
};
|
|
43
|
+
export type BridgeControlMessage = BridgeReadyMessage | BridgePingMessage | BridgePongMessage;
|
|
44
|
+
export type BridgeMessage<Event extends {
|
|
45
|
+
type: string;
|
|
46
|
+
} = BridgeEvents> = BridgeInvokeMessage | BridgeReturnMessage | BridgeEventMessage<Event> | BridgeControlMessage;
|
|
47
|
+
export declare const serializeBridgeMessage: (message: BridgeMessage) => string;
|
|
48
|
+
export declare const parseBridgeMessage: (raw: string) => BridgeMessage;
|
|
49
|
+
export declare const serializeBridgeError: (error: unknown) => SerializedBridgeError;
|
|
50
|
+
export declare const deserializeBridgeError: (serialized: SerializedBridgeError) => Error;
|
|
51
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElE,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,qBAAqB,CAAC;CAC9B,CAAC;AAEN,MAAM,MAAM,kBAAkB,CAAC,KAAK,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,IAAI;IAC9E,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAC5B,kBAAkB,GAClB,iBAAiB,GACjB,iBAAiB,CAAC;AAEtB,MAAM,MAAM,aAAa,CAAC,KAAK,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,IACnE,mBAAmB,GACnB,mBAAmB,GACnB,kBAAkB,CAAC,KAAK,CAAC,GACzB,oBAAoB,CAAC;AAmEzB,eAAO,MAAM,sBAAsB,GACjC,SAAS,aAAa,KACrB,MAEF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,KAAG,aAiDhD,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,OAAO,OAAO,KACb,qBA+BF,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,YAAY,qBAAqB,KAChC,KAYF,CAAC"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const isRecord = (value) => {
|
|
2
|
+
return typeof value === 'object' && value !== null;
|
|
3
|
+
};
|
|
4
|
+
const readNumber = (value, fieldName) => {
|
|
5
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
6
|
+
throw new Error(`Invalid bridge message: ${fieldName} must be a number`);
|
|
7
|
+
}
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
const readString = (value, fieldName) => {
|
|
11
|
+
if (typeof value !== 'string') {
|
|
12
|
+
throw new Error(`Invalid bridge message: ${fieldName} must be a string`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
};
|
|
16
|
+
const readSerializedBridgeError = (value) => {
|
|
17
|
+
if (!isRecord(value)) {
|
|
18
|
+
throw new Error('Invalid bridge message: error must be an object');
|
|
19
|
+
}
|
|
20
|
+
const name = readString(value.name, 'error.name');
|
|
21
|
+
const message = readString(value.message, 'error.message');
|
|
22
|
+
if (value.stack !== undefined) {
|
|
23
|
+
readString(value.stack, 'error.stack');
|
|
24
|
+
}
|
|
25
|
+
const stack = value.stack;
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
message,
|
|
29
|
+
stack,
|
|
30
|
+
cause: value.cause,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const readDeviceDescriptor = (value) => {
|
|
34
|
+
if (!isRecord(value)) {
|
|
35
|
+
throw new Error('Invalid bridge message: device must be an object');
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
platform: readString(value.platform, 'device.platform'),
|
|
39
|
+
manufacturer: readString(value.manufacturer, 'device.manufacturer'),
|
|
40
|
+
model: readString(value.model, 'device.model'),
|
|
41
|
+
osVersion: readString(value.osVersion, 'device.osVersion'),
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const readBridgeEvent = (value) => {
|
|
45
|
+
if (!isRecord(value)) {
|
|
46
|
+
throw new Error('Invalid bridge message: event must be an object');
|
|
47
|
+
}
|
|
48
|
+
readString(value.type, 'event.type');
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
export const serializeBridgeMessage = (message) => {
|
|
52
|
+
return JSON.stringify(message);
|
|
53
|
+
};
|
|
54
|
+
export const parseBridgeMessage = (raw) => {
|
|
55
|
+
const parsed = JSON.parse(raw);
|
|
56
|
+
if (!isRecord(parsed)) {
|
|
57
|
+
throw new Error('Invalid bridge message: expected an object');
|
|
58
|
+
}
|
|
59
|
+
const messageType = readString(parsed.type, 'type');
|
|
60
|
+
switch (messageType) {
|
|
61
|
+
case 'invoke': {
|
|
62
|
+
readNumber(parsed.id, 'id');
|
|
63
|
+
readString(parsed.method, 'method');
|
|
64
|
+
if (!Array.isArray(parsed.args)) {
|
|
65
|
+
throw new Error('Invalid bridge message: args must be an array');
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
case 'return': {
|
|
70
|
+
readNumber(parsed.id, 'id');
|
|
71
|
+
if (typeof parsed.ok !== 'boolean') {
|
|
72
|
+
throw new Error('Invalid bridge message: ok must be a boolean');
|
|
73
|
+
}
|
|
74
|
+
if (!parsed.ok) {
|
|
75
|
+
readSerializedBridgeError(parsed.error);
|
|
76
|
+
}
|
|
77
|
+
return parsed;
|
|
78
|
+
}
|
|
79
|
+
case 'event': {
|
|
80
|
+
readBridgeEvent(parsed.event);
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
case 'ready': {
|
|
84
|
+
readDeviceDescriptor(parsed.device);
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
case 'ping':
|
|
88
|
+
case 'pong': {
|
|
89
|
+
readNumber(parsed.id, 'id');
|
|
90
|
+
return parsed;
|
|
91
|
+
}
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`Invalid bridge message: unknown type ${messageType}`);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
export const serializeBridgeError = (error) => {
|
|
97
|
+
if (error instanceof Error) {
|
|
98
|
+
return {
|
|
99
|
+
name: error.name,
|
|
100
|
+
message: error.message,
|
|
101
|
+
stack: error.stack,
|
|
102
|
+
cause: error.cause,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (isRecord(error)) {
|
|
106
|
+
const message = typeof error.message === 'string'
|
|
107
|
+
? error.message
|
|
108
|
+
: `Non-Error thrown value: ${String(error)}`;
|
|
109
|
+
return {
|
|
110
|
+
name: typeof error.name === 'string' ? error.name : 'NonErrorThrown',
|
|
111
|
+
message,
|
|
112
|
+
stack: typeof error.stack === 'string' ? error.stack : undefined,
|
|
113
|
+
cause: 'cause' in error ? error.cause : undefined,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
name: 'NonErrorThrown',
|
|
118
|
+
message: typeof error === 'string'
|
|
119
|
+
? error
|
|
120
|
+
: `Non-Error thrown value: ${String(error)}`,
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
export const deserializeBridgeError = (serialized) => {
|
|
124
|
+
const error = new Error(serialized.message, {
|
|
125
|
+
cause: serialized.cause,
|
|
126
|
+
});
|
|
127
|
+
error.name = serialized.name;
|
|
128
|
+
if (serialized.stack) {
|
|
129
|
+
error.stack = serialized.stack;
|
|
130
|
+
}
|
|
131
|
+
return error;
|
|
132
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type BridgeControlMessage } from './protocol.js';
|
|
2
|
+
import type { RpcTransport } from './transport.js';
|
|
3
|
+
type RpcMethod = {
|
|
4
|
+
bivarianceHack(...args: unknown[]): unknown;
|
|
5
|
+
}['bivarianceHack'];
|
|
6
|
+
type RpcMethods = Record<string, RpcMethod>;
|
|
7
|
+
export type RpcPeer<Remote extends RpcMethods, Event extends {
|
|
8
|
+
type: string;
|
|
9
|
+
}> = {
|
|
10
|
+
invoke: <K extends keyof Remote>(method: K, ...args: Parameters<Remote[K]>) => Promise<Awaited<ReturnType<Remote[K]>>>;
|
|
11
|
+
sendEvent: (event: Event) => void;
|
|
12
|
+
handleMessage: (raw: string) => Promise<BridgeControlMessage | null>;
|
|
13
|
+
close: (reason?: Error) => void;
|
|
14
|
+
};
|
|
15
|
+
export type CreateRpcPeerOptions<Local extends RpcMethods, Event extends {
|
|
16
|
+
type: string;
|
|
17
|
+
}> = {
|
|
18
|
+
localMethods: Local;
|
|
19
|
+
transport: RpcTransport;
|
|
20
|
+
onEvent?: (event: Event) => void;
|
|
21
|
+
callTimeoutMs?: number;
|
|
22
|
+
createTimeoutError?: (method: string, args: unknown[]) => Error;
|
|
23
|
+
};
|
|
24
|
+
export declare const createRpcPeer: <Local extends RpcMethods, Remote extends RpcMethods, Event extends {
|
|
25
|
+
type: string;
|
|
26
|
+
}>(options: CreateRpcPeerOptions<Local, Event>) => RpcPeer<Remote, Event>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=rpc-peer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-peer.d.ts","sourceRoot":"","sources":["../src/rpc-peer.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,KAAK,SAAS,GAAG;IACf,cAAc,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;CAC7C,CAAC,gBAAgB,CAAC,CAAC;AACpB,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAU5C,MAAM,MAAM,OAAO,CACjB,MAAM,SAAS,UAAU,EACzB,KAAK,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,IAC5B;IACF,MAAM,EAAE,CAAC,CAAC,SAAS,MAAM,MAAM,EAC7B,MAAM,EAAE,CAAC,EACT,GAAG,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAC3B,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,SAAS,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAClC,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACrE,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,oBAAoB,CAC9B,KAAK,SAAS,UAAU,EACxB,KAAK,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,IAC5B;IACF,YAAY,EAAE,KAAK,CAAC;IACpB,SAAS,EAAE,YAAY,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,KAAK,CAAC;CACjE,CAAC;AAMF,eAAO,MAAM,aAAa,GACxB,KAAK,SAAS,UAAU,EACxB,MAAM,SAAS,UAAU,EACzB,KAAK,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAE9B,SAAS,oBAAoB,CAAC,KAAK,EAAE,KAAK,CAAC,KAC1C,OAAO,CAAC,MAAM,EAAE,KAAK,CAqKvB,CAAC"}
|
package/dist/rpc-peer.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { deserializeBridgeError, parseBridgeMessage, serializeBridgeError, serializeBridgeMessage, } from './protocol.js';
|
|
2
|
+
const createClosedPeerError = () => {
|
|
3
|
+
return new Error('Bridge RPC peer closed');
|
|
4
|
+
};
|
|
5
|
+
export const createRpcPeer = (options) => {
|
|
6
|
+
const pendingInvocations = new Map();
|
|
7
|
+
let nextMessageId = 1;
|
|
8
|
+
let closedReason = null;
|
|
9
|
+
const rejectPendingInvocations = (reason) => {
|
|
10
|
+
for (const [id, invocation] of pendingInvocations) {
|
|
11
|
+
if (invocation.timeout) {
|
|
12
|
+
clearTimeout(invocation.timeout);
|
|
13
|
+
}
|
|
14
|
+
pendingInvocations.delete(id);
|
|
15
|
+
invocation.reject(reason);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const close = (reason = createClosedPeerError()) => {
|
|
19
|
+
if (closedReason) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
closedReason = reason;
|
|
23
|
+
rejectPendingInvocations(reason);
|
|
24
|
+
};
|
|
25
|
+
const sendMessage = (message) => {
|
|
26
|
+
if (closedReason) {
|
|
27
|
+
throw closedReason;
|
|
28
|
+
}
|
|
29
|
+
options.transport.send(serializeBridgeMessage(message));
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
invoke: (method, ...args) => {
|
|
33
|
+
if (closedReason) {
|
|
34
|
+
return Promise.reject(closedReason);
|
|
35
|
+
}
|
|
36
|
+
const id = nextMessageId++;
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const methodName = String(method);
|
|
39
|
+
const invocation = {
|
|
40
|
+
args,
|
|
41
|
+
method: methodName,
|
|
42
|
+
reject,
|
|
43
|
+
resolve: (value) => {
|
|
44
|
+
resolve(value);
|
|
45
|
+
},
|
|
46
|
+
timeout: null,
|
|
47
|
+
};
|
|
48
|
+
if (options.callTimeoutMs !== undefined) {
|
|
49
|
+
invocation.timeout = setTimeout(() => {
|
|
50
|
+
pendingInvocations.delete(id);
|
|
51
|
+
reject(options.createTimeoutError?.(methodName, args) ??
|
|
52
|
+
new Error(`RPC call timed out: ${methodName}`));
|
|
53
|
+
}, options.callTimeoutMs);
|
|
54
|
+
}
|
|
55
|
+
pendingInvocations.set(id, invocation);
|
|
56
|
+
try {
|
|
57
|
+
sendMessage({
|
|
58
|
+
type: 'invoke',
|
|
59
|
+
id,
|
|
60
|
+
method: methodName,
|
|
61
|
+
args,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
pendingInvocations.delete(id);
|
|
66
|
+
if (invocation.timeout) {
|
|
67
|
+
clearTimeout(invocation.timeout);
|
|
68
|
+
}
|
|
69
|
+
reject(error);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
sendEvent: (event) => {
|
|
74
|
+
if (closedReason) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
sendMessage({ type: 'event', event });
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
close(error instanceof Error ? error : createClosedPeerError());
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
handleMessage: async (raw) => {
|
|
85
|
+
const message = parseBridgeMessage(raw);
|
|
86
|
+
switch (message.type) {
|
|
87
|
+
case 'invoke': {
|
|
88
|
+
const localMethod = options.localMethods[message.method];
|
|
89
|
+
if (!localMethod) {
|
|
90
|
+
sendMessage({
|
|
91
|
+
type: 'return',
|
|
92
|
+
id: message.id,
|
|
93
|
+
ok: false,
|
|
94
|
+
error: serializeBridgeError(new Error(`Unknown bridge RPC method: ${message.method}`)),
|
|
95
|
+
});
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const value = await localMethod(...message.args);
|
|
100
|
+
sendMessage({
|
|
101
|
+
type: 'return',
|
|
102
|
+
id: message.id,
|
|
103
|
+
ok: true,
|
|
104
|
+
value,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
sendMessage({
|
|
109
|
+
type: 'return',
|
|
110
|
+
id: message.id,
|
|
111
|
+
ok: false,
|
|
112
|
+
error: serializeBridgeError(error),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
case 'return': {
|
|
118
|
+
const invocation = pendingInvocations.get(message.id);
|
|
119
|
+
if (!invocation) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
pendingInvocations.delete(message.id);
|
|
123
|
+
if (invocation.timeout) {
|
|
124
|
+
clearTimeout(invocation.timeout);
|
|
125
|
+
}
|
|
126
|
+
if (message.ok) {
|
|
127
|
+
invocation.resolve(message.value);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
invocation.reject(deserializeBridgeError(message.error));
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
case 'event': {
|
|
135
|
+
options.onEvent?.(message.event);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
case 'ready':
|
|
139
|
+
case 'ping':
|
|
140
|
+
case 'pong':
|
|
141
|
+
return message;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
close,
|
|
145
|
+
};
|
|
146
|
+
};
|
package/dist/server.d.ts
CHANGED
|
@@ -2,21 +2,14 @@ import { WebSocketServer } from 'ws';
|
|
|
2
2
|
import type { Server as HttpServer } from 'node:http';
|
|
3
3
|
import type { Server as HttpsServer } from 'node:https';
|
|
4
4
|
import type { DeviceDescriptor, BridgeEvents, HarnessContext, TestExecutionOptions, TestSuiteResult } from './shared.js';
|
|
5
|
-
export { DeviceNotRespondingError } from './errors.js';
|
|
6
|
-
/**
|
|
7
|
-
* Represents a single app session — one app launch to the next restart.
|
|
8
|
-
* Obtained via HarnessBridge.nextConnection().
|
|
9
|
-
*/
|
|
5
|
+
export { AppBridgeDisconnectedError, DeviceNotRespondingError, } from './errors.js';
|
|
10
6
|
export type AppConnection = {
|
|
11
7
|
readonly device: DeviceDescriptor;
|
|
12
8
|
runTests: (path: string, options: TestExecutionOptions) => Promise<TestSuiteResult>;
|
|
13
9
|
};
|
|
14
10
|
export type HarnessBridgeEvents = {
|
|
15
|
-
/** Fired when the app connects and calls reportReady. */
|
|
16
11
|
connected: (connection: AppConnection) => void;
|
|
17
|
-
/** Fired when the app's WebSocket closes. */
|
|
18
12
|
disconnected: () => void;
|
|
19
|
-
/** Fired for every test/bundler event the app emits. */
|
|
20
13
|
event: (event: BridgeEvents) => void;
|
|
21
14
|
};
|
|
22
15
|
type TransportOptions = {
|
|
@@ -32,21 +25,9 @@ export type HarnessBridgeOptions = TransportOptions & {
|
|
|
32
25
|
timeout?: number;
|
|
33
26
|
context: HarnessContext;
|
|
34
27
|
};
|
|
35
|
-
/**
|
|
36
|
-
* The persistent CLI-side bridge. Spans the full test run regardless of how
|
|
37
|
-
* many times the app is restarted. Each restart produces a new AppConnection
|
|
38
|
-
* via nextConnection().
|
|
39
|
-
*/
|
|
40
28
|
export type HarnessBridge = {
|
|
41
|
-
/** The underlying WebSocket server, used to attach to Metro's HTTP server. */
|
|
42
29
|
readonly ws: WebSocketServer;
|
|
43
|
-
/** The currently active app connection, null if the app is not connected. */
|
|
44
30
|
readonly connection: AppConnection | null;
|
|
45
|
-
/**
|
|
46
|
-
* Resolves with the next AppConnection once the app connects and reports
|
|
47
|
-
* ready. Register this waiter before restarting the app so no ready signal
|
|
48
|
-
* is missed. Rejects if the supplied signal is aborted.
|
|
49
|
-
*/
|
|
50
31
|
nextConnection: (signal?: AbortSignal) => Promise<AppConnection>;
|
|
51
32
|
on: <T extends keyof HarnessBridgeEvents>(event: T, listener: HarnessBridgeEvents[T]) => void;
|
|
52
33
|
off: <T extends keyof HarnessBridgeEvents>(event: T, listener: HarnessBridgeEvents[T]) => void;
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkB,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkB,MAAM,IAAI,CAAC;AAErD,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,YAAY,CAAC;AAoBxD,OAAO,KAAK,EAGV,gBAAgB,EAChB,YAAY,EAGZ,cAAc,EACd,oBAAoB,EACpB,eAAe,EAChB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,aAAa,CAAC;AAKrB,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;CACrF,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,CAAC,UAAU,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CACtC,CAAC;AAEF,KAAK,gBAAgB,GACjB;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,cAAc,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,aAAa,GAAG,IAAI,CAAC;IAC1C,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IACjE,EAAE,EAAE,CAAC,CAAC,SAAS,MAAM,mBAAmB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC9F,GAAG,EAAE,CAAC,CAAC,SAAS,MAAM,mBAAmB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC/F,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAsCF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,oBAAoB,KAC5B,OAAO,CAAC,aAAa,CA2OvB,CAAC"}
|