twd-relay 0.2.1 → 1.0.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/index.d.ts CHANGED
@@ -28,6 +28,7 @@ export declare type InboundMessage = HelloMessage | Command | BrowserEvent;
28
28
  export declare interface RunCommand {
29
29
  type: 'run';
30
30
  scope: 'all';
31
+ testNames?: string[];
31
32
  }
32
33
 
33
34
  export declare interface RunCompleteEvent {
@@ -78,7 +79,7 @@ export declare interface TestStartEvent {
78
79
  suite: string;
79
80
  }
80
81
 
81
- export declare type TwdErrorCode = 'NO_BROWSER' | 'RUN_IN_PROGRESS' | 'UNKNOWN_COMMAND' | 'INVALID_MESSAGE';
82
+ export declare type TwdErrorCode = 'NO_BROWSER' | 'RUN_IN_PROGRESS' | 'UNKNOWN_COMMAND' | 'INVALID_MESSAGE' | 'NO_MATCH';
82
83
 
83
84
  export declare interface TwdErrorMessage {
84
85
  type: 'error';
package/dist/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { c as r } from "./createTwdRelay-22etCW_8.js";
1
+ import { t as r } from "./relay-C8UTSEbG.js";
2
2
  export {
3
3
  r as createTwdRelay
4
4
  };
@@ -0,0 +1,132 @@
1
+ import { WebSocket as s, WebSocketServer as I } from "ws";
2
+ var P = "/__twd/ws";
3
+ function _(y, p) {
4
+ const g = p?.path ?? P, f = p?.onError, u = new I({ noServer: !0 });
5
+ let t = null;
6
+ const a = /* @__PURE__ */ new Set();
7
+ let c = !1;
8
+ function i(e, n, r) {
9
+ const o = {
10
+ type: "error",
11
+ code: n,
12
+ message: r
13
+ };
14
+ e.readyState === s.OPEN && e.send(JSON.stringify(o));
15
+ }
16
+ function S(e) {
17
+ for (const n of a) n.readyState === s.OPEN && n.send(e);
18
+ }
19
+ function O(e) {
20
+ const n = {
21
+ type: "connected",
22
+ browser: t !== null && t.readyState === s.OPEN
23
+ };
24
+ e.readyState === s.OPEN && e.send(JSON.stringify(n));
25
+ }
26
+ function E() {
27
+ S(JSON.stringify({
28
+ type: "connected",
29
+ browser: !1
30
+ }));
31
+ }
32
+ function h(e) {
33
+ let n;
34
+ try {
35
+ n = JSON.parse(e);
36
+ } catch {
37
+ return;
38
+ }
39
+ n.type === "run:complete" && (c = !1), S(e);
40
+ }
41
+ function b(e, n) {
42
+ let r;
43
+ try {
44
+ r = JSON.parse(n);
45
+ } catch {
46
+ i(e, "INVALID_MESSAGE", "Invalid JSON");
47
+ return;
48
+ }
49
+ if (!r.type) {
50
+ i(e, "INVALID_MESSAGE", 'Missing "type" field');
51
+ return;
52
+ }
53
+ if (r.type === "run") {
54
+ if (!t || t.readyState !== s.OPEN) {
55
+ i(e, "NO_BROWSER", "No browser connected");
56
+ return;
57
+ }
58
+ if (c) {
59
+ i(e, "RUN_IN_PROGRESS", "A test run is already in progress");
60
+ return;
61
+ }
62
+ c = !0, t.send(n);
63
+ return;
64
+ }
65
+ if (r.type === "status") {
66
+ if (!t || t.readyState !== s.OPEN) {
67
+ i(e, "NO_BROWSER", "No browser connected");
68
+ return;
69
+ }
70
+ t.send(n);
71
+ return;
72
+ }
73
+ i(e, "UNKNOWN_COMMAND", `Unknown command: ${r.type}`);
74
+ }
75
+ function m(e) {
76
+ let n = !1;
77
+ const r = (o) => {
78
+ const A = typeof o == "string" ? o : o.toString();
79
+ if (!n) {
80
+ let d;
81
+ try {
82
+ d = JSON.parse(A);
83
+ } catch {
84
+ i(e, "INVALID_MESSAGE", "Invalid JSON");
85
+ return;
86
+ }
87
+ if (d.type !== "hello" || d.role !== "browser" && d.role !== "client") {
88
+ i(e, "INVALID_MESSAGE", 'First message must be a hello with role "browser" or "client"');
89
+ return;
90
+ }
91
+ n = !0, d.role === "browser" ? (t && t.readyState === s.OPEN && t.close(1e3, "Replaced by new browser"), t = e, c = !1, e.on("close", () => {
92
+ t === e && (t = null, c = !1, E());
93
+ }), S(JSON.stringify({
94
+ type: "connected",
95
+ browser: !0
96
+ })), e.on("message", (l) => {
97
+ h(typeof l == "string" ? l : l.toString());
98
+ })) : (a.add(e), e.on("close", () => {
99
+ a.delete(e);
100
+ }), O(e), e.on("message", (l) => {
101
+ b(e, typeof l == "string" ? l : l.toString());
102
+ })), e.removeListener("message", r);
103
+ }
104
+ };
105
+ e.on("message", r), e.on("error", (o) => {
106
+ f && f(o);
107
+ });
108
+ }
109
+ const N = (e, n, r) => {
110
+ new URL(e.url ?? "/", `http://${e.headers.host ?? "localhost"}`).pathname === g && u.handleUpgrade(e, n, r, (o) => {
111
+ m(o);
112
+ });
113
+ };
114
+ return y.on("upgrade", N), u.on("error", (e) => {
115
+ f && f(e);
116
+ }), {
117
+ close() {
118
+ y.removeListener("upgrade", N);
119
+ for (const e of a) e.close(1e3, "Relay shutting down");
120
+ a.clear(), t && t.readyState === s.OPEN && t.close(1e3, "Relay shutting down"), t = null, c = !1, u.close();
121
+ },
122
+ get browserConnected() {
123
+ return t !== null && t.readyState === s.OPEN;
124
+ },
125
+ get clientCount() {
126
+ return a.size;
127
+ }
128
+ };
129
+ }
130
+ export {
131
+ _ as t
132
+ };
@@ -0,0 +1 @@
1
+ let o=require("ws");var R="/__twd/ws";function W(y,N){const p=N?.path??R,f=N?.onError,u=new o.WebSocketServer({noServer:!0});let t=null;const l=new Set;let a=!1;function c(e,n,r){const s={type:"error",code:n,message:r};e.readyState===o.WebSocket.OPEN&&e.send(JSON.stringify(s))}function S(e){for(const n of l)n.readyState===o.WebSocket.OPEN&&n.send(e)}function b(e){const n={type:"connected",browser:t!==null&&t.readyState===o.WebSocket.OPEN};e.readyState===o.WebSocket.OPEN&&e.send(JSON.stringify(n))}function O(){S(JSON.stringify({type:"connected",browser:!1}))}function E(e){let n;try{n=JSON.parse(e)}catch{return}n.type==="run:complete"&&(a=!1),S(e)}function h(e,n){let r;try{r=JSON.parse(n)}catch{c(e,"INVALID_MESSAGE","Invalid JSON");return}if(!r.type){c(e,"INVALID_MESSAGE",'Missing "type" field');return}if(r.type==="run"){if(!t||t.readyState!==o.WebSocket.OPEN){c(e,"NO_BROWSER","No browser connected");return}if(a){c(e,"RUN_IN_PROGRESS","A test run is already in progress");return}a=!0,t.send(n);return}if(r.type==="status"){if(!t||t.readyState!==o.WebSocket.OPEN){c(e,"NO_BROWSER","No browser connected");return}t.send(n);return}c(e,"UNKNOWN_COMMAND",`Unknown command: ${r.type}`)}function m(e){let n=!1;const r=s=>{const P=typeof s=="string"?s:s.toString();if(!n){let d;try{d=JSON.parse(P)}catch{c(e,"INVALID_MESSAGE","Invalid JSON");return}if(d.type!=="hello"||d.role!=="browser"&&d.role!=="client"){c(e,"INVALID_MESSAGE",'First message must be a hello with role "browser" or "client"');return}n=!0,d.role==="browser"?(t&&t.readyState===o.WebSocket.OPEN&&t.close(1e3,"Replaced by new browser"),t=e,a=!1,e.on("close",()=>{t===e&&(t=null,a=!1,O())}),S(JSON.stringify({type:"connected",browser:!0})),e.on("message",i=>{E(typeof i=="string"?i:i.toString())})):(l.add(e),e.on("close",()=>{l.delete(e)}),b(e),e.on("message",i=>{h(e,typeof i=="string"?i:i.toString())})),e.removeListener("message",r)}};e.on("message",r),e.on("error",s=>{f&&f(s)})}const g=(e,n,r)=>{new URL(e.url??"/",`http://${e.headers.host??"localhost"}`).pathname===p&&u.handleUpgrade(e,n,r,s=>{m(s)})};return y.on("upgrade",g),u.on("error",e=>{f&&f(e)}),{close(){y.removeListener("upgrade",g);for(const e of l)e.close(1e3,"Relay shutting down");l.clear(),t&&t.readyState===o.WebSocket.OPEN&&t.close(1e3,"Relay shutting down"),t=null,a=!1,u.close()},get browserConnected(){return t!==null&&t.readyState===o.WebSocket.OPEN},get clientCount(){return l.size}}}Object.defineProperty(exports,"createTwdRelay",{enumerable:!0,get:function(){return W}});
package/dist/vite.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("./createTwdRelay-VRmjHboP.cjs");function n(t){return{name:"twd-relay",configureServer(e){if(!e.httpServer)return;const r=o.c(e.httpServer,{path:t?.path??"/__twd/ws"});e.httpServer.on("close",()=>r.close())}}}exports.twdRemote=n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("./relay-CpUORBd7.cjs");function n(r){let t="/";return{name:"twd-relay",configResolved(e){t=e.base},configureServer(e){if(!e.httpServer)return;const o=r?.path??t.replace(/\/$/,"")+"/__twd/ws",a=l.createTwdRelay(e.httpServer,{path:o});e.httpServer.on("close",()=>a.close())}}}exports.twdRemote=n;
package/dist/vite.d.ts CHANGED
@@ -9,6 +9,9 @@ export declare interface TwdRemoteOptions {
9
9
 
10
10
  declare interface VitePlugin {
11
11
  name: string;
12
+ configResolved?: (config: {
13
+ base: string;
14
+ }) => void;
12
15
  configureServer?: (server: {
13
16
  httpServer: Server | null;
14
17
  }) => void;
package/dist/vite.es.js CHANGED
@@ -1,16 +1,18 @@
1
- import { c as o } from "./createTwdRelay-22etCW_8.js";
2
- function n(e) {
1
+ import { t as n } from "./relay-C8UTSEbG.js";
2
+ function l(r) {
3
+ let t = "/";
3
4
  return {
4
5
  name: "twd-relay",
5
- configureServer(t) {
6
- if (!t.httpServer) return;
7
- const r = o(t.httpServer, {
8
- path: e?.path ?? "/__twd/ws"
9
- });
10
- t.httpServer.on("close", () => r.close());
6
+ configResolved(e) {
7
+ t = e.base;
8
+ },
9
+ configureServer(e) {
10
+ if (!e.httpServer) return;
11
+ const o = r?.path ?? t.replace(/\/$/, "") + "/__twd/ws", a = n(e.httpServer, { path: o });
12
+ e.httpServer.on("close", () => a.close());
11
13
  }
12
14
  };
13
15
  }
14
16
  export {
15
- n as twdRemote
17
+ l as twdRemote
16
18
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twd-relay",
3
- "version": "0.2.1",
3
+ "version": "1.0.0",
4
4
  "description": "WebSocket relay for TWD — enables AI agents and external tools to run and observe in-browser tests",
5
5
  "license": "MIT",
6
6
  "author": "BRIKEV",
@@ -60,14 +60,15 @@
60
60
  }
61
61
  },
62
62
  "dependencies": {
63
- "ws": "^8.19.0"
63
+ "ws": "^8.20.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/ws": "^8.18.1",
67
- "@vitest/coverage-v8": "^4.0.18",
67
+ "@vitest/coverage-v8": "^4.1.0",
68
+ "esbuild": "^0.27.4",
68
69
  "typescript": "^5.9.3",
69
- "vite": "^7.3.1",
70
+ "vite": "^8.0.1",
70
71
  "vite-plugin-dts": "^4.5.4",
71
- "vitest": "^4.0.18"
72
+ "vitest": "^4.1.0"
72
73
  }
73
74
  }
@@ -1,123 +0,0 @@
1
- import { WebSocketServer as P, WebSocket as o } from "ws";
2
- const R = "/__twd/ws";
3
- function J(p, N) {
4
- const E = N?.path ?? R, f = N?.onError, u = new P({ noServer: !0 });
5
- let t = null;
6
- const c = /* @__PURE__ */ new Set();
7
- let a = !1;
8
- function s(e, n, r) {
9
- const i = { type: "error", code: n, message: r };
10
- e.readyState === o.OPEN && e.send(JSON.stringify(i));
11
- }
12
- function S(e) {
13
- for (const n of c)
14
- n.readyState === o.OPEN && n.send(e);
15
- }
16
- function h(e) {
17
- const n = { type: "connected", browser: t !== null && t.readyState === o.OPEN };
18
- e.readyState === o.OPEN && e.send(JSON.stringify(n));
19
- }
20
- function m() {
21
- S(JSON.stringify({ type: "connected", browser: !1 }));
22
- }
23
- function b(e) {
24
- let n;
25
- try {
26
- n = JSON.parse(e);
27
- } catch {
28
- return;
29
- }
30
- n.type === "run:complete" && (a = !1), S(e);
31
- }
32
- function A(e, n) {
33
- let r;
34
- try {
35
- r = JSON.parse(n);
36
- } catch {
37
- s(e, "INVALID_MESSAGE", "Invalid JSON");
38
- return;
39
- }
40
- if (!r.type) {
41
- s(e, "INVALID_MESSAGE", 'Missing "type" field');
42
- return;
43
- }
44
- if (r.type === "run") {
45
- if (!t || t.readyState !== o.OPEN) {
46
- s(e, "NO_BROWSER", "No browser connected");
47
- return;
48
- }
49
- if (a) {
50
- s(e, "RUN_IN_PROGRESS", "A test run is already in progress");
51
- return;
52
- }
53
- a = !0, t.send(n);
54
- return;
55
- }
56
- if (r.type === "status") {
57
- if (!t || t.readyState !== o.OPEN) {
58
- s(e, "NO_BROWSER", "No browser connected");
59
- return;
60
- }
61
- t.send(n);
62
- return;
63
- }
64
- s(e, "UNKNOWN_COMMAND", `Unknown command: ${r.type}`);
65
- }
66
- function I(e) {
67
- let n = !1;
68
- const r = (i) => {
69
- const y = typeof i == "string" ? i : i.toString();
70
- if (!n) {
71
- let d;
72
- try {
73
- d = JSON.parse(y);
74
- } catch {
75
- s(e, "INVALID_MESSAGE", "Invalid JSON");
76
- return;
77
- }
78
- if (d.type !== "hello" || d.role !== "browser" && d.role !== "client") {
79
- s(e, "INVALID_MESSAGE", 'First message must be a hello with role "browser" or "client"');
80
- return;
81
- }
82
- n = !0, d.role === "browser" ? (t && t.readyState === o.OPEN && t.close(1e3, "Replaced by new browser"), t = e, a = !1, e.on("close", () => {
83
- t === e && (t = null, a = !1, m());
84
- }), S(JSON.stringify({ type: "connected", browser: !0 })), e.on("message", (l) => {
85
- const g = typeof l == "string" ? l : l.toString();
86
- b(g);
87
- })) : (c.add(e), e.on("close", () => {
88
- c.delete(e);
89
- }), h(e), e.on("message", (l) => {
90
- const g = typeof l == "string" ? l : l.toString();
91
- A(e, g);
92
- })), e.removeListener("message", r);
93
- }
94
- };
95
- e.on("message", r), e.on("error", (i) => {
96
- f && f(i);
97
- });
98
- }
99
- const O = (e, n, r) => {
100
- new URL(e.url ?? "/", `http://${e.headers.host ?? "localhost"}`).pathname === E && u.handleUpgrade(e, n, r, (y) => {
101
- I(y);
102
- });
103
- };
104
- return p.on("upgrade", O), u.on("error", (e) => {
105
- f && f(e);
106
- }), {
107
- close() {
108
- p.removeListener("upgrade", O);
109
- for (const e of c)
110
- e.close(1e3, "Relay shutting down");
111
- c.clear(), t && t.readyState === o.OPEN && t.close(1e3, "Relay shutting down"), t = null, a = !1, u.close();
112
- },
113
- get browserConnected() {
114
- return t !== null && t.readyState === o.OPEN;
115
- },
116
- get clientCount() {
117
- return c.size;
118
- }
119
- };
120
- }
121
- export {
122
- J as c
123
- };
@@ -1 +0,0 @@
1
- "use strict";const o=require("ws"),I="/__twd/ws";function P(N,p){const b=p?.path??I,f=p?.onError,S=new o.WebSocketServer({noServer:!0});let t=null;const l=new Set;let a=!1;function s(e,n,r){const c={type:"error",code:n,message:r};e.readyState===o.WebSocket.OPEN&&e.send(JSON.stringify(c))}function u(e){for(const n of l)n.readyState===o.WebSocket.OPEN&&n.send(e)}function E(e){const n={type:"connected",browser:t!==null&&t.readyState===o.WebSocket.OPEN};e.readyState===o.WebSocket.OPEN&&e.send(JSON.stringify(n))}function h(){u(JSON.stringify({type:"connected",browser:!1}))}function m(e){let n;try{n=JSON.parse(e)}catch{return}n.type==="run:complete"&&(a=!1),u(e)}function W(e,n){let r;try{r=JSON.parse(n)}catch{s(e,"INVALID_MESSAGE","Invalid JSON");return}if(!r.type){s(e,"INVALID_MESSAGE",'Missing "type" field');return}if(r.type==="run"){if(!t||t.readyState!==o.WebSocket.OPEN){s(e,"NO_BROWSER","No browser connected");return}if(a){s(e,"RUN_IN_PROGRESS","A test run is already in progress");return}a=!0,t.send(n);return}if(r.type==="status"){if(!t||t.readyState!==o.WebSocket.OPEN){s(e,"NO_BROWSER","No browser connected");return}t.send(n);return}s(e,"UNKNOWN_COMMAND",`Unknown command: ${r.type}`)}function A(e){let n=!1;const r=c=>{const y=typeof c=="string"?c:c.toString();if(!n){let d;try{d=JSON.parse(y)}catch{s(e,"INVALID_MESSAGE","Invalid JSON");return}if(d.type!=="hello"||d.role!=="browser"&&d.role!=="client"){s(e,"INVALID_MESSAGE",'First message must be a hello with role "browser" or "client"');return}n=!0,d.role==="browser"?(t&&t.readyState===o.WebSocket.OPEN&&t.close(1e3,"Replaced by new browser"),t=e,a=!1,e.on("close",()=>{t===e&&(t=null,a=!1,h())}),u(JSON.stringify({type:"connected",browser:!0})),e.on("message",i=>{const g=typeof i=="string"?i:i.toString();m(g)})):(l.add(e),e.on("close",()=>{l.delete(e)}),E(e),e.on("message",i=>{const g=typeof i=="string"?i:i.toString();W(e,g)})),e.removeListener("message",r)}};e.on("message",r),e.on("error",c=>{f&&f(c)})}const O=(e,n,r)=>{new URL(e.url??"/",`http://${e.headers.host??"localhost"}`).pathname===b&&S.handleUpgrade(e,n,r,y=>{A(y)})};return N.on("upgrade",O),S.on("error",e=>{f&&f(e)}),{close(){N.removeListener("upgrade",O);for(const e of l)e.close(1e3,"Relay shutting down");l.clear(),t&&t.readyState===o.WebSocket.OPEN&&t.close(1e3,"Relay shutting down"),t=null,a=!1,S.close()},get browserConnected(){return t!==null&&t.readyState===o.WebSocket.OPEN},get clientCount(){return l.size}}}exports.c=P;