@react-native-harness/bridge 1.5.0 → 1.6.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/binary.d.ts +16 -0
- package/dist/binary.d.ts.map +1 -0
- package/dist/binary.js +47 -0
- package/dist/events.d.ts +45 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +1 -0
- package/dist/link/channel.d.ts +45 -0
- package/dist/link/channel.d.ts.map +1 -0
- package/dist/link/channel.js +414 -0
- package/dist/link/envelope.d.ts +38 -0
- package/dist/link/envelope.d.ts.map +1 -0
- package/dist/link/envelope.js +90 -0
- package/dist/link/index.d.ts +22 -0
- package/dist/link/index.d.ts.map +1 -0
- package/dist/link/index.js +271 -0
- package/dist/link/ports.d.ts +31 -0
- package/dist/link/ports.d.ts.map +1 -0
- package/dist/link/ports.js +7 -0
- package/dist/link/scheduler.d.ts +21 -0
- package/dist/link/scheduler.d.ts.map +1 -0
- package/dist/link/scheduler.js +116 -0
- package/dist/link/standard-websocket.d.ts +36 -0
- package/dist/link/standard-websocket.d.ts.map +1 -0
- package/dist/link/standard-websocket.js +214 -0
- package/dist/link/streams.d.ts +4 -0
- package/dist/link/streams.d.ts.map +1 -0
- package/dist/link/streams.js +15 -0
- package/dist/platform-bridge.d.ts +4 -0
- package/dist/platform-bridge.d.ts.map +1 -0
- package/dist/platform-bridge.js +25 -0
- package/package.json +3 -3
|
@@ -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;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HarnessPlatformRunner } from '@react-native-harness/platforms';
|
|
2
|
+
import type { BridgeServerFunctions } from './shared.js';
|
|
3
|
+
export declare const createPlatformBridgeFunctions: (platformRunner: HarnessPlatformRunner) => Partial<BridgeServerFunctions>;
|
|
4
|
+
//# sourceMappingURL=platform-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-bridge.d.ts","sourceRoot":"","sources":["../src/platform-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD,eAAO,MAAM,6BAA6B,GACxC,gBAAgB,qBAAqB,KACpC,OAAO,CAAC,qBAAqB,CAwB/B,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const createPlatformBridgeFunctions = (platformRunner) => {
|
|
2
|
+
return {
|
|
3
|
+
'platform.actions.tap': async (x, y) => {
|
|
4
|
+
await platformRunner.actions.tap(x, y);
|
|
5
|
+
},
|
|
6
|
+
'platform.actions.inputText': async (text) => {
|
|
7
|
+
await platformRunner.actions.inputText(text);
|
|
8
|
+
},
|
|
9
|
+
'platform.actions.tapElement': async (element) => {
|
|
10
|
+
await platformRunner.actions.tapElement(element);
|
|
11
|
+
},
|
|
12
|
+
'platform.actions.screenshot': async () => {
|
|
13
|
+
return await platformRunner.actions.screenshot();
|
|
14
|
+
},
|
|
15
|
+
'platform.queries.getUiHierarchy': async () => {
|
|
16
|
+
return await platformRunner.queries.getUiHierarchy();
|
|
17
|
+
},
|
|
18
|
+
'platform.queries.findByTestId': async (testId) => {
|
|
19
|
+
return await platformRunner.queries.findByTestId(testId);
|
|
20
|
+
},
|
|
21
|
+
'platform.queries.findAllByTestId': async (testId) => {
|
|
22
|
+
return await platformRunner.queries.findAllByTestId(testId);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-harness/bridge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"ssim.js": "^3.5.0",
|
|
33
33
|
"tslib": "^2.3.0",
|
|
34
34
|
"ws": "^8.18.2",
|
|
35
|
-
"@react-native-harness/platforms": "1.
|
|
36
|
-
"@react-native-harness/tools": "1.
|
|
35
|
+
"@react-native-harness/platforms": "1.6.0",
|
|
36
|
+
"@react-native-harness/tools": "1.6.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/pixelmatch": "^5.2.6",
|