@volter/teams 0.2.16 → 0.2.17

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.
@@ -82,3 +82,15 @@ export interface BrowserPane extends ChannelEvents {
82
82
  }
83
83
 
84
84
  export function attachBrowserPane(channel: BrowserChannel, options: { pane: string; mode?: 'observe' | 'control'; cols?: number; rows?: number }): Promise<BrowserPane>;
85
+
86
+ /** This machine through its app door (`supercode app-door on`), with the pairing from `app-door link`. */
87
+ export function openBrowserLocalMachine(target: { door: string; token: string }, options?: ChannelOptions): Promise<BrowserChannel>;
88
+ /** The app door's pairing from a page's address (`#supercode=…`), or null. */
89
+ export function readAppDoorPairing(hash?: string): { door: string; token: string } | null;
90
+ /** A harness SDK transport over a browser channel's `harness` stream. */
91
+ export function browserHarnessTransport(channel: BrowserChannel | (() => BrowserChannel | Promise<BrowserChannel>)): {
92
+ readonly running: boolean;
93
+ start(handlers: { onLine(line: string): void; onExit(code: number | null, reason: string | null): void; onStderr?(text: string): void; onWriteError?(error: Error): void }): Promise<void>;
94
+ write(line: string): void;
95
+ close(): Promise<void>;
96
+ };
@@ -167,6 +167,81 @@ export function openBrowserSession({ origin, teamId, sessionId, token }, { WebSo
167
167
  return connectBrowserChannel(socket, channel, `could not reach session ${sessionId} through ${origin}`, 'the server closed the session channel');
168
168
  }
169
169
 
170
+ /**
171
+ * This machine, through its app door (the daemon's loopback WebSocket for web apps, off until the
172
+ * person turns it on: `supercode app-door on`). `door` and `token` come from the link the
173
+ * person opened the app with (`app-door link`); see readAppDoorPairing.
174
+ */
175
+ export function openBrowserLocalMachine({ door, token }, { WebSocketImpl = globalThis.WebSocket } = {}) {
176
+ const socket = new WebSocketImpl(door, [CHANNEL_PROTOCOL, `bearer.${token}`]);
177
+ const channel = new BrowserChannel(socket);
178
+ return connectBrowserChannel(socket, channel, `could not reach this machine's app door at ${door}: is it on? (supercode app-door status)`, 'the machine closed the app door\'s channel');
179
+ }
180
+
181
+ /**
182
+ * The app door's pairing from a page's address (`#supercode=<base64url JSON { door, token }>`, as
183
+ * `app-door link` writes it), or null. The caller keeps it and removes it from the address.
184
+ */
185
+ export function readAppDoorPairing(hash = globalThis.location?.hash ?? '') {
186
+ const match = /(?:^#|&)supercode=([A-Za-z0-9_-]+)/.exec(hash);
187
+ if (!match) return null;
188
+ try {
189
+ const padded = match[1].replace(/-/g, '+').replace(/_/g, '/');
190
+ const value = JSON.parse(decoder.decode(Uint8Array.from(atob(padded + '='.repeat((4 - (padded.length % 4)) % 4)), (c) => c.charCodeAt(0))));
191
+ // Only this machine's own door: a link naming any other address would hand the person's work to it.
192
+ const door = typeof value?.door === 'string' ? new URL(value.door) : null;
193
+ const local = door && door.protocol === 'ws:' && ['127.0.0.1', 'localhost', '[::1]'].includes(door.hostname);
194
+ return local && typeof value.token === 'string' && value.token ? { door: door.href, token: value.token } : null;
195
+ } catch {
196
+ return null;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * A harness SDK transport (`ClientOptions.transport`) over a browser machine channel's `harness`
202
+ * stream: harness.v1 JSON lines to the machine's harness service. `channel` is a channel, or a function
203
+ * that opens one, called again after the stream closes so a restarted daemon costs one reconnect.
204
+ */
205
+ export function browserHarnessTransport(channel) {
206
+ const owned = typeof channel === 'function';
207
+ const source = owned ? channel : () => channel;
208
+ let stream = null;
209
+ let opened = null;
210
+ return {
211
+ get running() { return stream !== null && !stream.closed; },
212
+ async start(handlers) {
213
+ // A channel this transport opened is its own to close when it opens the next.
214
+ if (owned && opened && !opened.closed) opened.close();
215
+ opened = await source();
216
+ const current = await opened.open('harness');
217
+ stream = current;
218
+ const text = new TextDecoder();
219
+ let pending = '';
220
+ current.on('data', (chunk) => {
221
+ pending += typeof chunk === 'string' ? chunk : text.decode(chunk, { stream: true });
222
+ let end;
223
+ while ((end = pending.indexOf('\n')) >= 0) {
224
+ const line = pending.slice(0, end).replace(/\r$/, '');
225
+ pending = pending.slice(end + 1);
226
+ if (line) handlers.onLine(line);
227
+ }
228
+ });
229
+ // Only the current stream's end is the transport's: an earlier one closing late is not.
230
+ current.on('close', (reason) => { if (stream !== current) return; stream = null; handlers.onExit(null, reason ?? null); });
231
+ },
232
+ write(line) {
233
+ if (!stream || stream.closed) throw new Error('the harness stream is not open');
234
+ stream.write(line.endsWith('\n') ? line : line + '\n');
235
+ },
236
+ async close() {
237
+ stream?.close();
238
+ stream = null;
239
+ if (owned) opened?.close();
240
+ opened = null;
241
+ },
242
+ };
243
+ }
244
+
170
245
  /** A turn was accepted by its stream, but its delivery receipt was not obtained. */
171
246
  export class TurnDeliveryUnconfirmedError extends Error {
172
247
  constructor(message) { super(message); this.name = 'TurnDeliveryUnconfirmedError'; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "type": "module",
5
5
  "description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
6
6
  "license": "MIT",