@react-native-harness/bridge 1.5.0 → 1.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.
Files changed (104) hide show
  1. package/dist/app-liveness.d.ts +33 -0
  2. package/dist/app-liveness.d.ts.map +1 -0
  3. package/dist/app-liveness.js +57 -0
  4. package/dist/binary.d.ts +16 -0
  5. package/dist/binary.d.ts.map +1 -0
  6. package/dist/binary.js +47 -0
  7. package/dist/client.d.ts +6 -4
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +112 -105
  10. package/dist/connection-check.d.ts +22 -0
  11. package/dist/connection-check.d.ts.map +1 -0
  12. package/dist/connection-check.js +30 -0
  13. package/dist/errors.d.ts +6 -2
  14. package/dist/errors.d.ts.map +1 -1
  15. package/dist/errors.js +12 -4
  16. package/dist/image-snapshot.d.ts +2 -2
  17. package/dist/image-snapshot.d.ts.map +1 -1
  18. package/dist/image-snapshot.js +21 -8
  19. package/dist/index.d.ts +0 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +0 -2
  22. package/dist/js-block-budget-policy.d.ts +31 -0
  23. package/dist/js-block-budget-policy.d.ts.map +1 -0
  24. package/dist/js-block-budget-policy.js +60 -0
  25. package/dist/js-watchdog.d.ts +31 -0
  26. package/dist/js-watchdog.d.ts.map +1 -0
  27. package/dist/js-watchdog.js +46 -0
  28. package/dist/link/channel.d.ts +45 -0
  29. package/dist/link/channel.d.ts.map +1 -0
  30. package/dist/link/channel.js +414 -0
  31. package/dist/link/envelope.d.ts +38 -0
  32. package/dist/link/envelope.d.ts.map +1 -0
  33. package/dist/link/envelope.js +90 -0
  34. package/dist/link/index.d.ts +22 -0
  35. package/dist/link/index.d.ts.map +1 -0
  36. package/dist/link/index.js +271 -0
  37. package/dist/link/ports.d.ts +31 -0
  38. package/dist/link/ports.d.ts.map +1 -0
  39. package/dist/link/ports.js +7 -0
  40. package/dist/link/scheduler.d.ts +21 -0
  41. package/dist/link/scheduler.d.ts.map +1 -0
  42. package/dist/link/scheduler.js +116 -0
  43. package/dist/link/standard-websocket.d.ts +36 -0
  44. package/dist/link/standard-websocket.d.ts.map +1 -0
  45. package/dist/link/standard-websocket.js +214 -0
  46. package/dist/link/streams.d.ts +4 -0
  47. package/dist/link/streams.d.ts.map +1 -0
  48. package/dist/link/streams.js +15 -0
  49. package/dist/protocol.d.ts +10 -5
  50. package/dist/protocol.d.ts.map +1 -1
  51. package/dist/protocol.js +2 -2
  52. package/dist/rpc-peer.d.ts +3 -1
  53. package/dist/rpc-peer.d.ts.map +1 -1
  54. package/dist/rpc-peer.js +2 -2
  55. package/dist/server.d.ts +4 -0
  56. package/dist/server.d.ts.map +1 -1
  57. package/dist/server.js +71 -56
  58. package/dist/shared/test-runner.d.ts +11 -0
  59. package/dist/shared/test-runner.d.ts.map +1 -1
  60. package/dist/shared.d.ts +5 -1
  61. package/dist/shared.d.ts.map +1 -1
  62. package/package.json +4 -3
  63. package/src/app-liveness.ts +103 -0
  64. package/src/binary.ts +74 -0
  65. package/src/client.ts +172 -170
  66. package/src/connection-check.ts +60 -0
  67. package/src/errors.ts +16 -5
  68. package/src/image-snapshot.ts +31 -9
  69. package/src/index.ts +0 -2
  70. package/src/js-block-budget-policy.ts +90 -0
  71. package/src/js-watchdog.ts +78 -0
  72. package/src/link/README.md +43 -0
  73. package/src/link/channel.ts +473 -0
  74. package/src/link/envelope.ts +110 -0
  75. package/src/link/index.ts +321 -0
  76. package/src/link/ports.ts +42 -0
  77. package/src/link/scheduler.ts +132 -0
  78. package/src/link/standard-websocket.ts +251 -0
  79. package/src/link/streams.ts +34 -0
  80. package/src/protocol.ts +14 -9
  81. package/src/rpc-peer.ts +6 -3
  82. package/src/server.ts +99 -67
  83. package/src/shared/test-runner.ts +12 -0
  84. package/src/shared.ts +6 -1
  85. package/dist/binary-transfer.d.ts +0 -17
  86. package/dist/binary-transfer.d.ts.map +0 -1
  87. package/dist/binary-transfer.js +0 -60
  88. package/dist/heartbeat.d.ts +0 -13
  89. package/dist/heartbeat.d.ts.map +0 -1
  90. package/dist/heartbeat.js +0 -49
  91. package/dist/transport.d.ts +0 -22
  92. package/dist/transport.d.ts.map +0 -1
  93. package/dist/transport.js +0 -22
  94. package/dist/websocket-client-transport.d.ts +0 -3
  95. package/dist/websocket-client-transport.d.ts.map +0 -1
  96. package/dist/websocket-client-transport.js +0 -45
  97. package/dist/websocket-server-transport.d.ts +0 -4
  98. package/dist/websocket-server-transport.d.ts.map +0 -1
  99. package/dist/websocket-server-transport.js +0 -43
  100. package/src/binary-transfer.ts +0 -79
  101. package/src/heartbeat.ts +0 -67
  102. package/src/transport.ts +0 -47
  103. package/src/websocket-client-transport.ts +0 -85
  104. package/src/websocket-server-transport.ts +0 -54
