@expo/serve-sim 0.3.2 → 0.3.4
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/README.md +3 -0
- package/dist/middleware.js +87 -86
- package/dist/native/serve-sim-native.node +0 -0
- package/dist/serve-sim.js +123 -122
- package/package.json +1 -1
- package/src/middleware.ts +47 -28
- package/src/native.ts +5 -0
package/package.json
CHANGED
package/src/middleware.ts
CHANGED
|
@@ -24,7 +24,12 @@ import {
|
|
|
24
24
|
peekDeviceSession,
|
|
25
25
|
type HidSocket,
|
|
26
26
|
} from "./device-session";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
acceptedTokenSubprotocol,
|
|
29
|
+
assertPreviewAccess,
|
|
30
|
+
assertUpgradeAccess,
|
|
31
|
+
upgradeAuthHeaders,
|
|
32
|
+
} from "./session-auth";
|
|
28
33
|
import {
|
|
29
34
|
eventLogEventForAction,
|
|
30
35
|
readEventLog,
|
|
@@ -654,26 +659,35 @@ function webSocketBinary(payload: Buffer<ArrayBufferLike>): Uint8Array<ArrayBuff
|
|
|
654
659
|
* handshake doesn't flush under Bun). Writes the 101 response and resumes the
|
|
655
660
|
* socket on success; on a missing key writes 400 and returns false.
|
|
656
661
|
*/
|
|
657
|
-
function writeWebSocketAccept(req: SimReq, socket: Socket): boolean {
|
|
662
|
+
function writeWebSocketAccept(req: SimReq, socket: Socket, execToken: string): boolean {
|
|
658
663
|
const key = req.headers["sec-websocket-key"];
|
|
659
664
|
if (typeof key !== "string") {
|
|
660
665
|
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
661
666
|
return false;
|
|
662
667
|
}
|
|
663
668
|
const accept = createHash("sha1").update(key + WS_ACCEPT_GUID).digest("base64");
|
|
669
|
+
// A client that offered subprotocols fails the handshake unless one is named back.
|
|
670
|
+
const subprotocol = acceptedTokenSubprotocol(req.headers, execToken);
|
|
664
671
|
socket.write(
|
|
665
672
|
"HTTP/1.1 101 Switching Protocols\r\n" +
|
|
666
673
|
"Upgrade: websocket\r\n" +
|
|
667
674
|
"Connection: Upgrade\r\n" +
|
|
668
675
|
`Sec-WebSocket-Accept: ${accept}\r\n` +
|
|
676
|
+
(subprotocol ? `Sec-WebSocket-Protocol: ${subprotocol}\r\n` : "") +
|
|
669
677
|
"\r\n",
|
|
670
678
|
);
|
|
671
679
|
socket.resume();
|
|
672
680
|
return true;
|
|
673
681
|
}
|
|
674
682
|
|
|
675
|
-
function bridgeWebSocketFrames(
|
|
676
|
-
|
|
683
|
+
function bridgeWebSocketFrames(
|
|
684
|
+
req: SimReq,
|
|
685
|
+
socket: Socket,
|
|
686
|
+
head: Buffer,
|
|
687
|
+
upstreamUrl: string,
|
|
688
|
+
execToken: string,
|
|
689
|
+
): void {
|
|
690
|
+
if (!writeWebSocketAccept(req, socket, execToken)) return;
|
|
677
691
|
|
|
678
692
|
const upstream = new WebSocket(upstreamUrl);
|
|
679
693
|
upstream.binaryType = "arraybuffer";
|
|
@@ -927,13 +941,21 @@ function rawHidSocket(socket: Socket, head: Buffer): HidSocket {
|
|
|
927
941
|
closed = true;
|
|
928
942
|
for (const cb of closeCbs) cb();
|
|
929
943
|
};
|
|
930
|
-
const shutdown = () => {
|
|
944
|
+
const shutdown = (code?: number, reason = "") => {
|
|
931
945
|
fireClose();
|
|
932
|
-
|
|
933
|
-
|
|
946
|
+
const payload = code === undefined ? Buffer.alloc(0) : Buffer.alloc(2 + Buffer.byteLength(reason));
|
|
947
|
+
if (code !== undefined) {
|
|
948
|
+
payload.writeUInt16BE(code);
|
|
949
|
+
payload.write(reason, 2);
|
|
950
|
+
}
|
|
951
|
+
try {
|
|
952
|
+
socket.end(websocketFrame(0x8, payload));
|
|
953
|
+
socket.destroySoon();
|
|
954
|
+
} catch { socket.destroy(); }
|
|
934
955
|
};
|
|
935
956
|
|
|
936
957
|
const drain = () => {
|
|
958
|
+
if (closed) return;
|
|
937
959
|
for (;;) {
|
|
938
960
|
let frame: ParsedWebSocketFrame | null;
|
|
939
961
|
try {
|
|
@@ -971,6 +993,7 @@ function rawHidSocket(socket: Socket, head: Buffer): HidSocket {
|
|
|
971
993
|
function attachHidInProcess(
|
|
972
994
|
req: SimReq,
|
|
973
995
|
socket: Socket,
|
|
996
|
+
execToken: string,
|
|
974
997
|
head: Buffer,
|
|
975
998
|
device: string | null,
|
|
976
999
|
initialStreamSettings?: StreamSettings,
|
|
@@ -982,7 +1005,7 @@ function attachHidInProcess(
|
|
|
982
1005
|
} catch {
|
|
983
1006
|
return false;
|
|
984
1007
|
}
|
|
985
|
-
if (!writeWebSocketAccept(req, socket)) return true; // bad request handled
|
|
1008
|
+
if (!writeWebSocketAccept(req, socket, execToken)) return true; // bad request handled
|
|
986
1009
|
session.attachHidSocket(rawHidSocket(socket, head));
|
|
987
1010
|
return true;
|
|
988
1011
|
}
|
|
@@ -1525,8 +1548,6 @@ export interface SimMiddlewareOptions {
|
|
|
1525
1548
|
* decides access. Loopback is always allowed.
|
|
1526
1549
|
*/
|
|
1527
1550
|
corsOrigins?: string[];
|
|
1528
|
-
/** @deprecated Use `corsOrigins`. */
|
|
1529
|
-
metricsCorsOrigins?: string[];
|
|
1530
1551
|
frameAncestors?: string[];
|
|
1531
1552
|
/** Public page the Share button copies instead of this preview's address. */
|
|
1532
1553
|
shareUrl?: string;
|
|
@@ -1702,7 +1723,7 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
1702
1723
|
// can't read this value (it's only injected into the preview page's config).
|
|
1703
1724
|
const execToken = options?.execToken ?? randomBytes(32).toString("base64url");
|
|
1704
1725
|
const requirePreviewToken = options?.requirePreviewToken ?? false;
|
|
1705
|
-
const corsOrigins = [...(options?.corsOrigins ?? [])
|
|
1726
|
+
const corsOrigins = [...(options?.corsOrigins ?? [])];
|
|
1706
1727
|
const frameAncestors = options?.frameAncestors ?? [];
|
|
1707
1728
|
const shareUrl = options?.shareUrl;
|
|
1708
1729
|
// The proxied DevTools frontend sits behind the same cookie, so its document needs the policy too.
|
|
@@ -2665,13 +2686,7 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
2665
2686
|
// their own, so gate them here too.
|
|
2666
2687
|
if (
|
|
2667
2688
|
!assertUpgradeAccess(
|
|
2668
|
-
|
|
2669
|
-
authorization: req.headers.authorization,
|
|
2670
|
-
cookie: req.headers.cookie,
|
|
2671
|
-
origin: req.headers.origin,
|
|
2672
|
-
host: req.headers.host,
|
|
2673
|
-
"sec-fetch-site": req.headers["sec-fetch-site"],
|
|
2674
|
-
},
|
|
2689
|
+
upgradeAuthHeaders(req.headers),
|
|
2675
2690
|
execToken,
|
|
2676
2691
|
{ required: requirePreviewToken },
|
|
2677
2692
|
)
|
|
@@ -2687,7 +2702,13 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
2687
2702
|
(async () => {
|
|
2688
2703
|
try {
|
|
2689
2704
|
const bridge = await getInspectWebKitBridge();
|
|
2690
|
-
bridgeWebSocketFrames(
|
|
2705
|
+
bridgeWebSocketFrames(
|
|
2706
|
+
req,
|
|
2707
|
+
socket,
|
|
2708
|
+
head,
|
|
2709
|
+
`ws://127.0.0.1:${bridge.port}${devtoolsTarget.upstreamPath}`,
|
|
2710
|
+
execToken,
|
|
2711
|
+
);
|
|
2691
2712
|
} catch (err) {
|
|
2692
2713
|
const message = err instanceof Error ? err.message : "Failed to start inspect-webkit";
|
|
2693
2714
|
socket.end(`HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n${message}`);
|
|
@@ -2702,7 +2723,7 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
2702
2723
|
const device = helperTarget.device ?? selectedDevice;
|
|
2703
2724
|
if (helperTarget.upstreamPath === "/ws") {
|
|
2704
2725
|
// HID input is delivered to the in-process DeviceSession.
|
|
2705
|
-
if (attachHidInProcess(req, socket, head, device, streamSettings)) return;
|
|
2726
|
+
if (attachHidInProcess(req, socket, execToken, head, device, streamSettings)) return;
|
|
2706
2727
|
socket.end("HTTP/1.1 404 Not Found\r\n\r\n");
|
|
2707
2728
|
return;
|
|
2708
2729
|
}
|
|
@@ -2717,6 +2738,7 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
2717
2738
|
const execWebSocketHandler = createExecWebSocketHandler({
|
|
2718
2739
|
path: `${base}/exec-ws`,
|
|
2719
2740
|
execToken,
|
|
2741
|
+
corsOrigins,
|
|
2720
2742
|
ssePrefixes: [
|
|
2721
2743
|
`${base}/api/events`,
|
|
2722
2744
|
`${base}/api/event-log/events`,
|
|
@@ -2740,17 +2762,14 @@ export function simMiddleware(options?: SimMiddlewareOptions): SimMiddleware {
|
|
|
2740
2762
|
});
|
|
2741
2763
|
|
|
2742
2764
|
fetchMiddleware.handleWebSocket = (request: Request, websocket: UpgradeHandlerWebSocket): boolean => {
|
|
2765
|
+
// Before any refusal below can close it: an unhandled error from a peer that keeps sending
|
|
2766
|
+
// would exit the process.
|
|
2767
|
+
websocket.on("error", () => websocket.close());
|
|
2743
2768
|
// Embedded hosts forward accepted sockets and bypass the request gate. The exec channel
|
|
2744
|
-
// re-checks the token
|
|
2769
|
+
// re-checks the token itself; the helper HID socket does not.
|
|
2745
2770
|
if (
|
|
2746
2771
|
!assertUpgradeAccess(
|
|
2747
|
-
|
|
2748
|
-
authorization: request.headers.get("authorization") ?? undefined,
|
|
2749
|
-
cookie: request.headers.get("cookie") ?? undefined,
|
|
2750
|
-
origin: request.headers.get("origin") ?? undefined,
|
|
2751
|
-
host: request.headers.get("host") ?? undefined,
|
|
2752
|
-
"sec-fetch-site": request.headers.get("sec-fetch-site") ?? undefined,
|
|
2753
|
-
},
|
|
2772
|
+
upgradeAuthHeaders(request),
|
|
2754
2773
|
execToken,
|
|
2755
2774
|
{ required: requirePreviewToken },
|
|
2756
2775
|
)
|
package/src/native.ts
CHANGED
|
@@ -75,6 +75,7 @@ interface NativeAddon {
|
|
|
75
75
|
) => SimCaptureHandle;
|
|
76
76
|
axDescribe(udid: string): Promise<string>;
|
|
77
77
|
axFrontmost(udid: string): Promise<string>;
|
|
78
|
+
axTypeKeyboardCharacter(udid: string, character: string): Promise<boolean>;
|
|
78
79
|
setHardwareKeyboard(udid: string, enabled: boolean): Promise<boolean>;
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -377,6 +378,10 @@ export function axFrontmostAsync(udid: string): Promise<string> {
|
|
|
377
378
|
return load().axFrontmost(udid);
|
|
378
379
|
}
|
|
379
380
|
|
|
381
|
+
export function axTypeKeyboardCharacterAsync(udid: string, character: string): Promise<boolean> {
|
|
382
|
+
return load().axTypeKeyboardCharacter(udid, character);
|
|
383
|
+
}
|
|
384
|
+
|
|
380
385
|
/**
|
|
381
386
|
* Connect/disconnect the device's hardware keyboard (⌘⇧K). Disconnecting makes
|
|
382
387
|
* the guest show its on-screen keyboard. Per-device; resolves true when the
|