@heojeongbo/wirestage 0.1.0 → 0.2.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/entities/plugin/model/http.d.ts +12 -3
- package/dist/entities/plugin/model/http.d.ts.map +1 -1
- package/dist/entities/plugin/model/socket.d.ts +12 -7
- package/dist/entities/plugin/model/socket.d.ts.map +1 -1
- package/dist/entities/plugin/model/stream.d.ts +7 -4
- package/dist/entities/plugin/model/stream.d.ts.map +1 -1
- package/dist/features/sse/lib/create-sse.d.ts.map +1 -1
- package/dist/features/sse/lib/create-sse.js +2 -1
- package/dist/features/sse/lib/create-sse.js.map +1 -1
- package/dist/features/sse/model/types.d.ts +8 -2
- package/dist/features/sse/model/types.d.ts.map +1 -1
- package/dist/features/websocket/lib/proxy.d.ts.map +1 -1
- package/dist/features/websocket/lib/proxy.js +16 -3
- package/dist/features/websocket/lib/proxy.js.map +1 -1
- package/dist/features/websocket/model/types.d.ts +5 -1
- package/dist/features/websocket/model/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/api/playwright/ports.d.ts +10 -1
- package/dist/shared/api/playwright/ports.d.ts.map +1 -1
- package/dist/shared/api/stream-server/stream-server.d.ts +3 -1
- package/dist/shared/api/stream-server/stream-server.d.ts.map +1 -1
- package/dist/shared/api/stream-server/stream-server.js +10 -3
- package/dist/shared/api/stream-server/stream-server.js.map +1 -1
- package/dist/widgets/stage/index.d.ts +1 -1
- package/dist/widgets/stage/index.d.ts.map +1 -1
- package/dist/widgets/stage/lib/create-stage.d.ts.map +1 -1
- package/dist/widgets/stage/lib/create-stage.js +4 -0
- package/dist/widgets/stage/lib/create-stage.js.map +1 -1
- package/dist/widgets/stage/lib/http-router.d.ts +11 -2
- package/dist/widgets/stage/lib/http-router.d.ts.map +1 -1
- package/dist/widgets/stage/lib/http-router.js +317 -76
- package/dist/widgets/stage/lib/http-router.js.map +1 -1
- package/dist/widgets/stage/lib/reads.d.ts +4 -1
- package/dist/widgets/stage/lib/reads.d.ts.map +1 -1
- package/dist/widgets/stage/lib/reads.js +32 -1
- package/dist/widgets/stage/lib/reads.js.map +1 -1
- package/dist/widgets/stage/lib/response-stream.d.ts +17 -3
- package/dist/widgets/stage/lib/response-stream.d.ts.map +1 -1
- package/dist/widgets/stage/lib/response-stream.js +88 -36
- package/dist/widgets/stage/lib/response-stream.js.map +1 -1
- package/dist/widgets/stage/lib/socket-router.d.ts.map +1 -1
- package/dist/widgets/stage/lib/socket-router.js +44 -15
- package/dist/widgets/stage/lib/socket-router.js.map +1 -1
- package/dist/widgets/stage/model/stage.d.ts +40 -7
- package/dist/widgets/stage/model/stage.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/entities/plugin/model/http.ts +12 -3
- package/src/entities/plugin/model/socket.ts +12 -7
- package/src/entities/plugin/model/stream.ts +7 -4
- package/src/features/sse/lib/create-sse.ts +2 -1
- package/src/features/sse/model/types.ts +8 -2
- package/src/features/websocket/lib/proxy.ts +15 -3
- package/src/features/websocket/model/types.ts +5 -1
- package/src/index.ts +2 -0
- package/src/shared/api/playwright/ports.ts +7 -2
- package/src/shared/api/stream-server/stream-server.ts +11 -3
- package/src/widgets/stage/index.ts +2 -0
- package/src/widgets/stage/lib/create-stage.ts +4 -0
- package/src/widgets/stage/lib/http-router.ts +425 -91
- package/src/widgets/stage/lib/reads.ts +44 -2
- package/src/widgets/stage/lib/response-stream.ts +110 -37
- package/src/widgets/stage/lib/socket-router.ts +45 -16
- package/src/widgets/stage/model/stage.ts +42 -7
|
@@ -31,9 +31,15 @@ export interface SseConnection {
|
|
|
31
31
|
readonly sent: number;
|
|
32
32
|
/** Write an event. A string is sent as `data`. Resolves once written. */
|
|
33
33
|
send(event: SseEvent | string): Promise<void>;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Close cleanly; EventSource will reconnect after its retry delay.
|
|
36
|
+
* Resolves once the end is on the timeline.
|
|
37
|
+
*/
|
|
35
38
|
end(): Promise<void>;
|
|
36
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Reset the connection, as a network drop would. Resolves once the cut is
|
|
41
|
+
* on the timeline.
|
|
42
|
+
*/
|
|
37
43
|
cut(): Promise<void>;
|
|
38
44
|
readonly closed: Promise<SseEnd>;
|
|
39
45
|
}
|
|
@@ -49,6 +49,9 @@ export async function createProxy(
|
|
|
49
49
|
label: `message on ${socket.url.pathname}`,
|
|
50
50
|
});
|
|
51
51
|
const closed = createDeferred<WsCloseInfo>();
|
|
52
|
+
// Who closed, when neither end did: the test, or the stub — the
|
|
53
|
+
// stage closes the socket of a rule that throws.
|
|
54
|
+
let closedBy: "test" | "stub" | undefined;
|
|
52
55
|
log("lifecycle", "none", "page", { event: "open", url: socket.url.href });
|
|
53
56
|
const forward = (
|
|
54
57
|
direction: "in" | "out",
|
|
@@ -57,7 +60,16 @@ export async function createProxy(
|
|
|
57
60
|
) => {
|
|
58
61
|
const passing = toMessage(direction, raw, context.clock.now());
|
|
59
62
|
messages.push(passing);
|
|
60
|
-
|
|
63
|
+
let dropped: boolean | undefined;
|
|
64
|
+
try {
|
|
65
|
+
dropped = options.drop?.(passing);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// The stage fails a rule that throws and closes both ends: the
|
|
68
|
+
// closes they report are the stub's, not the page's or server's.
|
|
69
|
+
closedBy ??= "stub";
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
if (dropped) {
|
|
61
73
|
log("fault", direction, "stub", { reason: "dropped" }, raw);
|
|
62
74
|
return;
|
|
63
75
|
}
|
|
@@ -77,7 +89,6 @@ export async function createProxy(
|
|
|
77
89
|
// end to pass the close on, and the end that started it to complete
|
|
78
90
|
// it — the page's socket hangs in CLOSING until its end is closed.
|
|
79
91
|
// Once: each of those closes is reported back through a handler.
|
|
80
|
-
let closedBy: "test" | undefined;
|
|
81
92
|
const relay =
|
|
82
93
|
(from: "page" | "network") =>
|
|
83
94
|
(code: number | undefined, reason: string | undefined) => {
|
|
@@ -97,7 +108,8 @@ export async function createProxy(
|
|
|
97
108
|
};
|
|
98
109
|
page.onClose(relay("page"));
|
|
99
110
|
server.onClose(relay("network"));
|
|
100
|
-
// Over from the moment the test closes it
|
|
111
|
+
// Over from the moment the test closes it or the rule fails, before
|
|
112
|
+
// either end hears.
|
|
101
113
|
const over = () => closed.settled || closedBy !== undefined;
|
|
102
114
|
connections.push({
|
|
103
115
|
url: socket.url.href,
|
|
@@ -90,7 +90,11 @@ export interface WsMock {
|
|
|
90
90
|
export interface WsProxyOptions {
|
|
91
91
|
/** Proxy this many connections, then leave the rest to the next rule. */
|
|
92
92
|
readonly times?: number;
|
|
93
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Drop a message instead of forwarding it. Dropped messages are recorded
|
|
95
|
+
* as faults. If it throws, the stage closes both ends, the page's with
|
|
96
|
+
* 1011, and records a `rule-error` fault.
|
|
97
|
+
*/
|
|
94
98
|
readonly drop?: (message: WsMessage) => boolean;
|
|
95
99
|
}
|
|
96
100
|
|
package/src/index.ts
CHANGED
|
@@ -102,9 +102,11 @@ export type { UrlMatcher } from "./shared/lib/match/index.js";
|
|
|
102
102
|
export { compileUrlMatcher } from "./shared/lib/match/index.js";
|
|
103
103
|
export type {
|
|
104
104
|
AttachOptions,
|
|
105
|
+
MarkOptions,
|
|
105
106
|
ReportOptions,
|
|
106
107
|
Stage,
|
|
107
108
|
StageCore,
|
|
109
|
+
StageCursor,
|
|
108
110
|
StageOptions,
|
|
109
111
|
StageReport,
|
|
110
112
|
} from "./widgets/stage/index.js";
|
|
@@ -30,6 +30,8 @@ export interface ResponsePort {
|
|
|
30
30
|
status(): number;
|
|
31
31
|
headers(): HeaderMap;
|
|
32
32
|
body(): Promise<Buffer>;
|
|
33
|
+
/** Who sent it; null for a response a route made up. */
|
|
34
|
+
serverAddr(): Promise<{ ipAddress: string; port: number } | null>;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export interface RequestPort {
|
|
@@ -44,8 +46,11 @@ export interface RequestPort {
|
|
|
44
46
|
allHeaders(): Promise<HeaderMap>;
|
|
45
47
|
postDataBuffer(): Buffer | null;
|
|
46
48
|
resourceType(): string;
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The frame that sent it, the same object for every request of that
|
|
51
|
+
* frame. Throws for requests a service worker makes; callers must guard it.
|
|
52
|
+
*/
|
|
53
|
+
frame(): { url(): string; page(): PagePort };
|
|
49
54
|
response(): Promise<ResponsePort | null>;
|
|
50
55
|
failure(): { errorText: string } | null;
|
|
51
56
|
}
|
|
@@ -37,7 +37,9 @@ export interface StreamServer {
|
|
|
37
37
|
/**
|
|
38
38
|
* Register a handler and return the URL to redirect one request to.
|
|
39
39
|
* `secure` is whether the page's request was https: Playwright only lets
|
|
40
|
-
* `route.continue` change the URL within the same scheme.
|
|
40
|
+
* `route.continue` change the URL within the same scheme. Every response
|
|
41
|
+
* closes its connection (`connection: close`); a handler leaves that
|
|
42
|
+
* header alone.
|
|
41
43
|
*/
|
|
42
44
|
open(
|
|
43
45
|
handler: NodeHandler,
|
|
@@ -107,6 +109,11 @@ export function createStreamServer(
|
|
|
107
109
|
};
|
|
108
110
|
|
|
109
111
|
const onRequest = (request: IncomingMessage, response: ServerResponse) => {
|
|
112
|
+
// One response per connection. A browser re-sends a request that dies
|
|
113
|
+
// before its first byte on a connection it reused — a stream cut at
|
|
114
|
+
// once — and its session is gone by then: the page would get a 404
|
|
115
|
+
// where the test cut the stream.
|
|
116
|
+
response.setHeader("connection", "close");
|
|
110
117
|
allowCors(request, response);
|
|
111
118
|
if (request.method === "OPTIONS") {
|
|
112
119
|
response.writeHead(204);
|
|
@@ -143,8 +150,9 @@ export function createStreamServer(
|
|
|
143
150
|
return;
|
|
144
151
|
}
|
|
145
152
|
options.log?.error("stream handler failed", { session: id }, error);
|
|
146
|
-
|
|
147
|
-
response.
|
|
153
|
+
// After the head, a clean end would pass for the whole response.
|
|
154
|
+
if (response.headersSent) response.destroy();
|
|
155
|
+
else response.writeHead(500).end();
|
|
148
156
|
});
|
|
149
157
|
};
|
|
150
158
|
|
|
@@ -41,6 +41,7 @@ const RESERVED = new Set<string>([
|
|
|
41
41
|
"frames",
|
|
42
42
|
"drain",
|
|
43
43
|
"mark",
|
|
44
|
+
"cursor",
|
|
44
45
|
"since",
|
|
45
46
|
"between",
|
|
46
47
|
"waitFor",
|
|
@@ -93,6 +94,8 @@ export function createStage<const Plugins extends readonly AnyWirePlugin[]>(
|
|
|
93
94
|
streams: streamServer,
|
|
94
95
|
pageId,
|
|
95
96
|
correlation: createSequence("x"),
|
|
97
|
+
clock,
|
|
98
|
+
timeoutMs: options.pageTimeoutMs ?? 2_000,
|
|
96
99
|
});
|
|
97
100
|
const sockets = createSocketRouter({
|
|
98
101
|
timeline,
|
|
@@ -121,6 +124,7 @@ export function createStage<const Plugins extends readonly AnyWirePlugin[]>(
|
|
|
121
124
|
timeline,
|
|
122
125
|
clock,
|
|
123
126
|
sync,
|
|
127
|
+
pageId,
|
|
124
128
|
wait: {
|
|
125
129
|
timeout: options.wait?.timeout ?? 5_000,
|
|
126
130
|
interval: options.wait?.interval ?? 25,
|