@reddb-io/client 1.9.0 → 1.10.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.
@@ -13,10 +13,10 @@
13
13
  * - It omits `splitNdjson()` (a `node:stream` `Transform`), which has no
14
14
  * browser counterpart.
15
15
  *
16
- * `connect()` only reaches `http(s)://` from a browser; `grpc(s)://`,
17
- * `red(s)://`, and `pg` throw `RedDBError` with code
18
- * `'BROWSER_TRANSPORT_UNSUPPORTED'`, and embedded URIs throw
19
- * `EmbeddedNotSupported`.
16
+ * `connect()` reaches `http(s)://` and `red+wss://` (RedWire-over-binary-
17
+ * WebSocket, #937) from a browser; `grpc(s)://`, `red(s)://`, and `pg`
18
+ * throw `RedDBError` with code `'BROWSER_TRANSPORT_UNSUPPORTED'`, and
19
+ * embedded URIs throw `EmbeddedNotSupported`.
20
20
  */
21
21
 
22
22
  export type AuthOptions =
@@ -422,7 +422,8 @@ export class RedDB {
422
422
  /**
423
423
  * Connect to a remote RedDB instance from a browser.
424
424
  *
425
- * Only `http://host:port` / `https://host:port` are reachable from a browser
425
+ * `http://host:port`, `https://host:port`, and `red+wss://host:port`
426
+ * (RedWire-over-binary-WebSocket, #937) are reachable from a browser
426
427
  * sandbox. `grpc(s)://`, `red(s)://`, and `pg` throw `RedDBError` with code
427
428
  * `'BROWSER_TRANSPORT_UNSUPPORTED'`. Embedded URIs (`memory://`, `memory:`,
428
429
  * `file:///path`, `red:///`, `red://:memory[:]`) throw `EmbeddedNotSupported`.
@@ -436,7 +437,7 @@ export function login(
436
437
  ): Promise<LoginResult>
437
438
 
438
439
  export interface ParsedUri {
439
- kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'grpc' | 'grpcs' | 'pg'
440
+ kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redwss' | 'grpc' | 'grpcs' | 'pg'
440
441
  host?: string
441
442
  port?: number
442
443
  path?: string
package/index.d.ts CHANGED
@@ -443,7 +443,7 @@ export function login(
443
443
  ): Promise<LoginResult>
444
444
 
445
445
  export interface ParsedUri {
446
- kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'grpc' | 'grpcs' | 'pg'
446
+ kind: 'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redwss' | 'grpc' | 'grpcs' | 'pg'
447
447
  host?: string
448
448
  port?: number
449
449
  path?: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reddb-io/client",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Thin remote-only RedDB driver. Downloads the `red_client` binary on install. Speaks RedWire/gRPC/HTTP. Embedded URIs (memory://, file://, red:///path) are rejected — use @reddb-io/sdk for those.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/core/url.js CHANGED
@@ -20,7 +20,7 @@ import { RedDBError } from './errors.js'
20
20
 
21
21
  /**
22
22
  * @typedef {object} ParsedUri
23
- * @property {'embedded' | 'http' | 'https' | 'red' | 'reds' | 'grpc' | 'grpcs' | 'pg'} kind
23
+ * @property {'embedded' | 'http' | 'https' | 'red' | 'reds' | 'redwss' | 'grpc' | 'grpcs' | 'pg'} kind
24
24
  * @property {string} [host]
25
25
  * @property {number} [port]
26
26
  * @property {string} [path] // for embedded `file://`-equivalent
@@ -47,12 +47,48 @@ export function parseUri(uri) {
47
47
  "connect() requires a URI string (e.g. 'red://localhost:5050' or 'red:///data.rdb')",
48
48
  )
49
49
  }
50
+ if (uri.startsWith('red+wss://')) {
51
+ return parseRedWssUrl(uri)
52
+ }
50
53
  if (uri.startsWith('red://') || uri === 'red:' || uri === 'red:/') {
51
54
  return parseRedUrl(uri)
52
55
  }
53
56
  return parseLegacyUrl(uri)
54
57
  }
55
58
 
59
+ /**
60
+ * Parse `red+wss://[user:pass@]host:port[?token=...]` — RedWire framing
61
+ * tunneled over a binary WebSocket (the browser transport; #937, ADR
62
+ * 0036). Resolves to a `wss://host:port/redwire` endpoint downstream.
63
+ * Default port is 443 (the TLS edge).
64
+ */
65
+ export function parseRedWssUrl(uri) {
66
+ let parsed
67
+ try {
68
+ // Borrow the URL parser via an `https://` authority so user / pass /
69
+ // host / port / query all decode with the standard rules.
70
+ parsed = new URL('https://' + uri.slice('red+wss://'.length))
71
+ } catch (err) {
72
+ throw new RedDBError('UNPARSEABLE_URI', `failed to parse '${uri}': ${err.message}`)
73
+ }
74
+ if (!parsed.hostname) {
75
+ throw new TypeError(`invalid red+wss:// URI: missing host in '${uri}'`)
76
+ }
77
+ const params = parsed.searchParams
78
+ return {
79
+ kind: 'redwss',
80
+ host: parsed.hostname,
81
+ port: parsed.port ? Number(parsed.port) : 443,
82
+ username: parsed.username ? decodeURIComponent(parsed.username) : undefined,
83
+ password: parsed.password ? decodeURIComponent(parsed.password) : undefined,
84
+ token: params.get('token') ?? undefined,
85
+ apiKey: params.get('apiKey') ?? params.get('api_key') ?? undefined,
86
+ loginUrl: params.get('loginUrl') ?? params.get('login_url') ?? undefined,
87
+ params,
88
+ originalUri: uri,
89
+ }
90
+ }
91
+
56
92
  /**
57
93
  * Parse a `red://` URL.
58
94
  *
@@ -40,6 +40,7 @@ import {
40
40
  mergeAuthFromUri,
41
41
  } from './core/index.js'
42
42
  import { HttpRpcClient } from './http.js'
43
+ import { connectRedwireWs, REDWIRE_WS_PATH } from './redwire-ws.js'
43
44
  import { createSelectStream, createInputStream } from './streaming-web.js'
44
45
 
45
46
  export { RedDBError, EmbeddedNotSupported, EMBEDDED_REJECTION_MESSAGE, isEmbeddedUri }
@@ -122,6 +123,28 @@ export async function connect(uri, options = {}) {
122
123
  return new RedDB(client)
123
124
  }
124
125
 
126
+ // RedWire-over-binary-WebSocket (#937, ADR 0036): the browser speaks the
127
+ // same multiplexed binary protocol as the native drivers, tunneled over
128
+ // a WSS the sandbox can open. The TLS edge enforces the Origin allowlist
129
+ // and WSS-only on its side; here we just open the socket and run the
130
+ // standard RedWire handshake over it.
131
+ if (parsed.kind === 'redwss') {
132
+ const merged = mergeAuthFromUri(parsed, options.auth)
133
+ let token = merged.token
134
+ if (!token && merged.username && merged.password) {
135
+ const loginUrl = merged.loginUrl ?? `https://${parsed.host}:${parsed.port}/auth/login`
136
+ const session = await login(loginUrl, {
137
+ username: merged.username,
138
+ password: merged.password,
139
+ })
140
+ token = session.token
141
+ }
142
+ const auth = token ? { kind: 'bearer', token } : { kind: 'anonymous' }
143
+ const url = `wss://${parsed.host}:${parsed.port}${REDWIRE_WS_PATH}`
144
+ const client = await connectRedwireWs({ url, auth })
145
+ return new RedDB(client)
146
+ }
147
+
125
148
  if (
126
149
  parsed.kind === 'grpc'
127
150
  || parsed.kind === 'grpcs'