dsh-realtime-audio-ws 0.2.1 → 0.2.2
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/lib/diagnostics.d.ts.map +1 -1
- package/lib/diagnostics.js +42 -4
- package/lib/diagnostics.js.map +1 -1
- package/package.json +1 -1
- package/src/diagnostics.ts +42 -4
package/lib/diagnostics.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAG3C;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,GAAG,WAAW,CAAC,CAAA;AAEjF,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;KAAE,KAAK,GAAG,GAAG,GAAG,GAAG,SAAS,CAAA;IAClG,4BAA4B;IAC5B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAA;CACrC;AAED,0GAA0G;AAC1G,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CACtE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,gBAAgB,
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAChE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAG3C;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,GAAG,WAAW,CAAC,CAAA;AAEjF,yDAAyD;AACzD,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;KAAE,KAAK,GAAG,GAAG,GAAG,GAAG,SAAS,CAAA;IAClG,4BAA4B;IAC5B,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAA;CACrC;AAED,0GAA0G;AAC1G,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CACtE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,gBAAgB,CAgCxE"}
|
package/lib/diagnostics.js
CHANGED
|
@@ -35,21 +35,58 @@ export function diagnosticsRoute(deps) {
|
|
|
35
35
|
kind: 'exact',
|
|
36
36
|
path: deps.path,
|
|
37
37
|
handler: (req, res) => {
|
|
38
|
+
// The host's own page is cross-origin to loopback, and a browser discards a cross-origin response
|
|
39
|
+
// that carries no `Access-Control-Allow-Origin` — the request authenticates, the route answers
|
|
40
|
+
// 200, and the caller sees a CORS failure with no body. The WebSocket sibling is exempt from CORS,
|
|
41
|
+
// which is how this went unnoticed: one policy, two doors, and only one of them told the browser
|
|
42
|
+
// it was allowed to read the answer.
|
|
43
|
+
const origin = typeof req.headers.origin === 'string' ? req.headers.origin : undefined;
|
|
38
44
|
const rejection = verdictFor(deps.rejectionFor(req), tokenFromUrl(req.url), deps.token);
|
|
39
45
|
if (rejection !== undefined) {
|
|
40
46
|
// The status carries the verdict the connection service actually gave, so a reader can tell a
|
|
41
47
|
// missing credential from a refused one rather than guessing from a generic failure.
|
|
42
|
-
send(res, rejection, { error: rejection === 401 ? 'unauthorized' : 'forbidden' });
|
|
48
|
+
send(res, rejection, { error: rejection === 401 ? 'unauthorized' : 'forbidden' }, origin);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// A preflight is answered only after the verdict, never before it. Answering one unauthenticated
|
|
52
|
+
// would confirm the route's existence to a caller holding no capability at all.
|
|
53
|
+
if (req.method === 'OPTIONS') {
|
|
54
|
+
res.writeHead(204, headers(origin));
|
|
55
|
+
res.end();
|
|
43
56
|
return;
|
|
44
57
|
}
|
|
45
58
|
const entries = deps.journal.snapshot();
|
|
46
59
|
// `size` and `oldestSeq` travel with the entries rather than being left for the reader to infer:
|
|
47
60
|
// a truncated buffer and an idle one are the same empty-looking list otherwise, and the sequence
|
|
48
61
|
// number is the only thing that separates "nothing happened" from "the buffer rolled over".
|
|
49
|
-
send(res, 200, { size: deps.journal.size, oldestSeq: deps.journal.oldestSeq, entries });
|
|
62
|
+
send(res, 200, { size: deps.journal.size, oldestSeq: deps.journal.oldestSeq, entries }, origin);
|
|
50
63
|
},
|
|
51
64
|
};
|
|
52
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The headers every response from this route carries.
|
|
68
|
+
*
|
|
69
|
+
* `Access-Control-Allow-Origin` **reflects the caller's own origin** rather than naming one. The token is
|
|
70
|
+
* the authorisation here, not the origin: a request only reaches this point holding a capability that
|
|
71
|
+
* exists nowhere but the page the host injected it into, so echoing the origin grants nothing the token
|
|
72
|
+
* has not already granted — and it keeps the route readable from a profile whose page is served on some
|
|
73
|
+
* other scheme. `Vary: Origin`, because the header varies with the request and a cache that missed that
|
|
74
|
+
* would serve one origin's answer to another.
|
|
75
|
+
* @param origin - the request's `Origin`, when it sent one.
|
|
76
|
+
* @returns the response headers.
|
|
77
|
+
*/
|
|
78
|
+
function headers(origin) {
|
|
79
|
+
const built = {
|
|
80
|
+
'content-type': 'application/json',
|
|
81
|
+
'cache-control': 'no-store',
|
|
82
|
+
vary: 'Origin',
|
|
83
|
+
};
|
|
84
|
+
// Absent when the caller sent no origin — a command-line client needs no permission to read a response
|
|
85
|
+
// it was never going to have a browser apply a same-origin policy to.
|
|
86
|
+
if (origin !== undefined)
|
|
87
|
+
built['access-control-allow-origin'] = origin;
|
|
88
|
+
return built;
|
|
89
|
+
}
|
|
53
90
|
/**
|
|
54
91
|
* Write one JSON response.
|
|
55
92
|
*
|
|
@@ -58,9 +95,10 @@ export function diagnosticsRoute(deps) {
|
|
|
58
95
|
* @param res - the response this handler owns.
|
|
59
96
|
* @param status - HTTP status to write.
|
|
60
97
|
* @param body - value to serialise as the JSON body.
|
|
98
|
+
* @param origin - the request's `Origin`, carried through so the answer is readable by whoever asked.
|
|
61
99
|
*/
|
|
62
|
-
function send(res, status, body) {
|
|
63
|
-
res.writeHead(status,
|
|
100
|
+
function send(res, status, body, origin) {
|
|
101
|
+
res.writeHead(status, headers(origin));
|
|
64
102
|
res.end(JSON.stringify(body));
|
|
65
103
|
}
|
|
66
104
|
//# sourceMappingURL=diagnostics.js.map
|
package/lib/diagnostics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AA8BzD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAqB;IACpD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACpB,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;YACvF,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,8FAA8F;gBAC9F,qFAAqF;gBACrF,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,KAAK,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AA8BzD;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAqB;IACpD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YACpB,kGAAkG;YAClG,+FAA+F;YAC/F,mGAAmG;YACnG,iGAAiG;YACjG,qCAAqC;YACrC,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;YACtF,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;YACvF,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,8FAA8F;gBAC9F,qFAAqF;gBACrF,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,KAAK,GAAG,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,MAAM,CAAC,CAAA;gBACzF,OAAM;YACR,CAAC;YACD,iGAAiG;YACjG,gFAAgF;YAChF,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC7B,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;gBACnC,GAAG,CAAC,GAAG,EAAE,CAAA;gBACT,OAAM;YACR,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAA;YACvC,iGAAiG;YACjG,iGAAiG;YACjG,4FAA4F;YAC5F,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,CAAA;QACjG,CAAC;KACF,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,OAAO,CAAC,MAA0B;IACzC,MAAM,KAAK,GAA2B;QACpC,cAAc,EAAE,kBAAkB;QAClC,eAAe,EAAE,UAAU;QAC3B,IAAI,EAAE,QAAQ;KACf,CAAA;IACD,uGAAuG;IACvG,sEAAsE;IACtE,IAAI,MAAM,KAAK,SAAS;QAAE,KAAK,CAAC,6BAA6B,CAAC,GAAG,MAAM,CAAA;IACvE,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,IAAI,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa,EAAE,MAA0B;IAC1F,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;IACtC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;AAC/B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-realtime-audio-ws",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "The host end of the client half's transport: a WebSocket upgrade route that bridges microphone audio in and agent speech out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/src/diagnostics.ts
CHANGED
|
@@ -67,22 +67,59 @@ export function diagnosticsRoute(deps: DiagnosticsDeps): DiagnosticsRoute {
|
|
|
67
67
|
kind: 'exact',
|
|
68
68
|
path: deps.path,
|
|
69
69
|
handler: (req, res) => {
|
|
70
|
+
// The host's own page is cross-origin to loopback, and a browser discards a cross-origin response
|
|
71
|
+
// that carries no `Access-Control-Allow-Origin` — the request authenticates, the route answers
|
|
72
|
+
// 200, and the caller sees a CORS failure with no body. The WebSocket sibling is exempt from CORS,
|
|
73
|
+
// which is how this went unnoticed: one policy, two doors, and only one of them told the browser
|
|
74
|
+
// it was allowed to read the answer.
|
|
75
|
+
const origin = typeof req.headers.origin === 'string' ? req.headers.origin : undefined
|
|
70
76
|
const rejection = verdictFor(deps.rejectionFor(req), tokenFromUrl(req.url), deps.token)
|
|
71
77
|
if (rejection !== undefined) {
|
|
72
78
|
// The status carries the verdict the connection service actually gave, so a reader can tell a
|
|
73
79
|
// missing credential from a refused one rather than guessing from a generic failure.
|
|
74
|
-
send(res, rejection, { error: rejection === 401 ? 'unauthorized' : 'forbidden' })
|
|
80
|
+
send(res, rejection, { error: rejection === 401 ? 'unauthorized' : 'forbidden' }, origin)
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
// A preflight is answered only after the verdict, never before it. Answering one unauthenticated
|
|
84
|
+
// would confirm the route's existence to a caller holding no capability at all.
|
|
85
|
+
if (req.method === 'OPTIONS') {
|
|
86
|
+
res.writeHead(204, headers(origin))
|
|
87
|
+
res.end()
|
|
75
88
|
return
|
|
76
89
|
}
|
|
77
90
|
const entries = deps.journal.snapshot()
|
|
78
91
|
// `size` and `oldestSeq` travel with the entries rather than being left for the reader to infer:
|
|
79
92
|
// a truncated buffer and an idle one are the same empty-looking list otherwise, and the sequence
|
|
80
93
|
// number is the only thing that separates "nothing happened" from "the buffer rolled over".
|
|
81
|
-
send(res, 200, { size: deps.journal.size, oldestSeq: deps.journal.oldestSeq, entries })
|
|
94
|
+
send(res, 200, { size: deps.journal.size, oldestSeq: deps.journal.oldestSeq, entries }, origin)
|
|
82
95
|
},
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
/**
|
|
100
|
+
* The headers every response from this route carries.
|
|
101
|
+
*
|
|
102
|
+
* `Access-Control-Allow-Origin` **reflects the caller's own origin** rather than naming one. The token is
|
|
103
|
+
* the authorisation here, not the origin: a request only reaches this point holding a capability that
|
|
104
|
+
* exists nowhere but the page the host injected it into, so echoing the origin grants nothing the token
|
|
105
|
+
* has not already granted — and it keeps the route readable from a profile whose page is served on some
|
|
106
|
+
* other scheme. `Vary: Origin`, because the header varies with the request and a cache that missed that
|
|
107
|
+
* would serve one origin's answer to another.
|
|
108
|
+
* @param origin - the request's `Origin`, when it sent one.
|
|
109
|
+
* @returns the response headers.
|
|
110
|
+
*/
|
|
111
|
+
function headers(origin: string | undefined): Record<string, string> {
|
|
112
|
+
const built: Record<string, string> = {
|
|
113
|
+
'content-type': 'application/json',
|
|
114
|
+
'cache-control': 'no-store',
|
|
115
|
+
vary: 'Origin',
|
|
116
|
+
}
|
|
117
|
+
// Absent when the caller sent no origin — a command-line client needs no permission to read a response
|
|
118
|
+
// it was never going to have a browser apply a same-origin policy to.
|
|
119
|
+
if (origin !== undefined) built['access-control-allow-origin'] = origin
|
|
120
|
+
return built
|
|
121
|
+
}
|
|
122
|
+
|
|
86
123
|
/**
|
|
87
124
|
* Write one JSON response.
|
|
88
125
|
*
|
|
@@ -91,8 +128,9 @@ export function diagnosticsRoute(deps: DiagnosticsDeps): DiagnosticsRoute {
|
|
|
91
128
|
* @param res - the response this handler owns.
|
|
92
129
|
* @param status - HTTP status to write.
|
|
93
130
|
* @param body - value to serialise as the JSON body.
|
|
131
|
+
* @param origin - the request's `Origin`, carried through so the answer is readable by whoever asked.
|
|
94
132
|
*/
|
|
95
|
-
function send(res: ServerResponse, status: number, body: unknown): void {
|
|
96
|
-
res.writeHead(status,
|
|
133
|
+
function send(res: ServerResponse, status: number, body: unknown, origin: string | undefined): void {
|
|
134
|
+
res.writeHead(status, headers(origin))
|
|
97
135
|
res.end(JSON.stringify(body))
|
|
98
136
|
}
|