@irtio/protocol 0.3.0 → 0.5.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
@@ -384,4 +384,52 @@ declare const relaySchema: Schema<{
384
384
  declare const RELAY_HASH8: Uint8Array;
385
385
  declare function isRelayHash8(hash8: Uint8Array): boolean;
386
386
 
387
- export { type Call, type ClientCallable, type Credential, ERROR_CATALOGUE, type ErrorCatalogueEntry, ErrorCode, type ErrorCodeDef, type ErrorCodeName, type ErrorPayload, type Frame, FrameType, type Hello, type Msg, type MsgTarget, PRESENCE_COLLECTION, PROTOCOL_VERSION, type Ping, type Pong, type PresenceRecord, RELAY_HASH8, type Reply, type Welcome, builtinRpcs, correctPayload, decodeCall, decodeErrorPayload, decodeFrame, decodeHello, decodeMsg, decodePing, decodePong, decodeReply, decodeWelcome, deltaPayload, encodeCall, encodeCorrectFrame, encodeDeltaFrame, encodeErrorPayload, encodeFrame, encodeHello, encodeMsg, encodePing, encodePong, encodeReply, encodeWelcome, encodeWriteFrame, errorByCode, formatError, isFrameType, isRelayHash8, presenceEntity, readCorrectAppliedTick, readCorrectClientTick, relaySchema, requestOwnership, rpcByIdOf, rpcIdOf, rpcTable, withBuiltins, writePayload };
387
+ /**
388
+ * Browser-origin policy, shared by every component that has to answer "may this page connect?"
389
+ *
390
+ * Three components ask that question about the same project and must answer it identically: the
391
+ * router (from the control plane's fresh list), the guest supervisor (from the list frozen into
392
+ * its boot env), and `irtio dev` (from `IRT_ORIGINS`). It lived only in the supervisor until the
393
+ * router was given the list it had been fetching and discarding, at which point one copy of the
394
+ * rules became the only honest arrangement.
395
+ *
396
+ * The rules, in the order they are applied:
397
+ *
398
+ * 1. No `Origin` header at all — bots, native clients, tests, `curl` — is NOT a browser and is
399
+ * allowed unless the caller opts out. Origin locking is a same-origin-policy backstop; it
400
+ * protects a browser user from a page they did not open, and there is no such user here.
401
+ * 2. `*` in the list allows every origin.
402
+ * 3. Localhost is ALWAYS allowed, on any port and any scheme. A project's list starts blank,
403
+ * and a developer running `vite dev` against hosted rooms must not have to register a port
404
+ * number before anything works. Nothing is protected by refusing it: an attacker who can
405
+ * serve the victim a page from their own machine has already won.
406
+ * 4. Otherwise the origin must appear in the list verbatim.
407
+ */
408
+ /**
409
+ * Is `origin` a page served from the connecting developer's own machine?
410
+ *
411
+ * Matches on the HOSTNAME only, so every port passes — a dev server's port is an implementation
412
+ * detail that changes per tool and per run. `*.localhost` (which resolves to loopback in every
413
+ * current browser) counts too. A `file://` page sends the literal `null`, which does not parse
414
+ * as a URL and is therefore not local: it is indistinguishable from a sandboxed iframe on a
415
+ * hostile site, and lumping the two together would silently widen rule 3 to the open internet.
416
+ */
417
+ declare function isLocalhostOrigin(origin: string): boolean;
418
+ /**
419
+ * `origins: ['*']` allows every browser origin; an empty list allows only localhost. A missing
420
+ * `Origin` (bots, native clients, tests) is allowed unless `allowNoOrigin` is explicitly `false`.
421
+ */
422
+ declare function originAllowed(origins: readonly string[], allowNoOrigin: boolean, origin: string | undefined): boolean;
423
+ /**
424
+ * Splits an `IRT_ORIGINS`-shaped value into a list. The distinction that matters is UNSET vs
425
+ * EMPTY, and it is the caller's to make: unset means "no policy configured, allow everything"
426
+ * (`['*']`, what a hand-run supervisor and every pre-blank-default deployment expect), while an
427
+ * empty string means "a policy IS configured and it lists nothing" — localhost only.
428
+ *
429
+ * Getting that backwards fails OPEN, which is why it is one function with one test rather than a
430
+ * `?? '*'` at each call site. `deploy/host/rootfs/tenant-init` has the same trap in shell:
431
+ * `${IRT_ORIGINS:-*}` treats empty as unset, `${IRT_ORIGINS-*}` does not.
432
+ */
433
+ declare function parseOriginList(raw: string | undefined): readonly string[];
434
+
435
+ export { type Call, type ClientCallable, type Credential, ERROR_CATALOGUE, type ErrorCatalogueEntry, ErrorCode, type ErrorCodeDef, type ErrorCodeName, type ErrorPayload, type Frame, FrameType, type Hello, type Msg, type MsgTarget, PRESENCE_COLLECTION, PROTOCOL_VERSION, type Ping, type Pong, type PresenceRecord, RELAY_HASH8, type Reply, type Welcome, builtinRpcs, correctPayload, decodeCall, decodeErrorPayload, decodeFrame, decodeHello, decodeMsg, decodePing, decodePong, decodeReply, decodeWelcome, deltaPayload, encodeCall, encodeCorrectFrame, encodeDeltaFrame, encodeErrorPayload, encodeFrame, encodeHello, encodeMsg, encodePing, encodePong, encodeReply, encodeWelcome, encodeWriteFrame, errorByCode, formatError, isFrameType, isLocalhostOrigin, isRelayHash8, originAllowed, parseOriginList, presenceEntity, readCorrectAppliedTick, readCorrectClientTick, relaySchema, requestOwnership, rpcByIdOf, rpcIdOf, rpcTable, withBuiltins, writePayload };
package/dist/index.js CHANGED
@@ -405,6 +405,29 @@ function isRelayHash8(hash8) {
405
405
  for (const b of hash8) if (b !== 0) return false;
406
406
  return true;
407
407
  }
408
+
409
+ // src/origin.ts
410
+ var LOCAL_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0"]);
411
+ function isLocalhostOrigin(origin) {
412
+ let url;
413
+ try {
414
+ url = new URL(origin);
415
+ } catch {
416
+ return false;
417
+ }
418
+ const host = url.hostname.toLowerCase();
419
+ return LOCAL_HOSTNAMES.has(host) || host.endsWith(".localhost");
420
+ }
421
+ function originAllowed(origins, allowNoOrigin, origin) {
422
+ if (origin === void 0 || origin === "") return allowNoOrigin;
423
+ if (origins.includes("*")) return true;
424
+ if (isLocalhostOrigin(origin)) return true;
425
+ return origins.includes(origin);
426
+ }
427
+ function parseOriginList(raw) {
428
+ if (raw === void 0) return ["*"];
429
+ return raw.split(",").map((o) => o.trim()).filter((o) => o.length > 0);
430
+ }
408
431
  export {
409
432
  ERROR_CATALOGUE,
410
433
  ErrorCode,
@@ -439,7 +462,10 @@ export {
439
462
  errorByCode,
440
463
  formatError,
441
464
  isFrameType,
465
+ isLocalhostOrigin,
442
466
  isRelayHash8,
467
+ originAllowed,
468
+ parseOriginList,
443
469
  presenceEntity,
444
470
  readCorrectAppliedTick,
445
471
  readCorrectClientTick,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@irtio/protocol",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "irtio wire protocol: frames, framing, session payloads, error codes, built-in presence",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -20,7 +20,7 @@
20
20
  "dist"
21
21
  ],
22
22
  "dependencies": {
23
- "@irtio/schema": "0.3.0"
23
+ "@irtio/schema": "0.5.0"
24
24
  },
25
25
  "scripts": {
26
26
  "build": "tsup",