@@ -0,0 +1,214 @@
1
+ import { globalClock } from "./ports.js";
2
+ export { globalClock } from "./ports.js";
3
+ const OPEN = 1;
4
+ const DEFAULT_POLL_MS = 4;
5
+ export class StandardWebSocketConnection {
6
+ #socket;
7
+ #clock;
8
+ #pollMs;
9
+ #dataListeners = new Set();
10
+ #binaryListeners = new Set();
11
+ #closeListeners = new Set();
12
+ #errorListeners = new Set();
13
+ #sentListeners = new Set();
14
+ #pending = [];
15
+ #waiters = [];
16
+ #queued = 0;
17
+ #flushed = 0;
18
+ #checkScheduled = false;
19
+ #timer;
20
+ #closed = false;
21
+ constructor(socket, options = {}) {
22
+ this.#socket = socket;
23
+ this.#clock = options.clock ?? globalClock;
24
+ this.#pollMs = options.pollMs ?? DEFAULT_POLL_MS;
25
+ // why: the default, "blob", cannot be read without awaiting, which would reorder messages.
26
+ socket.binaryType = "arraybuffer";
27
+ const onMessage = (event) => {
28
+ this.#onMessage(event.data);
29
+ };
30
+ const onClose = () => this.#markClosed();
31
+ const onError = (event) => {
32
+ const error = event.error instanceof Error ? event.error : new Error("WebSocket error");
33
+ for (const listener of this.#errorListeners)
34
+ listener(error);
35
+ this.#markClosed();
36
+ };
37
+ socket.addEventListener("message", onMessage);
38
+ socket.addEventListener("close", onClose);
39
+ socket.addEventListener("error", onError);
40
+ }
41
+ get closed() {
42
+ return this.#closed;
43
+ }
44
+ get #buffered() {
45
+ return this.#socket.bufferedAmount ?? 0;
46
+ }
47
+ get unsentBytes() {
48
+ return this.#buffered;
49
+ }
50
+ onData(listener) {
51
+ this.#dataListeners.add(listener);
52
+ if (this.#pending.length > 0) {
53
+ for (const chunk of this.#pending.splice(0))
54
+ listener(chunk);
55
+ }
56
+ return () => {
57
+ this.#dataListeners.delete(listener);
58
+ };
59
+ }
60
+ onBinary(listener) {
61
+ this.#binaryListeners.add(listener);
62
+ return () => {
63
+ this.#binaryListeners.delete(listener);
64
+ };
65
+ }
66
+ onClose(listener) {
67
+ if (this.#closed) {
68
+ listener();
69
+ return () => undefined;
70
+ }
71
+ this.#closeListeners.add(listener);
72
+ return () => {
73
+ this.#closeListeners.delete(listener);
74
+ };
75
+ }
76
+ onError(listener) {
77
+ this.#errorListeners.add(listener);
78
+ return () => {
79
+ this.#errorListeners.delete(listener);
80
+ };
81
+ }
82
+ onSent(listener) {
83
+ this.#sentListeners.add(listener);
84
+ return () => {
85
+ this.#sentListeners.delete(listener);
86
+ };
87
+ }
88
+ write(contents) {
89
+ return this.#send(contents);
90
+ }
91
+ writeBinary(bytes) {
92
+ return this.#send(bytes);
93
+ }
94
+ async close() {
95
+ if (this.#closed)
96
+ return;
97
+ this.#socket.close();
98
+ this.#markClosed();
99
+ }
100
+ #send(data) {
101
+ if (this.#closed || this.#socket.readyState !== OPEN)
102
+ return Promise.resolve();
103
+ // why: the standard send() has no completion callback, so what has left the buffer is worked out from bufferedAmount.
104
+ const before = this.#buffered;
105
+ this.#socket.send(data);
106
+ this.#queued += Math.max(0, this.#buffered - before);
107
+ return new Promise((resolve) => {
108
+ this.#waiters.push({ resolve, target: this.#queued });
109
+ this.#scheduleCheck();
110
+ });
111
+ }
112
+ #onMessage(data) {
113
+ if (typeof data === "string") {
114
+ if (this.#dataListeners.size === 0) {
115
+ this.#pending.push(data);
116
+ }
117
+ else {
118
+ for (const listener of this.#dataListeners)
119
+ listener(data);
120
+ }
121
+ }
122
+ else if (data instanceof ArrayBuffer) {
123
+ const bytes = new Uint8Array(data);
124
+ for (const listener of this.#binaryListeners)
125
+ listener(bytes);
126
+ }
127
+ else if (ArrayBuffer.isView(data)) {
128
+ const bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
129
+ for (const listener of this.#binaryListeners)
130
+ listener(bytes);
131
+ }
132
+ // why: a peer's message, a credit most of all, says bytes are moving, and is a cheaper look than a timer.
133
+ if (this.#waiters.length > 0 || this.#flushed < this.#queued)
134
+ this.#check();
135
+ }
136
+ #scheduleCheck() {
137
+ if (this.#checkScheduled)
138
+ return;
139
+ // why: a write must not settle, or report sent, before the call that made it has returned.
140
+ this.#checkScheduled = true;
141
+ void Promise.resolve().then(() => {
142
+ this.#checkScheduled = false;
143
+ this.#check();
144
+ });
145
+ }
146
+ #check() {
147
+ if (this.#closed)
148
+ return;
149
+ const buffered = this.#buffered;
150
+ const flushed = Math.max(this.#flushed, this.#queued - buffered);
151
+ const moved = flushed > this.#flushed;
152
+ this.#flushed = flushed;
153
+ if (this.#waiters.some((waiter) => waiter.target <= flushed)) {
154
+ const due = this.#waiters.filter((waiter) => waiter.target <= flushed);
155
+ this.#waiters = this.#waiters.filter((waiter) => waiter.target > flushed);
156
+ for (const waiter of due)
157
+ waiter.resolve();
158
+ }
159
+ if (moved)
160
+ for (const listener of this.#sentListeners)
161
+ listener();
162
+ if (buffered === 0 && this.#timer !== undefined) {
163
+ this.#clock.cancel(this.#timer);
164
+ this.#timer = undefined;
165
+ }
166
+ // why: a background tab throttles timers to about one a second, so a large transfer there slows until the tab is shown again.
167
+ if (buffered > 0 && this.#timer === undefined) {
168
+ this.#timer = this.#clock.setTimer(this.#pollMs, () => {
169
+ this.#timer = undefined;
170
+ this.#check();
171
+ });
172
+ }
173
+ }
174
+ #markClosed() {
175
+ if (this.#closed)
176
+ return;
177
+ this.#closed = true;
178
+ if (this.#timer !== undefined) {
179
+ this.#clock.cancel(this.#timer);
180
+ this.#timer = undefined;
181
+ }
182
+ for (const waiter of this.#waiters.splice(0))
183
+ waiter.resolve();
184
+ for (const listener of this.#closeListeners)
185
+ listener();
186
+ }
187
+ }
188
+ export async function connectStandardWebSocket(options) {
189
+ const create = options.createSocket ??
190
+ ((url, protocols) => new WebSocket(url, protocols));
191
+ const socket = create(options.url, options.protocols);
192
+ const connection = new StandardWebSocketConnection(socket, options);
193
+ await new Promise((resolve, reject) => {
194
+ if (socket.readyState === OPEN)
195
+ return resolve();
196
+ const onOpen = () => {
197
+ cleanup();
198
+ resolve();
199
+ };
200
+ const onFail = () => {
201
+ cleanup();
202
+ reject(new Error(`could not open a WebSocket to ${options.url}`));
203
+ };
204
+ const cleanup = () => {
205
+ socket.removeEventListener("open", onOpen);
206
+ socket.removeEventListener("error", onFail);
207
+ socket.removeEventListener("close", onFail);
208
+ };
209
+ socket.addEventListener("open", onOpen);
210
+ socket.addEventListener("error", onFail);
211
+ socket.addEventListener("close", onFail);
212
+ });
213
+ return connection;
214
+ }
@@ -0,0 +1,4 @@
1
+ export declare const LinkReadableStream: typeof globalThis.ReadableStream;
2
+ export declare const LinkWritableStream: typeof globalThis.WritableStream;
3
+ export declare const LinkByteLengthQueuingStrategy: typeof globalThis.ByteLengthQueuingStrategy;
4
+ //# sourceMappingURL=streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streams.d.ts","sourceRoot":"","sources":["../../src/link/streams.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,kBAAkB,EAAE,OAAO,UAAU,CAAC,cAEwB,CAAC;AAE5E,eAAO,MAAM,kBAAkB,EAAE,OAAO,UAAU,CAAC,cAEwB,CAAC;AAE5E,eAAO,MAAM,6BAA6B,EAAE,OAAO,UAAU,CAAC,yBAEmC,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { ByteLengthQueuingStrategy as PonyfillByteLengthQueuingStrategy, ReadableStream as PonyfillReadableStream, WritableStream as PonyfillWritableStream, } from "web-streams-polyfill";
2
+ const host = globalThis;
3
+ // why: one source for all three, so a native stream never meets a ponyfilled strategy.
4
+ const native = typeof host.ReadableStream === "function" &&
5
+ typeof host.WritableStream === "function" &&
6
+ typeof host.ByteLengthQueuingStrategy === "function";
7
+ export const LinkReadableStream = native
8
+ ? host.ReadableStream
9
+ : PonyfillReadableStream;
10
+ export const LinkWritableStream = native
11
+ ? host.WritableStream
12
+ : PonyfillWritableStream;
13
+ export const LinkByteLengthQueuingStrategy = native
14
+ ? host.ByteLengthQueuingStrategy
15
+ : PonyfillByteLengthQueuingStrategy;
@@ -32,15 +32,20 @@ export type BridgeReadyMessage = {
32
32
  type: 'ready';
33
33
  device: DeviceDescriptor;
34
34
  };
35
- export type BridgePingMessage = {
36
- type: 'ping';
35
+ /**
36
+ * Sent by the server to check that the app's JS thread is getting a turn to
37
+ * run. Only JS answers it, unlike WebSocket protocol pings, which the native
38
+ * WebSocket answers.
39
+ */
40
+ export type BridgeJsProbeMessage = {
41
+ type: 'js-probe';
37
42
  id: number;
38
43
  };
39
- export type BridgePongMessage = {
40
- type: 'pong';
44
+ export type BridgeJsProbeAckMessage = {
45
+ type: 'js-probe-ack';
41
46
  id: number;
42
47
  };
43
- export type BridgeControlMessage = BridgeReadyMessage | BridgePingMessage | BridgePongMessage;
48
+ export type BridgeControlMessage = BridgeReadyMessage | BridgeJsProbeMessage | BridgeJsProbeAckMessage;
44
49
  export type BridgeMessage<Event extends {
45
50
  type: string;
46
51
  } = BridgeEvents> = BridgeInvokeMessage | BridgeReturnMessage | BridgeEventMessage<Event> | BridgeControlMessage;
@@ -1 +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"}
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;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,cAAc,CAAC;IACrB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAC5B,kBAAkB,GAClB,oBAAoB,GACpB,uBAAuB,CAAC;AAE5B,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 CHANGED
@@ -84,8 +84,8 @@ export const parseBridgeMessage = (raw) => {
84
84
  readDeviceDescriptor(parsed.device);
85
85
  return parsed;
86
86
  }
87
- case 'ping':
88
- case 'pong': {
87
+ case 'js-probe':
88
+ case 'js-probe-ack': {
89
89
  readNumber(parsed.id, 'id');
90
90
  return parsed;
91
91
  }
@@ -1,5 +1,7 @@
1
1
  import { type BridgeControlMessage } from './protocol.js';
2
- import type { RpcTransport } from './transport.js';
2
+ export type RpcTransport = {
3
+ send: (message: string) => void;
4
+ };
3
5
  type RpcMethod = {
4
6
  bivarianceHack(...args: unknown[]): unknown;
5
7
  }['bivarianceHack'];
@@ -1 +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,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC;IACnF,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,CA0KvB,CAAC"}
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;AAEvB,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC;AAEF,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,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC;IACnF,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,CA0KvB,CAAC"}
package/dist/rpc-peer.js CHANGED
@@ -139,8 +139,8 @@ export const createRpcPeer = (options) => {
139
139
  return null;
140
140
  }
141
141
  case 'ready':
142
- case 'ping':
143
- case 'pong':
142
+ case 'js-probe':
143
+ case 'js-probe-ack':
144
144
  return message;
145
145
  }
146
146
  },
package/dist/server.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  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
+ import { type AppLivenessOverrides } from './app-liveness.js';
4
5
  import type { DeviceDescriptor, BridgeEvents, HarnessContext, TestExecutionOptions, TestSuiteResult } from './shared.js';
5
6
  export { AppBridgeDisconnectedError, DeviceNotRespondingError, } from './errors.js';
7
+ export { DEFAULT_APP_LIVENESS_OPTIONS, type AppLivenessOverrides, } from './app-liveness.js';
6
8
  export type AppConnection = {
7
9
  readonly device: DeviceDescriptor;
8
10
  runTests: (path: string, options: TestExecutionOptions) => Promise<TestSuiteResult>;
@@ -25,6 +27,8 @@ type TransportOptions = {
25
27
  export type HarnessBridgeOptions = TransportOptions & {
26
28
  timeout?: number;
27
29
  context: HarnessContext;
30
+ /** Overrides for the connection check and JS watchdog limits. */
31
+ liveness?: AppLivenessOverrides;
28
32
  };
29
33
  export type HarnessBridge = {
30
34
  readonly ws: WebSocketServer;
@@ -1 +1 @@
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;IACpF,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,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,CA4OvB,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;AAWxD,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,mBAAmB,CAAC;AAW3B,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;AACrB,OAAO,EACL,4BAA4B,EAC5B,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC;AAK3B,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;IACpF,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,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;IACxB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC,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,CAgQvB,CAAC"}
package/dist/server.js CHANGED
@@ -5,15 +5,15 @@ import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { randomUUID } from 'node:crypto';
7
7
  import { logger } from '@react-native-harness/tools';
8
- import { BinaryStore, parseBinaryFrame } from './binary-transfer.js';
8
+ import { receiveBinary } from './binary.js';
9
9
  import { AppBridgeDisconnectedError, DeviceNotRespondingError, } from './errors.js';
10
- import { createHeartbeat } from './heartbeat.js';
10
+ import { createAppLiveness, resolveAppLivenessOptions, } from './app-liveness.js';
11
11
  import { matchImageSnapshot } from './image-snapshot.js';
12
+ import { createLink, globalClock, StandardWebSocketConnection, } from './link/index.js';
12
13
  import { serializeBridgeMessage } from './protocol.js';
13
14
  import { createRpcPeer } from './rpc-peer.js';
14
- import { createRpcTransport, } from './transport.js';
15
- import { createNodeWebSocketTransport } from './websocket-server-transport.js';
16
15
  export { AppBridgeDisconnectedError, DeviceNotRespondingError, } from './errors.js';
16
+ export { DEFAULT_APP_LIVENESS_OPTIONS, } from './app-liveness.js';
17
17
  const bridgeLogger = logger.child('bridge');
18
18
  const noop = () => undefined;
19
19
  const createWss = (transport) => {
@@ -26,21 +26,24 @@ const createWss = (transport) => {
26
26
  ? { server: transport.server, path: transport.path }
27
27
  : { noServer: true }));
28
28
  };
29
- const receiveScreenshot = async (binaryStore, reference) => {
30
- const data = binaryStore.get(reference.transferId);
31
- if (!data) {
32
- throw new Error(`Binary data for transfer ${reference.transferId} not found or expired`);
29
+ const receiveScreenshot = async (link, reference) => {
30
+ const data = await receiveBinary(link, reference.channelId);
31
+ if (data.length !== reference.size) {
32
+ throw new Error(`Screenshot transfer ended after ${data.length} of ${reference.size} bytes`);
33
33
  }
34
- binaryStore.delete(reference.transferId);
35
34
  const file = path.join(os.tmpdir(), `harness-screenshot-${randomUUID()}.png`);
36
35
  await fs.writeFile(file, data);
37
36
  return { path: file };
38
37
  };
39
38
  export const createHarnessBridge = async (options) => {
40
- const { timeout, context, ...transportOptions } = options;
39
+ const { timeout, context, liveness, ...transportOptions } = options;
40
+ const livenessOptions = resolveAppLivenessOptions(liveness);
41
41
  const wss = await createWss(transportOptions);
42
42
  bridgeLogger.debug('bridge server ready');
43
43
  const emitter = new EventEmitter();
44
+ // Screenshot files written by this bridge that have not been compared yet.
45
+ const receivedScreenshots = new Set();
46
+ const removeScreenshot = (file) => fs.rm(file, { force: true }).catch(() => undefined);
44
47
  let currentConnection = null;
45
48
  let activeSession = null;
46
49
  const connectionWaiters = [];
@@ -50,26 +53,42 @@ export const createHarnessBridge = async (options) => {
50
53
  activeSession.disconnect(new AppBridgeDisconnectedError('app-replaced'));
51
54
  }
52
55
  bridgeLogger.debug('app connected');
53
- const transport = createNodeWebSocketTransport(ws);
54
- const binaryStore = new BinaryStore();
56
+ const link = createLink(new StandardWebSocketConnection(ws), {
57
+ end: 'gateway',
58
+ clock: globalClock,
59
+ logger: bridgeLogger.child('link'),
60
+ });
55
61
  let readyConnection = null;
56
62
  let disconnected = false;
57
63
  let offMessage = noop;
58
64
  let offClose = noop;
59
65
  let offError = noop;
60
- const closeTransport = () => {
61
- if (transport.state === 'closing' || transport.state === 'closed') {
62
- return;
63
- }
64
- transport.close(1012);
65
- };
66
66
  const rpc = createRpcPeer({
67
67
  localMethods: {
68
- 'device.screenshot.receive': (ref) => receiveScreenshot(binaryStore, ref),
69
- 'test.matchImageSnapshot': (screenshot, testPath, opts) => matchImageSnapshot(screenshot, testPath, opts, context.platform.name),
68
+ 'device.screenshot.receive': async (ref) => {
69
+ const file = await receiveScreenshot(link, ref);
70
+ receivedScreenshots.add(file.path);
71
+ return file;
72
+ },
73
+ 'test.matchImageSnapshot': async (screenshot, testPath, opts) => {
74
+ try {
75
+ return await matchImageSnapshot(screenshot, testPath, opts, context.platform.name, context.updateSnapshot ?? 'new');
76
+ }
77
+ finally {
78
+ // Only delete files this bridge created, never a client-supplied path.
79
+ if (receivedScreenshots.delete(screenshot.path)) {
80
+ await removeScreenshot(screenshot.path);
81
+ }
82
+ }
83
+ },
84
+ },
85
+ transport: {
86
+ send: (message) => {
87
+ void link.write(message);
88
+ },
70
89
  },
71
- transport: createRpcTransport(transport),
72
90
  onEvent: (event) => {
91
+ appLiveness.handleEvent(event);
73
92
  emitter.emit('event', event);
74
93
  },
75
94
  callTimeoutMs: (method) => method === 'runTests' ? undefined : timeout,
@@ -77,14 +96,24 @@ export const createHarnessBridge = async (options) => {
77
96
  return new DeviceNotRespondingError(functionName, args);
78
97
  },
79
98
  });
80
- const heartbeat = createHeartbeat({
81
- sendPing: (id) => {
82
- transport.send(serializeBridgeMessage({ type: 'ping', id }));
99
+ const appLiveness = createAppLiveness({
100
+ socket: ws,
101
+ sendProbe: (id) => {
102
+ void link.write(serializeBridgeMessage({ type: 'js-probe', id }));
83
103
  },
84
- onTimeout: () => {
85
- bridgeLogger.warn('app heartbeat timed out');
86
- disconnect(new AppBridgeDisconnectedError('heartbeat-timeout'));
104
+ onLost: (error) => {
105
+ if (error.reason === 'connection-lost') {
106
+ bridgeLogger.warn('app connection lost (no response to WebSocket ping)');
107
+ }
108
+ else {
109
+ bridgeLogger.warn('app JS thread unresponsive: %s', error.detail);
110
+ }
111
+ disconnect(error);
112
+ // Neither a half-open socket nor a blocked JS thread completes a
113
+ // close handshake, so drop the socket instead of waiting for one.
114
+ ws.terminate();
87
115
  },
116
+ config: livenessOptions,
88
117
  });
89
118
  const disconnect = (reason) => {
90
119
  if (disconnected) {
@@ -95,21 +124,20 @@ export const createHarnessBridge = async (options) => {
95
124
  offClose();
96
125
  offError();
97
126
  bridgeLogger.debug('app disconnected');
98
- heartbeat.dispose();
99
- binaryStore.dispose();
100
- if (activeSession?.transport === transport) {
127
+ appLiveness.dispose();
128
+ if (activeSession?.link === link) {
101
129
  activeSession = null;
102
130
  }
103
131
  if (currentConnection === readyConnection) {
104
132
  currentConnection = null;
105
133
  }
106
134
  rpc.close(reason ?? new AppBridgeDisconnectedError('app-disconnected'));
107
- closeTransport();
135
+ void link.close();
108
136
  if (readyConnection) {
109
137
  emitter.emit('disconnected');
110
138
  }
111
139
  };
112
- activeSession = { disconnect, transport };
140
+ activeSession = { disconnect, link };
113
141
  const handleControlMessage = async (message) => {
114
142
  const controlMessage = await rpc.handleMessage(message);
115
143
  if (!controlMessage) {
@@ -134,30 +162,13 @@ export const createHarnessBridge = async (options) => {
134
162
  }
135
163
  return;
136
164
  }
137
- case 'ping': {
138
- transport.send(serializeBridgeMessage({ type: 'pong', id: controlMessage.id }));
139
- return;
140
- }
141
- case 'pong': {
142
- heartbeat.notifyPong(controlMessage.id);
165
+ case 'js-probe-ack': {
166
+ appLiveness.handleProbeAck(controlMessage.id);
143
167
  return;
144
168
  }
145
169
  }
146
170
  };
147
- const handleBinaryMessage = (message) => {
148
- try {
149
- const { transferId, data } = parseBinaryFrame(message);
150
- binaryStore.add(transferId, data);
151
- }
152
- catch (error) {
153
- bridgeLogger.warn('failed to parse binary frame: %s', error);
154
- }
155
- };
156
- offMessage = transport.onMessage((message) => {
157
- if (typeof message !== 'string') {
158
- handleBinaryMessage(message);
159
- return;
160
- }
171
+ offMessage = link.onData((message) => {
161
172
  void handleControlMessage(message).catch((error) => {
162
173
  bridgeLogger.warn('failed to handle bridge message: %s', error);
163
174
  disconnect(error instanceof Error
@@ -165,14 +176,14 @@ export const createHarnessBridge = async (options) => {
165
176
  : new Error('Received invalid app bridge message'));
166
177
  });
167
178
  });
168
- offClose = transport.onClose(() => {
169
- disconnect();
170
- });
171
- offError = transport.onError((error) => {
179
+ offError = link.onError((error) => {
172
180
  disconnect(error instanceof Error
173
181
  ? error
174
182
  : new AppBridgeDisconnectedError('socket-error'));
175
183
  });
184
+ offClose = link.onClose(() => {
185
+ disconnect();
186
+ });
176
187
  });
177
188
  return {
178
189
  get ws() {
@@ -213,6 +224,10 @@ export const createHarnessBridge = async (options) => {
213
224
  }
214
225
  wss.close();
215
226
  emitter.removeAllListeners();
227
+ for (const file of receivedScreenshots) {
228
+ void removeScreenshot(file);
229
+ }
230
+ receivedScreenshots.clear();
216
231
  },
217
232
  };
218
233
  };
@@ -11,6 +11,7 @@ export type TestResultStatus = 'passed' | 'failed' | 'skipped' | 'todo';
11
11
  export type SerializedError = {
12
12
  name: string;
13
13
  message: string;
14
+ diff?: string;
14
15
  codeFrame?: CodeFrame;
15
16
  diagnostics?: {
16
17
  pendingPromises?: {
@@ -36,6 +37,11 @@ export type TestRunnerSuiteStartedEvent = {
36
37
  type: 'suite-started';
37
38
  name: string;
38
39
  file: string;
40
+ /**
41
+ * The timeout in ms of the suite's `beforeAll`/`afterAll` hooks. Absent
42
+ * for suites that are skipped or todo.
43
+ */
44
+ timeout?: number;
39
45
  };
40
46
  export type TestRunnerTestStartedEvent = {
41
47
  type: 'test-started';
@@ -46,6 +52,11 @@ export type TestRunnerTestStartedEvent = {
46
52
  fullName: string;
47
53
  startedAt: number;
48
54
  declarationMode?: TestDeclarationMode;
55
+ /**
56
+ * The test's timeout in ms, covering its `beforeEach`/`afterEach` hooks.
57
+ * Absent for tests that are skipped or todo.
58
+ */
59
+ timeout?: number;
49
60
  };
50
61
  export type TestRunnerTestFinishedEvent = {
51
62
  type: 'test-finished';
@@ -1 +1 @@
1
- {"version":3,"file":"test-runner.d.ts","sourceRoot":"","sources":["../../src/shared/test-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test-runner.d.ts","sourceRoot":"","sources":["../../src/shared/test-runner.ts"],"names":[],"mappings":"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"}
package/dist/shared.d.ts CHANGED
@@ -96,7 +96,8 @@ export type BridgeClientFunctions = {
96
96
  };
97
97
  export type BinaryDataReference = {
98
98
  type: 'binary';
99
- transferId: number;
99
+ /** The link channel carrying the bytes. */
100
+ channelId: string;
100
101
  size: number;
101
102
  mimeType: 'image/png';
102
103
  };
@@ -111,7 +112,10 @@ export type BridgeServerFunctions = {
111
112
  message: string;
112
113
  }>;
113
114
  };
115
+ export type SnapshotUpdateState = 'all' | 'new' | 'none';
114
116
  export type HarnessContext = {
115
117
  platform: HarnessPlatform;
118
+ /** Jest's snapshot update state; when absent, a missing image snapshot is written. */
119
+ updateSnapshot?: SnapshotUpdateState;
116
120
  };
117
121
  //# sourceMappingURL=shared.d.ts.map