@heojeongbo/wirestage 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/LICENSES.md +21 -0
- package/README.md +150 -2
- package/dist/app/playwright/index.d.ts +3 -0
- package/dist/app/playwright/index.d.ts.map +1 -0
- package/dist/app/playwright/index.js +2 -0
- package/dist/app/playwright/index.js.map +1 -0
- package/dist/app/playwright/lib/fixture.d.ts +61 -0
- package/dist/app/playwright/lib/fixture.d.ts.map +1 -0
- package/dist/app/playwright/lib/fixture.js +110 -0
- package/dist/app/playwright/lib/fixture.js.map +1 -0
- package/dist/connect.d.ts +7 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +7 -0
- package/dist/connect.js.map +1 -0
- package/dist/entities/frame/@x/plugin.d.ts +9 -0
- package/dist/entities/frame/@x/plugin.d.ts.map +1 -0
- package/dist/entities/frame/@x/plugin.js +2 -0
- package/dist/entities/frame/@x/plugin.js.map +1 -0
- package/dist/entities/frame/index.d.ts +6 -0
- package/dist/entities/frame/index.d.ts.map +1 -0
- package/dist/entities/frame/index.js +4 -0
- package/dist/entities/frame/index.js.map +1 -0
- package/dist/entities/frame/lib/filter.d.ts +8 -0
- package/dist/entities/frame/lib/filter.d.ts.map +1 -0
- package/dist/entities/frame/lib/filter.js +62 -0
- package/dist/entities/frame/lib/filter.js.map +1 -0
- package/dist/entities/frame/lib/payload.d.ts +8 -0
- package/dist/entities/frame/lib/payload.d.ts.map +1 -0
- package/dist/entities/frame/lib/payload.js +42 -0
- package/dist/entities/frame/lib/payload.js.map +1 -0
- package/dist/entities/frame/lib/snapshot.d.ts +15 -0
- package/dist/entities/frame/lib/snapshot.d.ts.map +1 -0
- package/dist/entities/frame/lib/snapshot.js +90 -0
- package/dist/entities/frame/lib/snapshot.js.map +1 -0
- package/dist/entities/frame/lib/timeline.d.ts +23 -0
- package/dist/entities/frame/lib/timeline.d.ts.map +1 -0
- package/dist/entities/frame/lib/timeline.js +122 -0
- package/dist/entities/frame/lib/timeline.js.map +1 -0
- package/dist/entities/frame/model/frame.d.ts +182 -0
- package/dist/entities/frame/model/frame.d.ts.map +1 -0
- package/dist/entities/frame/model/frame.js +2 -0
- package/dist/entities/frame/model/frame.js.map +1 -0
- package/dist/entities/plugin/index.d.ts +9 -0
- package/dist/entities/plugin/index.d.ts.map +1 -0
- package/dist/entities/plugin/index.js +3 -0
- package/dist/entities/plugin/index.js.map +1 -0
- package/dist/entities/plugin/lib/define-plugin.d.ts +8 -0
- package/dist/entities/plugin/lib/define-plugin.d.ts.map +1 -0
- package/dist/entities/plugin/lib/define-plugin.js +9 -0
- package/dist/entities/plugin/lib/define-plugin.js.map +1 -0
- package/dist/entities/plugin/lib/named.d.ts +10 -0
- package/dist/entities/plugin/lib/named.d.ts.map +1 -0
- package/dist/entities/plugin/lib/named.js +17 -0
- package/dist/entities/plugin/lib/named.js.map +1 -0
- package/dist/entities/plugin/model/attachment.d.ts +18 -0
- package/dist/entities/plugin/model/attachment.d.ts.map +1 -0
- package/dist/entities/plugin/model/attachment.js +2 -0
- package/dist/entities/plugin/model/attachment.js.map +1 -0
- package/dist/entities/plugin/model/http.d.ts +126 -0
- package/dist/entities/plugin/model/http.d.ts.map +1 -0
- package/dist/entities/plugin/model/http.js +2 -0
- package/dist/entities/plugin/model/http.js.map +1 -0
- package/dist/entities/plugin/model/page.d.ts +100 -0
- package/dist/entities/plugin/model/page.d.ts.map +1 -0
- package/dist/entities/plugin/model/page.js +2 -0
- package/dist/entities/plugin/model/page.js.map +1 -0
- package/dist/entities/plugin/model/plugin.d.ts +83 -0
- package/dist/entities/plugin/model/plugin.d.ts.map +1 -0
- package/dist/entities/plugin/model/plugin.js +2 -0
- package/dist/entities/plugin/model/plugin.js.map +1 -0
- package/dist/entities/plugin/model/socket.d.ts +102 -0
- package/dist/entities/plugin/model/socket.d.ts.map +1 -0
- package/dist/entities/plugin/model/socket.js +2 -0
- package/dist/entities/plugin/model/socket.js.map +1 -0
- package/dist/entities/plugin/model/stream.d.ts +43 -0
- package/dist/entities/plugin/model/stream.d.ts.map +1 -0
- package/dist/entities/plugin/model/stream.js +2 -0
- package/dist/entities/plugin/model/stream.js.map +1 -0
- package/dist/features/connect/index.d.ts +5 -0
- package/dist/features/connect/index.d.ts.map +1 -0
- package/dist/features/connect/index.js +4 -0
- package/dist/features/connect/index.js.map +1 -0
- package/dist/features/connect/lib/calls.d.ts +9 -0
- package/dist/features/connect/lib/calls.d.ts.map +1 -0
- package/dist/features/connect/lib/calls.js +46 -0
- package/dist/features/connect/lib/calls.js.map +1 -0
- package/dist/features/connect/lib/client.d.ts +33 -0
- package/dist/features/connect/lib/client.d.ts.map +1 -0
- package/dist/features/connect/lib/client.js +147 -0
- package/dist/features/connect/lib/client.js.map +1 -0
- package/dist/features/connect/lib/create-connect.d.ts +14 -0
- package/dist/features/connect/lib/create-connect.d.ts.map +1 -0
- package/dist/features/connect/lib/create-connect.js +124 -0
- package/dist/features/connect/lib/create-connect.js.map +1 -0
- package/dist/features/connect/lib/faults.d.ts +18 -0
- package/dist/features/connect/lib/faults.d.ts.map +1 -0
- package/dist/features/connect/lib/faults.js +85 -0
- package/dist/features/connect/lib/faults.js.map +1 -0
- package/dist/features/connect/lib/protocol.d.ts +42 -0
- package/dist/features/connect/lib/protocol.d.ts.map +1 -0
- package/dist/features/connect/lib/protocol.js +228 -0
- package/dist/features/connect/lib/protocol.js.map +1 -0
- package/dist/features/connect/lib/record.d.ts +14 -0
- package/dist/features/connect/lib/record.d.ts.map +1 -0
- package/dist/features/connect/lib/record.js +36 -0
- package/dist/features/connect/lib/record.js.map +1 -0
- package/dist/features/connect/lib/recorder.d.ts +77 -0
- package/dist/features/connect/lib/recorder.d.ts.map +1 -0
- package/dist/features/connect/lib/recorder.js +209 -0
- package/dist/features/connect/lib/recorder.js.map +1 -0
- package/dist/features/connect/lib/rules.d.ts +51 -0
- package/dist/features/connect/lib/rules.d.ts.map +1 -0
- package/dist/features/connect/lib/rules.js +222 -0
- package/dist/features/connect/lib/rules.js.map +1 -0
- package/dist/features/connect/lib/serve.d.ts +10 -0
- package/dist/features/connect/lib/serve.d.ts.map +1 -0
- package/dist/features/connect/lib/serve.js +78 -0
- package/dist/features/connect/lib/serve.js.map +1 -0
- package/dist/features/connect/lib/status-of.d.ts +11 -0
- package/dist/features/connect/lib/status-of.d.ts.map +1 -0
- package/dist/features/connect/lib/status-of.js +22 -0
- package/dist/features/connect/lib/status-of.js.map +1 -0
- package/dist/features/connect/lib/stubs.d.ts +18 -0
- package/dist/features/connect/lib/stubs.d.ts.map +1 -0
- package/dist/features/connect/lib/stubs.js +174 -0
- package/dist/features/connect/lib/stubs.js.map +1 -0
- package/dist/features/connect/lib/universal.d.ts +19 -0
- package/dist/features/connect/lib/universal.d.ts.map +1 -0
- package/dist/features/connect/lib/universal.js +71 -0
- package/dist/features/connect/lib/universal.js.map +1 -0
- package/dist/features/connect/model/types.d.ts +247 -0
- package/dist/features/connect/model/types.d.ts.map +1 -0
- package/dist/features/connect/model/types.js +2 -0
- package/dist/features/connect/model/types.js.map +1 -0
- package/dist/features/rest/index.d.ts +5 -0
- package/dist/features/rest/index.d.ts.map +1 -0
- package/dist/features/rest/index.js +3 -0
- package/dist/features/rest/index.js.map +1 -0
- package/dist/features/rest/lib/calls.d.ts +8 -0
- package/dist/features/rest/lib/calls.d.ts.map +1 -0
- package/dist/features/rest/lib/calls.js +32 -0
- package/dist/features/rest/lib/calls.js.map +1 -0
- package/dist/features/rest/lib/create-rest.d.ts +8 -0
- package/dist/features/rest/lib/create-rest.d.ts.map +1 -0
- package/dist/features/rest/lib/create-rest.js +136 -0
- package/dist/features/rest/lib/create-rest.js.map +1 -0
- package/dist/features/rest/lib/path.d.ts +14 -0
- package/dist/features/rest/lib/path.d.ts.map +1 -0
- package/dist/features/rest/lib/path.js +58 -0
- package/dist/features/rest/lib/path.js.map +1 -0
- package/dist/features/rest/lib/reply.d.ts +25 -0
- package/dist/features/rest/lib/reply.d.ts.map +1 -0
- package/dist/features/rest/lib/reply.js +40 -0
- package/dist/features/rest/lib/reply.js.map +1 -0
- package/dist/features/rest/model/types.d.ts +74 -0
- package/dist/features/rest/model/types.d.ts.map +1 -0
- package/dist/features/rest/model/types.js +2 -0
- package/dist/features/rest/model/types.js.map +1 -0
- package/dist/features/sse/index.d.ts +4 -0
- package/dist/features/sse/index.d.ts.map +1 -0
- package/dist/features/sse/index.js +3 -0
- package/dist/features/sse/index.js.map +1 -0
- package/dist/features/sse/lib/create-sse.d.ts +9 -0
- package/dist/features/sse/lib/create-sse.d.ts.map +1 -0
- package/dist/features/sse/lib/create-sse.js +120 -0
- package/dist/features/sse/lib/create-sse.js.map +1 -0
- package/dist/features/sse/lib/encode.d.ts +8 -0
- package/dist/features/sse/lib/encode.d.ts.map +1 -0
- package/dist/features/sse/lib/encode.js +29 -0
- package/dist/features/sse/lib/encode.js.map +1 -0
- package/dist/features/sse/model/types.d.ts +62 -0
- package/dist/features/sse/model/types.d.ts.map +1 -0
- package/dist/features/sse/model/types.js +2 -0
- package/dist/features/sse/model/types.js.map +1 -0
- package/dist/features/webrtc/index.d.ts +3 -0
- package/dist/features/webrtc/index.d.ts.map +1 -0
- package/dist/features/webrtc/index.js +2 -0
- package/dist/features/webrtc/index.js.map +1 -0
- package/dist/features/webrtc/lib/create-webrtc.d.ts +9 -0
- package/dist/features/webrtc/lib/create-webrtc.d.ts.map +1 -0
- package/dist/features/webrtc/lib/create-webrtc.js +123 -0
- package/dist/features/webrtc/lib/create-webrtc.js.map +1 -0
- package/dist/features/webrtc/lib/rtc-page-module.d.ts +22 -0
- package/dist/features/webrtc/lib/rtc-page-module.d.ts.map +1 -0
- package/dist/features/webrtc/lib/rtc-page-module.js +161 -0
- package/dist/features/webrtc/lib/rtc-page-module.js.map +1 -0
- package/dist/features/webrtc/lib/to-frame.d.ts +9 -0
- package/dist/features/webrtc/lib/to-frame.d.ts.map +1 -0
- package/dist/features/webrtc/lib/to-frame.js +43 -0
- package/dist/features/webrtc/lib/to-frame.js.map +1 -0
- package/dist/features/webrtc/model/types.d.ts +70 -0
- package/dist/features/webrtc/model/types.d.ts.map +1 -0
- package/dist/features/webrtc/model/types.js +2 -0
- package/dist/features/webrtc/model/types.js.map +1 -0
- package/dist/features/websocket/index.d.ts +3 -0
- package/dist/features/websocket/index.d.ts.map +1 -0
- package/dist/features/websocket/index.js +2 -0
- package/dist/features/websocket/index.js.map +1 -0
- package/dist/features/websocket/lib/create-websocket.d.ts +9 -0
- package/dist/features/websocket/lib/create-websocket.d.ts.map +1 -0
- package/dist/features/websocket/lib/create-websocket.js +34 -0
- package/dist/features/websocket/lib/create-websocket.js.map +1 -0
- package/dist/features/websocket/lib/mock.d.ts +6 -0
- package/dist/features/websocket/lib/mock.d.ts.map +1 -0
- package/dist/features/websocket/lib/mock.js +96 -0
- package/dist/features/websocket/lib/mock.js.map +1 -0
- package/dist/features/websocket/lib/proxy.d.ts +9 -0
- package/dist/features/websocket/lib/proxy.d.ts.map +1 -0
- package/dist/features/websocket/lib/proxy.js +107 -0
- package/dist/features/websocket/lib/proxy.js.map +1 -0
- package/dist/features/websocket/lib/wire.d.ts +16 -0
- package/dist/features/websocket/lib/wire.d.ts.map +1 -0
- package/dist/features/websocket/lib/wire.js +46 -0
- package/dist/features/websocket/lib/wire.js.map +1 -0
- package/dist/features/websocket/model/types.d.ts +129 -0
- package/dist/features/websocket/model/types.d.ts.map +1 -0
- package/dist/features/websocket/model/types.js +2 -0
- package/dist/features/websocket/model/types.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/playwright.d.ts +4 -0
- package/dist/playwright.d.ts.map +1 -0
- package/dist/playwright.js +2 -0
- package/dist/playwright.js.map +1 -0
- package/dist/rest.d.ts +3 -0
- package/dist/rest.d.ts.map +1 -0
- package/dist/rest.js +3 -0
- package/dist/rest.js.map +1 -0
- package/dist/shared/api/http-server/index.d.ts +3 -0
- package/dist/shared/api/http-server/index.d.ts.map +1 -0
- package/dist/shared/api/http-server/index.js +2 -0
- package/dist/shared/api/http-server/index.js.map +1 -0
- package/dist/shared/api/http-server/listen.d.ts +25 -0
- package/dist/shared/api/http-server/listen.d.ts.map +1 -0
- package/dist/shared/api/http-server/listen.js +44 -0
- package/dist/shared/api/http-server/listen.js.map +1 -0
- package/dist/shared/api/playwright/index.d.ts +2 -0
- package/dist/shared/api/playwright/index.d.ts.map +1 -0
- package/dist/shared/api/playwright/index.js +2 -0
- package/dist/shared/api/playwright/index.js.map +1 -0
- package/dist/shared/api/playwright/ports.d.ts +117 -0
- package/dist/shared/api/playwright/ports.d.ts.map +1 -0
- package/dist/shared/api/playwright/ports.js +11 -0
- package/dist/shared/api/playwright/ports.js.map +1 -0
- package/dist/shared/api/stream-server/index.d.ts +3 -0
- package/dist/shared/api/stream-server/index.d.ts.map +1 -0
- package/dist/shared/api/stream-server/index.js +2 -0
- package/dist/shared/api/stream-server/index.js.map +1 -0
- package/dist/shared/api/stream-server/stream-server.d.ts +43 -0
- package/dist/shared/api/stream-server/stream-server.d.ts.map +1 -0
- package/dist/shared/api/stream-server/stream-server.js +160 -0
- package/dist/shared/api/stream-server/stream-server.js.map +1 -0
- package/dist/shared/lib/async/arrivals.d.ts +31 -0
- package/dist/shared/lib/async/arrivals.d.ts.map +1 -0
- package/dist/shared/lib/async/arrivals.js +57 -0
- package/dist/shared/lib/async/arrivals.js.map +1 -0
- package/dist/shared/lib/async/async-queue.d.ts +23 -0
- package/dist/shared/lib/async/async-queue.d.ts.map +1 -0
- package/dist/shared/lib/async/async-queue.js +80 -0
- package/dist/shared/lib/async/async-queue.js.map +1 -0
- package/dist/shared/lib/async/deferred.d.ts +12 -0
- package/dist/shared/lib/async/deferred.d.ts.map +1 -0
- package/dist/shared/lib/async/deferred.js +34 -0
- package/dist/shared/lib/async/deferred.js.map +1 -0
- package/dist/shared/lib/async/index.d.ts +8 -0
- package/dist/shared/lib/async/index.d.ts.map +1 -0
- package/dist/shared/lib/async/index.js +5 -0
- package/dist/shared/lib/async/index.js.map +1 -0
- package/dist/shared/lib/async/run-all.d.ts +8 -0
- package/dist/shared/lib/async/run-all.d.ts.map +1 -0
- package/dist/shared/lib/async/run-all.js +22 -0
- package/dist/shared/lib/async/run-all.js.map +1 -0
- package/dist/shared/lib/bytes/bytes.d.ts +18 -0
- package/dist/shared/lib/bytes/bytes.d.ts.map +1 -0
- package/dist/shared/lib/bytes/bytes.js +48 -0
- package/dist/shared/lib/bytes/bytes.js.map +1 -0
- package/dist/shared/lib/bytes/index.d.ts +3 -0
- package/dist/shared/lib/bytes/index.d.ts.map +1 -0
- package/dist/shared/lib/bytes/index.js +2 -0
- package/dist/shared/lib/bytes/index.js.map +1 -0
- package/dist/shared/lib/clock/clock.d.ts +29 -0
- package/dist/shared/lib/clock/clock.d.ts.map +1 -0
- package/dist/shared/lib/clock/clock.js +85 -0
- package/dist/shared/lib/clock/clock.js.map +1 -0
- package/dist/shared/lib/clock/index.d.ts +3 -0
- package/dist/shared/lib/clock/index.d.ts.map +1 -0
- package/dist/shared/lib/clock/index.js +2 -0
- package/dist/shared/lib/clock/index.js.map +1 -0
- package/dist/shared/lib/cookies/index.d.ts +3 -0
- package/dist/shared/lib/cookies/index.d.ts.map +1 -0
- package/dist/shared/lib/cookies/index.js +2 -0
- package/dist/shared/lib/cookies/index.js.map +1 -0
- package/dist/shared/lib/cookies/set-cookie.d.ts +22 -0
- package/dist/shared/lib/cookies/set-cookie.d.ts.map +1 -0
- package/dist/shared/lib/cookies/set-cookie.js +89 -0
- package/dist/shared/lib/cookies/set-cookie.js.map +1 -0
- package/dist/shared/lib/envelope/envelope.d.ts +50 -0
- package/dist/shared/lib/envelope/envelope.d.ts.map +1 -0
- package/dist/shared/lib/envelope/envelope.js +111 -0
- package/dist/shared/lib/envelope/envelope.js.map +1 -0
- package/dist/shared/lib/envelope/index.d.ts +3 -0
- package/dist/shared/lib/envelope/index.d.ts.map +1 -0
- package/dist/shared/lib/envelope/index.js +2 -0
- package/dist/shared/lib/envelope/index.js.map +1 -0
- package/dist/shared/lib/errors/index.d.ts +3 -0
- package/dist/shared/lib/errors/index.d.ts.map +1 -0
- package/dist/shared/lib/errors/index.js +2 -0
- package/dist/shared/lib/errors/index.js.map +1 -0
- package/dist/shared/lib/errors/wirestage-error.d.ts +28 -0
- package/dist/shared/lib/errors/wirestage-error.d.ts.map +1 -0
- package/dist/shared/lib/errors/wirestage-error.js +16 -0
- package/dist/shared/lib/errors/wirestage-error.js.map +1 -0
- package/dist/shared/lib/id/id.d.ts +10 -0
- package/dist/shared/lib/id/id.d.ts.map +1 -0
- package/dist/shared/lib/id/id.js +26 -0
- package/dist/shared/lib/id/id.js.map +1 -0
- package/dist/shared/lib/id/index.d.ts +2 -0
- package/dist/shared/lib/id/index.d.ts.map +1 -0
- package/dist/shared/lib/id/index.js +2 -0
- package/dist/shared/lib/id/index.js.map +1 -0
- package/dist/shared/lib/logger/index.d.ts +3 -0
- package/dist/shared/lib/logger/index.d.ts.map +1 -0
- package/dist/shared/lib/logger/index.js +2 -0
- package/dist/shared/lib/logger/index.js.map +1 -0
- package/dist/shared/lib/logger/logger.d.ts +46 -0
- package/dist/shared/lib/logger/logger.d.ts.map +1 -0
- package/dist/shared/lib/logger/logger.js +67 -0
- package/dist/shared/lib/logger/logger.js.map +1 -0
- package/dist/shared/lib/match/index.d.ts +3 -0
- package/dist/shared/lib/match/index.d.ts.map +1 -0
- package/dist/shared/lib/match/index.js +2 -0
- package/dist/shared/lib/match/index.js.map +1 -0
- package/dist/shared/lib/match/url-matcher.d.ts +27 -0
- package/dist/shared/lib/match/url-matcher.d.ts.map +1 -0
- package/dist/shared/lib/match/url-matcher.js +88 -0
- package/dist/shared/lib/match/url-matcher.js.map +1 -0
- package/dist/sse.d.ts +3 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +3 -0
- package/dist/sse.js.map +1 -0
- package/dist/webrtc.d.ts +3 -0
- package/dist/webrtc.d.ts.map +1 -0
- package/dist/webrtc.js +3 -0
- package/dist/webrtc.js.map +1 -0
- package/dist/websocket.d.ts +3 -0
- package/dist/websocket.d.ts.map +1 -0
- package/dist/websocket.js +3 -0
- package/dist/websocket.js.map +1 -0
- package/dist/widgets/stage/index.d.ts +3 -0
- package/dist/widgets/stage/index.d.ts.map +1 -0
- package/dist/widgets/stage/index.js +2 -0
- package/dist/widgets/stage/index.js.map +1 -0
- package/dist/widgets/stage/lib/attached-context.d.ts +10 -0
- package/dist/widgets/stage/lib/attached-context.d.ts.map +1 -0
- package/dist/widgets/stage/lib/attached-context.js +2 -0
- package/dist/widgets/stage/lib/attached-context.js.map +1 -0
- package/dist/widgets/stage/lib/create-stage.d.ts +12 -0
- package/dist/widgets/stage/lib/create-stage.d.ts.map +1 -0
- package/dist/widgets/stage/lib/create-stage.js +248 -0
- package/dist/widgets/stage/lib/create-stage.js.map +1 -0
- package/dist/widgets/stage/lib/http-router.d.ts +35 -0
- package/dist/widgets/stage/lib/http-router.d.ts.map +1 -0
- package/dist/widgets/stage/lib/http-router.js +455 -0
- package/dist/widgets/stage/lib/http-router.js.map +1 -0
- package/dist/widgets/stage/lib/page-host.d.ts +36 -0
- package/dist/widgets/stage/lib/page-host.d.ts.map +1 -0
- package/dist/widgets/stage/lib/page-host.js +240 -0
- package/dist/widgets/stage/lib/page-host.js.map +1 -0
- package/dist/widgets/stage/lib/page-runtime.d.ts +93 -0
- package/dist/widgets/stage/lib/page-runtime.d.ts.map +1 -0
- package/dist/widgets/stage/lib/page-runtime.js +247 -0
- package/dist/widgets/stage/lib/page-runtime.js.map +1 -0
- package/dist/widgets/stage/lib/reads.d.ts +21 -0
- package/dist/widgets/stage/lib/reads.d.ts.map +1 -0
- package/dist/widgets/stage/lib/reads.js +97 -0
- package/dist/widgets/stage/lib/reads.js.map +1 -0
- package/dist/widgets/stage/lib/response-stream.d.ts +9 -0
- package/dist/widgets/stage/lib/response-stream.d.ts.map +1 -0
- package/dist/widgets/stage/lib/response-stream.js +56 -0
- package/dist/widgets/stage/lib/response-stream.js.map +1 -0
- package/dist/widgets/stage/lib/socket-router.d.ts +25 -0
- package/dist/widgets/stage/lib/socket-router.d.ts.map +1 -0
- package/dist/widgets/stage/lib/socket-router.js +204 -0
- package/dist/widgets/stage/lib/socket-router.js.map +1 -0
- package/dist/widgets/stage/model/stage.d.ts +86 -0
- package/dist/widgets/stage/model/stage.d.ts.map +1 -0
- package/dist/widgets/stage/model/stage.js +2 -0
- package/dist/widgets/stage/model/stage.js.map +1 -0
- package/package.json +140 -5
- package/src/app/playwright/index.ts +7 -0
- package/src/app/playwright/lib/fixture.ts +201 -0
- package/src/connect.ts +6 -0
- package/src/entities/frame/@x/plugin.ts +8 -0
- package/src/entities/frame/index.ts +19 -0
- package/src/entities/frame/lib/filter.ts +58 -0
- package/src/entities/frame/lib/payload.ts +42 -0
- package/src/entities/frame/lib/snapshot.ts +105 -0
- package/src/entities/frame/lib/timeline.ts +147 -0
- package/src/entities/frame/model/frame.ts +191 -0
- package/src/entities/plugin/index.ts +36 -0
- package/src/entities/plugin/lib/define-plugin.ts +12 -0
- package/src/entities/plugin/lib/named.ts +21 -0
- package/src/entities/plugin/model/attachment.ts +18 -0
- package/src/entities/plugin/model/http.ts +147 -0
- package/src/entities/plugin/model/page.ts +110 -0
- package/src/entities/plugin/model/plugin.ts +96 -0
- package/src/entities/plugin/model/socket.ts +108 -0
- package/src/entities/plugin/model/stream.ts +43 -0
- package/src/features/connect/index.ts +23 -0
- package/src/features/connect/lib/calls.ts +58 -0
- package/src/features/connect/lib/client.ts +203 -0
- package/src/features/connect/lib/create-connect.ts +164 -0
- package/src/features/connect/lib/faults.ts +132 -0
- package/src/features/connect/lib/protocol.ts +321 -0
- package/src/features/connect/lib/record.ts +48 -0
- package/src/features/connect/lib/recorder.ts +306 -0
- package/src/features/connect/lib/rules.ts +385 -0
- package/src/features/connect/lib/serve.ts +114 -0
- package/src/features/connect/lib/status-of.ts +20 -0
- package/src/features/connect/lib/stubs.ts +282 -0
- package/src/features/connect/lib/universal.ts +100 -0
- package/src/features/connect/model/types.ts +305 -0
- package/src/features/rest/index.ts +23 -0
- package/src/features/rest/lib/calls.ts +31 -0
- package/src/features/rest/lib/create-rest.ts +169 -0
- package/src/features/rest/lib/path.ts +70 -0
- package/src/features/rest/lib/reply.ts +74 -0
- package/src/features/rest/model/types.ts +113 -0
- package/src/features/sse/index.ts +10 -0
- package/src/features/sse/lib/create-sse.ts +128 -0
- package/src/features/sse/lib/encode.ts +28 -0
- package/src/features/sse/model/types.ts +66 -0
- package/src/features/webrtc/index.ts +6 -0
- package/src/features/webrtc/lib/create-webrtc.ts +149 -0
- package/src/features/webrtc/lib/rtc-page-module.ts +189 -0
- package/src/features/webrtc/lib/to-frame.ts +63 -0
- package/src/features/webrtc/model/types.ts +80 -0
- package/src/features/websocket/index.ts +15 -0
- package/src/features/websocket/lib/create-websocket.ts +41 -0
- package/src/features/websocket/lib/mock.ts +114 -0
- package/src/features/websocket/lib/proxy.ts +141 -0
- package/src/features/websocket/lib/wire.ts +78 -0
- package/src/features/websocket/model/types.ts +130 -0
- package/src/index.ts +111 -0
- package/src/playwright.ts +8 -0
- package/src/rest.ts +2 -0
- package/src/shared/api/http-server/index.ts +2 -0
- package/src/shared/api/http-server/listen.ts +71 -0
- package/src/shared/api/playwright/index.ts +17 -0
- package/src/shared/api/playwright/ports.ts +145 -0
- package/src/shared/api/stream-server/index.ts +7 -0
- package/src/shared/api/stream-server/stream-server.ts +246 -0
- package/src/shared/lib/async/arrivals.ts +91 -0
- package/src/shared/lib/async/async-queue.ts +101 -0
- package/src/shared/lib/async/deferred.ts +38 -0
- package/src/shared/lib/async/index.ts +11 -0
- package/src/shared/lib/async/run-all.ts +19 -0
- package/src/shared/lib/bytes/bytes.ts +53 -0
- package/src/shared/lib/bytes/index.ts +9 -0
- package/src/shared/lib/clock/clock.ts +114 -0
- package/src/shared/lib/clock/index.ts +2 -0
- package/src/shared/lib/cookies/index.ts +2 -0
- package/src/shared/lib/cookies/set-cookie.ts +105 -0
- package/src/shared/lib/envelope/envelope.ts +133 -0
- package/src/shared/lib/envelope/index.ts +9 -0
- package/src/shared/lib/errors/index.ts +5 -0
- package/src/shared/lib/errors/wirestage-error.ts +50 -0
- package/src/shared/lib/id/id.ts +29 -0
- package/src/shared/lib/id/index.ts +1 -0
- package/src/shared/lib/logger/index.ts +12 -0
- package/src/shared/lib/logger/logger.ts +126 -0
- package/src/shared/lib/match/index.ts +6 -0
- package/src/shared/lib/match/url-matcher.ts +107 -0
- package/src/sse.ts +2 -0
- package/src/webrtc.ts +2 -0
- package/src/websocket.ts +2 -0
- package/src/widgets/stage/index.ts +9 -0
- package/src/widgets/stage/lib/attached-context.ts +10 -0
- package/src/widgets/stage/lib/create-stage.ts +305 -0
- package/src/widgets/stage/lib/http-router.ts +620 -0
- package/src/widgets/stage/lib/page-host.ts +375 -0
- package/src/widgets/stage/lib/page-runtime.ts +314 -0
- package/src/widgets/stage/lib/reads.ts +128 -0
- package/src/widgets/stage/lib/response-stream.ts +66 -0
- package/src/widgets/stage/lib/socket-router.ts +271 -0
- package/src/widgets/stage/model/stage.ts +108 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Frame } from "../../../entities/frame/index.js";
|
|
2
|
+
import type {
|
|
3
|
+
AbortErrorCode,
|
|
4
|
+
HttpRequestView,
|
|
5
|
+
} from "../../../entities/plugin/index.js";
|
|
6
|
+
import type { UrlMatcher } from "../../../shared/lib/match/index.js";
|
|
7
|
+
|
|
8
|
+
export type RestMethod =
|
|
9
|
+
| "GET"
|
|
10
|
+
| "POST"
|
|
11
|
+
| "PUT"
|
|
12
|
+
| "PATCH"
|
|
13
|
+
| "DELETE"
|
|
14
|
+
| "HEAD"
|
|
15
|
+
| "OPTIONS";
|
|
16
|
+
|
|
17
|
+
export interface RestOptions {
|
|
18
|
+
/** A path prefix (`/api/v1`) or an origin (`https://api.example.test`) routes are relative to. */
|
|
19
|
+
readonly baseURL?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Record requests that no route answers, with their real responses.
|
|
22
|
+
* `true` records everything under `baseURL`. A request a route does
|
|
23
|
+
* answer is recorded by that route, not twice.
|
|
24
|
+
*/
|
|
25
|
+
readonly record?: boolean | UrlMatcher;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RestRequest extends HttpRequestView {
|
|
29
|
+
/** `:name` segments of the matched path. */
|
|
30
|
+
readonly params: Readonly<Record<string, string>>;
|
|
31
|
+
readonly query: URLSearchParams;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** What a resolver answers. Build one with the helpers: `json`, `text`, `abort`, ... */
|
|
35
|
+
export type RestReply =
|
|
36
|
+
| {
|
|
37
|
+
readonly kind: "response";
|
|
38
|
+
readonly status: number;
|
|
39
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
40
|
+
readonly body?: Uint8Array | string;
|
|
41
|
+
}
|
|
42
|
+
| { readonly kind: "passthrough" }
|
|
43
|
+
| { readonly kind: "abort"; readonly error: AbortErrorCode }
|
|
44
|
+
| { readonly kind: "delay"; readonly ms: number; readonly reply: RestReply };
|
|
45
|
+
|
|
46
|
+
export type RestResolver =
|
|
47
|
+
| RestReply
|
|
48
|
+
| ((request: RestRequest) => RestReply | Promise<RestReply>);
|
|
49
|
+
|
|
50
|
+
export interface RestRouteOptions {
|
|
51
|
+
readonly times?: number;
|
|
52
|
+
readonly match?: (request: RestRequest) => boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RestRoute {
|
|
56
|
+
/** Requests this route answered. */
|
|
57
|
+
readonly hits: number;
|
|
58
|
+
readonly requests: readonly RestRequest[];
|
|
59
|
+
/** The next request this route answers (or answered and nobody took). */
|
|
60
|
+
next(options?: { readonly timeout?: number }): Promise<RestRequest>;
|
|
61
|
+
remove(): Promise<void>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface RestCall {
|
|
65
|
+
readonly correlation: string;
|
|
66
|
+
readonly method: string;
|
|
67
|
+
readonly url: string;
|
|
68
|
+
readonly request: Frame;
|
|
69
|
+
readonly response: Frame | undefined;
|
|
70
|
+
/** The HTTP status; undefined for an aborted or unfinished request. */
|
|
71
|
+
readonly status: number | undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type RestPath = string | RegExp;
|
|
75
|
+
|
|
76
|
+
export interface RestApi {
|
|
77
|
+
on(
|
|
78
|
+
method: RestMethod | "*",
|
|
79
|
+
path: RestPath,
|
|
80
|
+
resolver: RestResolver,
|
|
81
|
+
options?: RestRouteOptions,
|
|
82
|
+
): Promise<RestRoute>;
|
|
83
|
+
get(
|
|
84
|
+
path: RestPath,
|
|
85
|
+
resolver: RestResolver,
|
|
86
|
+
options?: RestRouteOptions,
|
|
87
|
+
): Promise<RestRoute>;
|
|
88
|
+
post(
|
|
89
|
+
path: RestPath,
|
|
90
|
+
resolver: RestResolver,
|
|
91
|
+
options?: RestRouteOptions,
|
|
92
|
+
): Promise<RestRoute>;
|
|
93
|
+
put(
|
|
94
|
+
path: RestPath,
|
|
95
|
+
resolver: RestResolver,
|
|
96
|
+
options?: RestRouteOptions,
|
|
97
|
+
): Promise<RestRoute>;
|
|
98
|
+
patch(
|
|
99
|
+
path: RestPath,
|
|
100
|
+
resolver: RestResolver,
|
|
101
|
+
options?: RestRouteOptions,
|
|
102
|
+
): Promise<RestRoute>;
|
|
103
|
+
delete(
|
|
104
|
+
path: RestPath,
|
|
105
|
+
resolver: RestResolver,
|
|
106
|
+
options?: RestRouteOptions,
|
|
107
|
+
): Promise<RestRoute>;
|
|
108
|
+
/** Recorded REST exchanges, oldest first. */
|
|
109
|
+
calls(filter?: {
|
|
110
|
+
readonly method?: string;
|
|
111
|
+
readonly path?: RestPath;
|
|
112
|
+
}): Promise<RestCall[]>;
|
|
113
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
definePlugin,
|
|
3
|
+
type WirePlugin,
|
|
4
|
+
} from "../../../entities/plugin/index.js";
|
|
5
|
+
import {
|
|
6
|
+
type Arrivals,
|
|
7
|
+
createArrivals,
|
|
8
|
+
} from "../../../shared/lib/async/index.js";
|
|
9
|
+
import type { SseApi, SseConnection, SseEvent } from "../model/types.js";
|
|
10
|
+
import { encodeSseEvent } from "./encode.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Server-sent events: answer an EventSource (or a fetch reading an event
|
|
14
|
+
* stream) with a response the test writes one event at a time, with clean
|
|
15
|
+
* ends and cuts that exercise the client's reconnect.
|
|
16
|
+
*/
|
|
17
|
+
export function sse(): WirePlugin<"sse", SseApi> {
|
|
18
|
+
return definePlugin({
|
|
19
|
+
name: "sse",
|
|
20
|
+
scope: "Sse",
|
|
21
|
+
setup(context) {
|
|
22
|
+
const { name } = context;
|
|
23
|
+
const api: SseApi = {
|
|
24
|
+
async stream(url, streamOptions = {}) {
|
|
25
|
+
/** Requests the stub answered: each should become a connection. */
|
|
26
|
+
let taken = 0;
|
|
27
|
+
const connections: Arrivals<SseConnection> = createArrivals({
|
|
28
|
+
clock: context.clock,
|
|
29
|
+
label: "SSE connection",
|
|
30
|
+
// The stub answered, yet no connection came of it: the
|
|
31
|
+
// redirect to the stream server is what went missing.
|
|
32
|
+
hint: (): string | undefined =>
|
|
33
|
+
taken > connections.items.length
|
|
34
|
+
? `${taken - connections.items.length} request(s) matched but never reached the local stream server — see "Live streams" in docs/troubleshooting.md`
|
|
35
|
+
: undefined,
|
|
36
|
+
});
|
|
37
|
+
const rule = await context.http.add({
|
|
38
|
+
protocol: name,
|
|
39
|
+
label: `sse ${String(url)}`,
|
|
40
|
+
url,
|
|
41
|
+
...(streamOptions.times === undefined
|
|
42
|
+
? {}
|
|
43
|
+
: { times: streamOptions.times }),
|
|
44
|
+
...(streamOptions.match === undefined
|
|
45
|
+
? {}
|
|
46
|
+
: { match: streamOptions.match }),
|
|
47
|
+
handle(view) {
|
|
48
|
+
taken += 1;
|
|
49
|
+
return {
|
|
50
|
+
type: "stream",
|
|
51
|
+
open(stream) {
|
|
52
|
+
stream.head(streamOptions.status ?? 200, {
|
|
53
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
54
|
+
"cache-control": "no-cache",
|
|
55
|
+
connection: "keep-alive",
|
|
56
|
+
...streamOptions.headers,
|
|
57
|
+
});
|
|
58
|
+
// An EventSource opens on the headers, before any event.
|
|
59
|
+
stream.flush();
|
|
60
|
+
let sent = 0;
|
|
61
|
+
const connection: SseConnection = {
|
|
62
|
+
request: view,
|
|
63
|
+
lastEventId: stream.requestHeaders["last-event-id"],
|
|
64
|
+
get sent() {
|
|
65
|
+
return sent;
|
|
66
|
+
},
|
|
67
|
+
async send(event: SseEvent | string) {
|
|
68
|
+
const chunk = encodeSseEvent(event);
|
|
69
|
+
await stream.write(chunk);
|
|
70
|
+
sent += 1;
|
|
71
|
+
context.timeline.append({
|
|
72
|
+
protocol: name,
|
|
73
|
+
kind: "event",
|
|
74
|
+
direction: "in",
|
|
75
|
+
endpoint: view.url.pathname,
|
|
76
|
+
source: "stub",
|
|
77
|
+
data: chunk,
|
|
78
|
+
value: event,
|
|
79
|
+
correlation: view.correlation,
|
|
80
|
+
context: view.context,
|
|
81
|
+
...(view.page === undefined ? {} : { page: view.page }),
|
|
82
|
+
});
|
|
83
|
+
},
|
|
84
|
+
end: () => stream.end(),
|
|
85
|
+
async cut() {
|
|
86
|
+
stream.cut();
|
|
87
|
+
},
|
|
88
|
+
closed: stream.closed,
|
|
89
|
+
};
|
|
90
|
+
if (streamOptions.retry !== undefined)
|
|
91
|
+
void stream
|
|
92
|
+
.write(`retry: ${streamOptions.retry}\n\n`)
|
|
93
|
+
.catch(() => {});
|
|
94
|
+
connections.push(connection);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
get hits() {
|
|
101
|
+
return rule.hits;
|
|
102
|
+
},
|
|
103
|
+
next: (waitOptions) => connections.next(waitOptions),
|
|
104
|
+
get connections() {
|
|
105
|
+
return connections.items;
|
|
106
|
+
},
|
|
107
|
+
async broadcast(event) {
|
|
108
|
+
for (const connection of connections.items)
|
|
109
|
+
await connection.send(event).catch(() => {});
|
|
110
|
+
},
|
|
111
|
+
remove: () => rule.remove(),
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
async events(filter = {}) {
|
|
115
|
+
await context.sync();
|
|
116
|
+
return context.timeline.frames({
|
|
117
|
+
protocol: name,
|
|
118
|
+
kind: "event",
|
|
119
|
+
...(filter.endpoint === undefined
|
|
120
|
+
? {}
|
|
121
|
+
: { endpoint: filter.endpoint }),
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
return { api };
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SseEvent } from "../model/types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encode one event in the text/event-stream format. Multi-line data becomes
|
|
5
|
+
* several `data:` lines — a single line with an embedded newline would end
|
|
6
|
+
* the field early and the client would see half the payload.
|
|
7
|
+
*/
|
|
8
|
+
export function encodeSseEvent(event: SseEvent | string): string {
|
|
9
|
+
if (typeof event === "string") return `${dataLines(event)}\n`;
|
|
10
|
+
let out = "";
|
|
11
|
+
if (event.comment !== undefined)
|
|
12
|
+
for (const line of event.comment.split(/\r?\n/)) out += `: ${line}\n`;
|
|
13
|
+
if (event.event !== undefined) out += `event: ${event.event}\n`;
|
|
14
|
+
if (event.id !== undefined) out += `id: ${event.id}\n`;
|
|
15
|
+
if (event.retry !== undefined) out += `retry: ${event.retry}\n`;
|
|
16
|
+
if (event.data !== undefined)
|
|
17
|
+
out += dataLines(
|
|
18
|
+
typeof event.data === "string" ? event.data : JSON.stringify(event.data),
|
|
19
|
+
);
|
|
20
|
+
return `${out}\n`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function dataLines(text: string): string {
|
|
24
|
+
return text
|
|
25
|
+
.split(/\r?\n/)
|
|
26
|
+
.map((line) => `data: ${line}\n`)
|
|
27
|
+
.join("");
|
|
28
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Frame } from "../../../entities/frame/index.js";
|
|
2
|
+
import type {
|
|
3
|
+
HttpRequestView,
|
|
4
|
+
StreamEnd,
|
|
5
|
+
} from "../../../entities/plugin/index.js";
|
|
6
|
+
import type { UrlMatcher } from "../../../shared/lib/match/index.js";
|
|
7
|
+
|
|
8
|
+
/** One server-sent event. A non-string `data` is sent as JSON. */
|
|
9
|
+
export interface SseEvent {
|
|
10
|
+
readonly data?: unknown;
|
|
11
|
+
readonly event?: string;
|
|
12
|
+
readonly id?: string;
|
|
13
|
+
/** Reconnection delay the client should use, in milliseconds. */
|
|
14
|
+
readonly retry?: number;
|
|
15
|
+
/** A `:` comment line, e.g. a keep-alive. */
|
|
16
|
+
readonly comment?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** How a connection ended. `cancelled` means the page closed it. */
|
|
20
|
+
export type SseEnd = StreamEnd;
|
|
21
|
+
|
|
22
|
+
export interface SseConnection {
|
|
23
|
+
readonly request: HttpRequestView;
|
|
24
|
+
/**
|
|
25
|
+
* The `Last-Event-ID` the client reconnected with. Read off the request the
|
|
26
|
+
* stream server received: the browser's network stack adds it after
|
|
27
|
+
* Playwright's route has seen the request, so `request.headers` lacks it.
|
|
28
|
+
*/
|
|
29
|
+
readonly lastEventId: string | undefined;
|
|
30
|
+
/** Events written so far. */
|
|
31
|
+
readonly sent: number;
|
|
32
|
+
/** Write an event. A string is sent as `data`. Resolves once written. */
|
|
33
|
+
send(event: SseEvent | string): Promise<void>;
|
|
34
|
+
/** Close cleanly; EventSource will reconnect after its retry delay. */
|
|
35
|
+
end(): Promise<void>;
|
|
36
|
+
/** Reset the connection, as a network drop would. */
|
|
37
|
+
cut(): Promise<void>;
|
|
38
|
+
readonly closed: Promise<SseEnd>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SseStreamOptions {
|
|
42
|
+
readonly times?: number;
|
|
43
|
+
readonly status?: number;
|
|
44
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
45
|
+
/** Sent before anything else: the client's reconnection delay. */
|
|
46
|
+
readonly retry?: number;
|
|
47
|
+
readonly match?: (request: HttpRequestView) => boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SseStub {
|
|
51
|
+
/** Requests this stub answered. */
|
|
52
|
+
readonly hits: number;
|
|
53
|
+
/** The next connection the page opens (or opened and nobody took). */
|
|
54
|
+
next(options?: { readonly timeout?: number }): Promise<SseConnection>;
|
|
55
|
+
readonly connections: readonly SseConnection[];
|
|
56
|
+
/** Send to every open connection. */
|
|
57
|
+
broadcast(event: SseEvent | string): Promise<void>;
|
|
58
|
+
remove(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SseApi {
|
|
62
|
+
/** Answer matching requests with an event stream the test drives. */
|
|
63
|
+
stream(url: UrlMatcher, options?: SseStreamOptions): Promise<SseStub>;
|
|
64
|
+
/** Events sent so far, oldest first. */
|
|
65
|
+
events(filter?: { readonly endpoint?: string | RegExp }): Promise<Frame[]>;
|
|
66
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
definePlugin,
|
|
3
|
+
type WirePlugin,
|
|
4
|
+
} from "../../../entities/plugin/index.js";
|
|
5
|
+
import { toBase64, toBytes } from "../../../shared/lib/bytes/index.js";
|
|
6
|
+
import { WirestageError } from "../../../shared/lib/errors/index.js";
|
|
7
|
+
import type {
|
|
8
|
+
RtcInjectArgs,
|
|
9
|
+
RtcPageConfig,
|
|
10
|
+
RtcPageState,
|
|
11
|
+
WebRtcApi,
|
|
12
|
+
WebRtcOptions,
|
|
13
|
+
} from "../model/types.js";
|
|
14
|
+
import { installWebRtc } from "./rtc-page-module.js";
|
|
15
|
+
import { toFrame } from "./to-frame.js";
|
|
16
|
+
|
|
17
|
+
/** The calls in force on each label; a label with none is not suppressed. */
|
|
18
|
+
type Suppressions = Map<string, Set<symbol>>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* WebRTC data channels: record what the page sends and receives, drop
|
|
22
|
+
* messages on one channel while it stays open, and inject messages as the
|
|
23
|
+
* remote peer.
|
|
24
|
+
*/
|
|
25
|
+
export function webrtc(
|
|
26
|
+
options: WebRtcOptions = {},
|
|
27
|
+
): WirePlugin<"webrtc", WebRtcApi> {
|
|
28
|
+
return definePlugin({
|
|
29
|
+
name: "webrtc",
|
|
30
|
+
scope: "Rtc",
|
|
31
|
+
setup(context) {
|
|
32
|
+
const { name } = context;
|
|
33
|
+
const suppressIn: Suppressions = new Map();
|
|
34
|
+
const blockOut: Suppressions = new Map();
|
|
35
|
+
const directions = options.record ?? ["in", "out"];
|
|
36
|
+
const config: RtcPageConfig = {
|
|
37
|
+
labels: options.labels ?? null,
|
|
38
|
+
recordIn: directions.includes("in"),
|
|
39
|
+
recordOut: directions.includes("out"),
|
|
40
|
+
lifecycle: options.lifecycle ?? true,
|
|
41
|
+
};
|
|
42
|
+
context.pages.register<RtcPageState, RtcPageConfig>({
|
|
43
|
+
id: name,
|
|
44
|
+
install: installWebRtc,
|
|
45
|
+
config,
|
|
46
|
+
state: () => ({
|
|
47
|
+
suppressIn: [...suppressIn.keys()],
|
|
48
|
+
blockOut: [...blockOut.keys()],
|
|
49
|
+
}),
|
|
50
|
+
receive(entries, origin) {
|
|
51
|
+
for (const entry of entries)
|
|
52
|
+
context.timeline.append(toFrame(name, entry, origin));
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const refresh = () => context.pages.refresh(name);
|
|
56
|
+
const hold = (held: Suppressions, label: string, token: symbol) => {
|
|
57
|
+
const tokens = held.get(label) ?? new Set();
|
|
58
|
+
tokens.add(token);
|
|
59
|
+
held.set(label, tokens);
|
|
60
|
+
};
|
|
61
|
+
const release = (held: Suppressions, label: string, token: symbol) => {
|
|
62
|
+
const tokens = held.get(label);
|
|
63
|
+
// Gone already when `restore` cleared the label first.
|
|
64
|
+
if (tokens === undefined) return;
|
|
65
|
+
tokens.delete(token);
|
|
66
|
+
if (tokens.size === 0) held.delete(label);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const api: WebRtcApi = {
|
|
70
|
+
async frames(filter = {}) {
|
|
71
|
+
await context.sync();
|
|
72
|
+
return context.timeline.frames({ ...filter, protocol: name });
|
|
73
|
+
},
|
|
74
|
+
async messages(label, messageOptions = {}) {
|
|
75
|
+
await context.sync();
|
|
76
|
+
return context.timeline.frames({
|
|
77
|
+
protocol: name,
|
|
78
|
+
kind: "message",
|
|
79
|
+
endpoint: label,
|
|
80
|
+
...(messageOptions.direction === undefined
|
|
81
|
+
? {}
|
|
82
|
+
: { direction: messageOptions.direction }),
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
async suppress(label, suppressOptions = {}) {
|
|
86
|
+
const direction = suppressOptions.direction ?? "in";
|
|
87
|
+
const token = Symbol(label);
|
|
88
|
+
if (direction !== "out") hold(suppressIn, label, token);
|
|
89
|
+
if (direction !== "in") hold(blockOut, label, token);
|
|
90
|
+
await refresh();
|
|
91
|
+
return async () => {
|
|
92
|
+
release(suppressIn, label, token);
|
|
93
|
+
release(blockOut, label, token);
|
|
94
|
+
await refresh();
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
async restore(label) {
|
|
98
|
+
if (label === undefined) {
|
|
99
|
+
suppressIn.clear();
|
|
100
|
+
blockOut.clear();
|
|
101
|
+
} else {
|
|
102
|
+
suppressIn.delete(label);
|
|
103
|
+
blockOut.delete(label);
|
|
104
|
+
}
|
|
105
|
+
await refresh();
|
|
106
|
+
},
|
|
107
|
+
async inject(label, data, injectOptions = {}) {
|
|
108
|
+
const args: RtcInjectArgs =
|
|
109
|
+
typeof data === "string"
|
|
110
|
+
? { label, text: data }
|
|
111
|
+
: { label, data: toBase64(toBytes(data)) };
|
|
112
|
+
const results = await context.pages.command(
|
|
113
|
+
name,
|
|
114
|
+
"inject",
|
|
115
|
+
args,
|
|
116
|
+
injectOptions.page === undefined
|
|
117
|
+
? {}
|
|
118
|
+
: { page: context.pages.pageId(injectOptions.page) },
|
|
119
|
+
);
|
|
120
|
+
return results.reduce<number>(
|
|
121
|
+
(sum, delivered) => sum + Number(delivered),
|
|
122
|
+
0,
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
async waitForChannel(label, waitOptions = {}) {
|
|
126
|
+
if (!config.lifecycle)
|
|
127
|
+
// It would only ever time out: there is no open frame to wait for.
|
|
128
|
+
throw new WirestageError(
|
|
129
|
+
"INVALID_STATE",
|
|
130
|
+
`waitForChannel("${label}") needs lifecycle frames, and this plugin was created with \`lifecycle: false\``,
|
|
131
|
+
);
|
|
132
|
+
const [frame] = await context.waitFor(
|
|
133
|
+
{
|
|
134
|
+
protocol: name,
|
|
135
|
+
kind: "lifecycle",
|
|
136
|
+
endpoint: label,
|
|
137
|
+
where: (f) => f.meta["event"] === "open",
|
|
138
|
+
},
|
|
139
|
+
waitOptions.timeout === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: { timeout: waitOptions.timeout },
|
|
142
|
+
);
|
|
143
|
+
return frame as NonNullable<typeof frame>;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
return { api };
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { PageModuleEnv } from "../../../entities/plugin/index.js";
|
|
2
|
+
import type {
|
|
3
|
+
RtcInjectArgs,
|
|
4
|
+
RtcPageConfig,
|
|
5
|
+
RtcPageState,
|
|
6
|
+
} from "../model/types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Runs INSIDE the page (serialized): self-contained, `function` declaration.
|
|
10
|
+
*
|
|
11
|
+
* Three hooks, each placed so the page's own code is untouched:
|
|
12
|
+
*
|
|
13
|
+
* - `RTCDataChannel.prototype.send` is wrapped, so every send on every channel
|
|
14
|
+
* is seen as it leaves — the last point the page and a real peer agree on.
|
|
15
|
+
* - `createDataChannel` is wrapped and the constructor is replaced by one that
|
|
16
|
+
* adds a `datachannel` listener first, so every channel — page-created or
|
|
17
|
+
* remote-created — gets OUR `message` listener before the page's. Only a
|
|
18
|
+
* first listener can `stopImmediatePropagation()` the rest, which is what
|
|
19
|
+
* suppression needs; wrapping `onmessage` instead would race the page's own
|
|
20
|
+
* assignment of it.
|
|
21
|
+
* - Injected messages are dispatched as real `MessageEvent`s on the channel,
|
|
22
|
+
* so the page's handlers cannot tell them from the peer's.
|
|
23
|
+
*
|
|
24
|
+
* All three come off again when the stage detaches.
|
|
25
|
+
*/
|
|
26
|
+
export function installWebRtc(
|
|
27
|
+
env: PageModuleEnv<RtcPageState, RtcPageConfig>,
|
|
28
|
+
): void {
|
|
29
|
+
const win = env.window;
|
|
30
|
+
const PeerConnection = win.RTCPeerConnection;
|
|
31
|
+
const DataChannel = win.RTCDataChannel;
|
|
32
|
+
if (typeof PeerConnection !== "function" || typeof DataChannel !== "function")
|
|
33
|
+
return;
|
|
34
|
+
const config = env.config;
|
|
35
|
+
let suppressIn = new Set<string>();
|
|
36
|
+
let blockOut = new Set<string>();
|
|
37
|
+
const open = new Map<string, Set<RTCDataChannel>>();
|
|
38
|
+
const watched = new WeakSet<RTCDataChannel>();
|
|
39
|
+
const injected = new WeakSet<Event>();
|
|
40
|
+
/** Listeners added to the page's channels, to take off on detach. */
|
|
41
|
+
const unwatch: (() => void)[] = [];
|
|
42
|
+
|
|
43
|
+
const wanted = (label: string) =>
|
|
44
|
+
config.labels === null || config.labels.includes(label);
|
|
45
|
+
|
|
46
|
+
const emit = (
|
|
47
|
+
type: string,
|
|
48
|
+
label: string,
|
|
49
|
+
data: unknown,
|
|
50
|
+
extra: Record<string, unknown> = {},
|
|
51
|
+
) => {
|
|
52
|
+
const meta: Record<string, unknown> = { label, ...extra };
|
|
53
|
+
let payload: { data?: string; text?: string } = {};
|
|
54
|
+
if (typeof data === "string") payload = { text: data };
|
|
55
|
+
else if (data instanceof ArrayBuffer)
|
|
56
|
+
payload = { data: env.encode(new Uint8Array(data)) };
|
|
57
|
+
else if (ArrayBuffer.isView(data))
|
|
58
|
+
payload = {
|
|
59
|
+
data: env.encode(
|
|
60
|
+
new Uint8Array(data.buffer, data.byteOffset, data.byteLength),
|
|
61
|
+
),
|
|
62
|
+
};
|
|
63
|
+
// A Blob would need an async read, which would put it out of order with
|
|
64
|
+
// the frames around it; it is recorded as skipped instead.
|
|
65
|
+
else if (data !== undefined) meta["skipped"] = "blob";
|
|
66
|
+
env.emit({ type, at: env.now(), ...payload, meta });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const watch = (channel: RTCDataChannel) => {
|
|
70
|
+
if (watched.has(channel)) return;
|
|
71
|
+
watched.add(channel);
|
|
72
|
+
const label = channel.label;
|
|
73
|
+
const track = () => {
|
|
74
|
+
let set = open.get(label);
|
|
75
|
+
if (set === undefined) {
|
|
76
|
+
set = new Set();
|
|
77
|
+
open.set(label, set);
|
|
78
|
+
}
|
|
79
|
+
set.add(channel);
|
|
80
|
+
if (config.lifecycle && wanted(label))
|
|
81
|
+
emit("open", label, undefined, { id: channel.id });
|
|
82
|
+
};
|
|
83
|
+
const untrack = () => {
|
|
84
|
+
open.get(label)?.delete(channel);
|
|
85
|
+
if (config.lifecycle && wanted(label))
|
|
86
|
+
emit("close", label, undefined, { id: channel.id });
|
|
87
|
+
};
|
|
88
|
+
const onMessage = (event: MessageEvent) => {
|
|
89
|
+
const mine = injected.has(event);
|
|
90
|
+
// What the test injects is the point of suppressing the peer:
|
|
91
|
+
// silence the real one, speak in its place.
|
|
92
|
+
if (suppressIn.has(label) && !mine) {
|
|
93
|
+
event.stopImmediatePropagation();
|
|
94
|
+
if (wanted(label)) emit("suppressed", label, event.data);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (config.recordIn && wanted(label))
|
|
98
|
+
emit("in", label, event.data, mine ? { injected: true } : {});
|
|
99
|
+
};
|
|
100
|
+
if (channel.readyState === "open") track();
|
|
101
|
+
else channel.addEventListener("open", track, { once: true });
|
|
102
|
+
channel.addEventListener("close", untrack, { once: true });
|
|
103
|
+
channel.addEventListener("message", onMessage);
|
|
104
|
+
unwatch.push(() => {
|
|
105
|
+
channel.removeEventListener("open", track);
|
|
106
|
+
channel.removeEventListener("close", untrack);
|
|
107
|
+
channel.removeEventListener("message", onMessage);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const send = DataChannel.prototype.send;
|
|
112
|
+
DataChannel.prototype.send = function wirestageSend(
|
|
113
|
+
this: RTCDataChannel,
|
|
114
|
+
data: never,
|
|
115
|
+
): void {
|
|
116
|
+
const label = this.label;
|
|
117
|
+
if (blockOut.has(label)) {
|
|
118
|
+
if (wanted(label)) emit("blocked", label, data);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// Recorded once it has left: a send the browser refuses (a channel not
|
|
122
|
+
// open yet) throws here, and must not land on the timeline as sent.
|
|
123
|
+
send.call(this, data);
|
|
124
|
+
if (config.recordOut && wanted(label)) emit("out", label, data);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const createDataChannel = PeerConnection.prototype.createDataChannel;
|
|
128
|
+
PeerConnection.prototype.createDataChannel = function wirestageCreate(
|
|
129
|
+
this: RTCPeerConnection,
|
|
130
|
+
label: string,
|
|
131
|
+
init?: RTCDataChannelInit,
|
|
132
|
+
): RTCDataChannel {
|
|
133
|
+
const channel = createDataChannel.call(this, label, init);
|
|
134
|
+
watch(channel);
|
|
135
|
+
return channel;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
function WirestagePeerConnection(
|
|
139
|
+
...args: ConstructorParameters<typeof RTCPeerConnection>
|
|
140
|
+
): RTCPeerConnection {
|
|
141
|
+
// `new.target`, so a page's own subclass still gets its prototype —
|
|
142
|
+
// and, as with the real constructor, calling it without `new` throws.
|
|
143
|
+
const connection: RTCPeerConnection = Reflect.construct(
|
|
144
|
+
PeerConnection,
|
|
145
|
+
args,
|
|
146
|
+
new.target as never,
|
|
147
|
+
);
|
|
148
|
+
connection.addEventListener("datachannel", (event: RTCDataChannelEvent) =>
|
|
149
|
+
watch(event.channel),
|
|
150
|
+
);
|
|
151
|
+
return connection;
|
|
152
|
+
}
|
|
153
|
+
// Same prototype, so `instanceof RTCPeerConnection` and the patched
|
|
154
|
+
// methods above keep working; statics such as generateCertificate come
|
|
155
|
+
// through the prototype chain of the function itself.
|
|
156
|
+
WirestagePeerConnection.prototype = PeerConnection.prototype;
|
|
157
|
+
Object.setPrototypeOf(WirestagePeerConnection, PeerConnection);
|
|
158
|
+
win.RTCPeerConnection =
|
|
159
|
+
WirestagePeerConnection as unknown as typeof RTCPeerConnection;
|
|
160
|
+
|
|
161
|
+
env.onState((state) => {
|
|
162
|
+
suppressIn = new Set(state.suppressIn);
|
|
163
|
+
blockOut = new Set(state.blockOut);
|
|
164
|
+
});
|
|
165
|
+
env.onCommand("inject", (args: RtcInjectArgs) => {
|
|
166
|
+
let delivered = 0;
|
|
167
|
+
for (const channel of open.get(args.label) ?? []) {
|
|
168
|
+
if (channel.readyState !== "open") continue;
|
|
169
|
+
let data: unknown = args.text;
|
|
170
|
+
if (data === undefined) {
|
|
171
|
+
const buffer = env.decode(args.data ?? "").buffer as ArrayBuffer;
|
|
172
|
+
data = channel.binaryType === "blob" ? new win.Blob([buffer]) : buffer;
|
|
173
|
+
}
|
|
174
|
+
const event = new win.MessageEvent("message", { data });
|
|
175
|
+
injected.add(event);
|
|
176
|
+
channel.dispatchEvent(event);
|
|
177
|
+
delivered += 1;
|
|
178
|
+
}
|
|
179
|
+
return delivered;
|
|
180
|
+
});
|
|
181
|
+
env.onDetach(() => {
|
|
182
|
+
DataChannel.prototype.send = send;
|
|
183
|
+
PeerConnection.prototype.createDataChannel = createDataChannel;
|
|
184
|
+
win.RTCPeerConnection = PeerConnection;
|
|
185
|
+
for (const undo of unwatch.splice(0)) undo();
|
|
186
|
+
suppressIn = new Set();
|
|
187
|
+
blockOut = new Set();
|
|
188
|
+
});
|
|
189
|
+
}
|