@yourrealm/sdk 0.0.0-alpha.1378.ce03c8dc
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/LICENSE-EXCEPTION +26 -0
- package/README.md +34 -0
- package/dist/chunk-3R6KEFOI.js +22 -0
- package/dist/chunk-67BDAMYX.js +376 -0
- package/dist/chunk-AITGS27R.js +139 -0
- package/dist/chunk-CMAEHQ4F.js +6 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-O3UIK34B.js +1430 -0
- package/dist/chunk-VHTUSZUX.js +176 -0
- package/dist/cli.js +9006 -0
- package/dist/docker.js +54 -0
- package/dist/index.js +1053 -0
- package/dist/ingress.js +436 -0
- package/dist/jsx-runtime.js +2 -0
- package/dist/preact.js +22 -0
- package/dist/service-url.js +11 -0
- package/dist/services.js +213 -0
- package/dist/test.js +5069 -0
- package/dist/ui.js +254 -0
- package/dist/worker-protocol.js +89 -0
- package/package.json +112 -0
- package/src/app-icon.ts +74 -0
- package/src/apply.ts +52 -0
- package/src/bundle-ui.ts +211 -0
- package/src/bundle.ts +546 -0
- package/src/cli/build-dev-landing.ts +84 -0
- package/src/cli/build-install-panel.ts +90 -0
- package/src/cli/build-runtime.ts +133 -0
- package/src/cli/build.ts +63 -0
- package/src/cli/dev.ts +107 -0
- package/src/cli/down.ts +60 -0
- package/src/cli/main.ts +56 -0
- package/src/cli/test.ts +61 -0
- package/src/cli/validate.ts +37 -0
- package/src/compile.ts +100 -0
- package/src/create-app.ts +982 -0
- package/src/deep-merge.ts +33 -0
- package/src/dev/fixture.ts +48 -0
- package/src/dev/install-handler.ts +383 -0
- package/src/dev/server.ts +968 -0
- package/src/dev/stack.ts +108 -0
- package/src/dev/state.ts +159 -0
- package/src/docker.ts +96 -0
- package/src/errors.ts +61 -0
- package/src/factory.ts +481 -0
- package/src/garage.ts +56 -0
- package/src/generator/hash.ts +6 -0
- package/src/generator/resolve-refs.ts +207 -0
- package/src/grants-builder.ts +100 -0
- package/src/grants.ts +260 -0
- package/src/home-api.ts +172 -0
- package/src/home-cookie.ts +10 -0
- package/src/home-registry.ts +120 -0
- package/src/home.ts +187 -0
- package/src/host-registry.ts +143 -0
- package/src/http-header.ts +20 -0
- package/src/identifiers.ts +49 -0
- package/src/index.ts +66 -0
- package/src/info.ts +64 -0
- package/src/ini.ts +50 -0
- package/src/json.ts +14 -0
- package/src/jsx-runtime/index.ts +5 -0
- package/src/keys.ts +29 -0
- package/src/mysql.ts +56 -0
- package/src/pack-build.ts +108 -0
- package/src/pack.ts +115 -0
- package/src/peer-registry.ts +121 -0
- package/src/peer-world.ts +117 -0
- package/src/permission-meta.ts +116 -0
- package/src/permissions.ts +108 -0
- package/src/pkg-root.ts +34 -0
- package/src/postgres.ts +79 -0
- package/src/preact/index.ts +27 -0
- package/src/redis.ts +43 -0
- package/src/ref.ts +271 -0
- package/src/remote-registry.ts +33 -0
- package/src/required-host-kinds.ts +110 -0
- package/src/resolve-context.ts +111 -0
- package/src/resolve.ts +471 -0
- package/src/runtime/docker/compose/compose-schema.ts +118 -0
- package/src/runtime/docker/compose/convert-to-docker-compose.ts +278 -0
- package/src/runtime/docker/compose/default-hardening.ts +21 -0
- package/src/runtime/docker/compose/effects.ts +107 -0
- package/src/runtime/docker/compose/index.ts +17 -0
- package/src/runtime/docker/compose/labels.ts +39 -0
- package/src/runtime/docker/compose/types.ts +78 -0
- package/src/runtime/docker/home-proxy-server/index.ts +177 -0
- package/src/runtime/docker/home-proxy-server/protocol.ts +53 -0
- package/src/runtime/docker/ingress/build-config.ts +819 -0
- package/src/runtime/docker/ingress/index.ts +16 -0
- package/src/runtime/docker/ingress/types.ts +182 -0
- package/src/runtime/docker/log-frames.ts +121 -0
- package/src/runtime/docker/logs-server/index.ts +366 -0
- package/src/runtime/docker/spawn-server/context-key.ts +82 -0
- package/src/runtime/docker/spawn-server/index.ts +492 -0
- package/src/runtime/docker/spawn-server/protocol.ts +70 -0
- package/src/runtime/docker/spawn-server/spec.ts +122 -0
- package/src/runtime/docker/stats-server/disks.ts +161 -0
- package/src/runtime/docker/stats-server/index.ts +322 -0
- package/src/runtime/home-client/index.ts +71 -0
- package/src/runtime/log-line.ts +24 -0
- package/src/runtime/logs-client/index.ts +175 -0
- package/src/runtime/spawn-client/index.ts +93 -0
- package/src/runtime/spawn-client/kaniko.ts +79 -0
- package/src/runtime/stats-client/index.ts +65 -0
- package/src/runtime/stats-sample.ts +95 -0
- package/src/safe-json.ts +37 -0
- package/src/sandbox.ts +1209 -0
- package/src/schema/build-apps.ts +38 -0
- package/src/schema/home.ts +217 -0
- package/src/schema/host.ts +792 -0
- package/src/schema/inputs.ts +26 -0
- package/src/schema/nodes.ts +75 -0
- package/src/schema/peer.ts +400 -0
- package/src/schema/public-domain.ts +116 -0
- package/src/schema/remote.ts +74 -0
- package/src/security.ts +594 -0
- package/src/serialize.ts +21 -0
- package/src/service-definition.ts +284 -0
- package/src/service-url.ts +64 -0
- package/src/service.ts +39 -0
- package/src/services/index.ts +5 -0
- package/src/shared.ts +65 -0
- package/src/speculative-silo.ts +96 -0
- package/src/stable-secret.ts +14 -0
- package/src/surfaces.ts +264 -0
- package/src/tailwind-bin.ts +56 -0
- package/src/test/boot.ts +192 -0
- package/src/test/compile.ts +501 -0
- package/src/test/describe.ts +117 -0
- package/src/test/index.ts +11 -0
- package/src/test/ingress-fixture.ts +37 -0
- package/src/test/types.ts +318 -0
- package/src/trusted-headers.ts +79 -0
- package/src/types.ts +126 -0
- package/src/ui/components/aside.tsx +96 -0
- package/src/ui/components/badge.tsx +42 -0
- package/src/ui/components/button.tsx +59 -0
- package/src/ui/components/card.tsx +106 -0
- package/src/ui/components/checkbox.tsx +37 -0
- package/src/ui/components/code-block.tsx +29 -0
- package/src/ui/components/combobox.tsx +119 -0
- package/src/ui/components/config-field.tsx +284 -0
- package/src/ui/components/dialog.tsx +137 -0
- package/src/ui/components/domain-editor.tsx +317 -0
- package/src/ui/components/domain-list.ts +205 -0
- package/src/ui/components/dropdown-menu.tsx +73 -0
- package/src/ui/components/empty-state.tsx +51 -0
- package/src/ui/components/highlight.ts +59 -0
- package/src/ui/components/icons.tsx +101 -0
- package/src/ui/components/image-posture.tsx +85 -0
- package/src/ui/components/input.tsx +28 -0
- package/src/ui/components/label.tsx +16 -0
- package/src/ui/components/line-chart.tsx +113 -0
- package/src/ui/components/page.tsx +39 -0
- package/src/ui/components/permission-row.tsx +262 -0
- package/src/ui/components/permission-section.tsx +51 -0
- package/src/ui/components/pie-chart.tsx +245 -0
- package/src/ui/components/schema-block.tsx +60 -0
- package/src/ui/components/separator.tsx +42 -0
- package/src/ui/components/sheet.tsx +130 -0
- package/src/ui/components/sidebar.tsx +650 -0
- package/src/ui/components/silo-picker.tsx +510 -0
- package/src/ui/components/skeleton.tsx +13 -0
- package/src/ui/components/sonner.tsx +33 -0
- package/src/ui/components/switch.tsx +58 -0
- package/src/ui/components/table-view.ts +219 -0
- package/src/ui/components/table.tsx +506 -0
- package/src/ui/components/tabs.tsx +59 -0
- package/src/ui/components/timeago.tsx +84 -0
- package/src/ui/components/tooltip.tsx +56 -0
- package/src/ui/components.json +21 -0
- package/src/ui/dev/dev-shell.tsx +145 -0
- package/src/ui/home/home-grid.tsx +177 -0
- package/src/ui/hooks/use-mobile.ts +19 -0
- package/src/ui/index.ts +189 -0
- package/src/ui/install/adapter.ts +193 -0
- package/src/ui/install/deploy-targets-picker.tsx +185 -0
- package/src/ui/install/install-app-panel.tsx +912 -0
- package/src/ui/install/install-error.tsx +62 -0
- package/src/ui/install/perms-inactive.ts +50 -0
- package/src/ui/install/required-fields.ts +37 -0
- package/src/ui/install/tabs-form.tsx +557 -0
- package/src/ui/tokens.css +279 -0
- package/src/ui/utils.ts +78 -0
- package/src/ui-error.ts +139 -0
- package/src/ui-runtime/dev-landing-entry.tsx +258 -0
- package/src/ui-runtime/index.ts +83 -0
- package/src/ui-runtime/install-entry.tsx +119 -0
- package/src/ui-runtime/mount.ts +367 -0
- package/src/ui-runtime/runtime.css +56 -0
- package/src/ui-surface.ts +451 -0
- package/src/valkey.ts +43 -0
- package/src/worker/extract-tar.ts +125 -0
- package/src/worker/identity.ts +183 -0
- package/src/worker/index.ts +16 -0
- package/src/worker/public-ip.ts +92 -0
- package/src/worker/queue.ts +54 -0
- package/src/worker/reconcile.ts +127 -0
- package/src/worker/start-worker.ts +227 -0
- package/src/worker/ui-proxy.ts +335 -0
- package/src/worker/uplink.ts +523 -0
- package/src/worker/validate.ts +34 -0
- package/src/worker/verify-relay.ts +102 -0
- package/src/worker-protocol/app-dirs.ts +94 -0
- package/src/worker-protocol/home-proxy-paths.ts +76 -0
- package/src/worker-protocol/index.ts +55 -0
- package/src/worker-protocol/ingress.ts +166 -0
- package/src/worker-protocol/logs-socket-path.ts +50 -0
- package/src/worker-protocol/networks.ts +39 -0
- package/src/worker-protocol/node-version.ts +70 -0
- package/src/worker-protocol/silo-paths.ts +114 -0
- package/src/worker-protocol/spawn-paths.ts +165 -0
- package/src/worker-protocol/stats-socket-path.ts +40 -0
- package/src/worker-protocol/update-files.ts +77 -0
- package/src/worker-protocol/uplink/constants.ts +86 -0
- package/src/worker-protocol/uplink/errors.ts +61 -0
- package/src/worker-protocol/uplink/frame.ts +114 -0
- package/src/worker-protocol/uplink/identity.ts +40 -0
- package/src/worker-protocol/uplink/index.ts +63 -0
- package/src/worker-protocol/uplink/methods.ts +462 -0
- package/src/worker-protocol/uplink/mux.ts +646 -0
- package/src/worker-protocol/uplink/protocol.ts +11 -0
- package/src/worker-protocol/uplink/rpc.ts +309 -0
- package/src/worker-protocol/uplink/session.ts +540 -0
- package/src/worker-protocol/uplink/test-support.ts +188 -0
- package/src/worker-protocol/worker-mount.ts +27 -0
- package/src/worker-protocol/worker-state.ts +190 -0
- package/src/xml.ts +49 -0
- package/src/yaml.ts +15 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Wire constants for `host.spawn()` — the worker's disposable-container
|
|
2
|
+
// broker, keyed per granted app. Mirrors `home-proxy-paths.ts`: one socket
|
|
3
|
+
// per app, so the worker attaches the caller's identity from *which* socket a
|
|
4
|
+
// request arrived on, never from the wire. The resolved
|
|
5
|
+
// `{socket, scratch, artifacts}` is built per-app by `host.spawn`'s resolver at
|
|
6
|
+
// eval, not a fixed constant — so the SDK only ships the placeholder + the path
|
|
7
|
+
// builders.
|
|
8
|
+
//
|
|
9
|
+
// Pure constants + string-join builders, no runtime imports (the
|
|
10
|
+
// `WorkerHostPath` import is type-only and erases), so schema/host.ts can pull
|
|
11
|
+
// `SPAWN_PLACEHOLDER` under QuickJS and any realm.ts consumer can read the
|
|
12
|
+
// resolved shape without dragging Deno deps.
|
|
13
|
+
|
|
14
|
+
import type { WorkerHostPath } from "./logs-socket-path.ts";
|
|
15
|
+
import type { WorkerMount } from "./worker-mount.ts";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Host directory the worker keeps every spawn app's socket + per-app tree
|
|
19
|
+
* under. Lives beneath `/opt/realm/worker/` — the one directory the worker
|
|
20
|
+
* container already shares with the host — so the inode the worker binds is
|
|
21
|
+
* the same one a granted app bind-mounts through the host docker daemon
|
|
22
|
+
* (mirrors `REALM_HOME_PROXY_DIR`).
|
|
23
|
+
*
|
|
24
|
+
* Two fixed sub-roots hang off this: `sock/` holds one per-app socket dir each
|
|
25
|
+
* (`sock/<appId>/spawn.sock`), and `data/` holds the *heavy* per-app build
|
|
26
|
+
* storage (scratch / artifacts / the broker-private back room). Keeping the
|
|
27
|
+
* sockets under their own `sock/` segment — a sibling of `data/` — means a
|
|
28
|
+
* per-app socket dir can never collide with the `data/<appId>` tree, even for
|
|
29
|
+
* an app literally named `data`. The `data/` segment is also the relocation
|
|
30
|
+
* seam: an operator who wants build storage on another drive mounts it at
|
|
31
|
+
* `${REALM_SPAWN_DIR}/data` (a fixed path, not a config knob), which keeps
|
|
32
|
+
* scratch / artifacts / private on the *same* filesystem — load-bearing,
|
|
33
|
+
* because the back-room seal renames between them and a rename must not cross a
|
|
34
|
+
* device boundary.
|
|
35
|
+
*/
|
|
36
|
+
export const REALM_SPAWN_DIR = "/opt/realm/worker/spawn";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Host directory holding one app's spawn broker socket
|
|
40
|
+
* (`/opt/realm/worker/spawn/sock/<appId>`). The socket sits alone inside it, so
|
|
41
|
+
* this is the path the app bind-mounts (a file bind-mount of a unix socket
|
|
42
|
+
* doesn't wire across containers on macOS Docker; the app mounts the dir and
|
|
43
|
+
* connects to the socket inside it). `host.spawn`'s resolver builds its
|
|
44
|
+
* `{ socket }` from this per-app dir at eval, and `collectAllowed` exempts it
|
|
45
|
+
* from the absolute-mount gate.
|
|
46
|
+
*/
|
|
47
|
+
export function spawnSocketDir(appId: string): WorkerHostPath {
|
|
48
|
+
return `${REALM_SPAWN_DIR}/sock/${appId}` as WorkerHostPath;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Host path of one app's spawn broker socket, inside {@link spawnSocketDir}
|
|
53
|
+
* (`/opt/realm/worker/spawn/sock/<appId>/spawn.sock`). The worker binds this in
|
|
54
|
+
* `reconcileSpawnSockets` and carries it on the wire; the app connects to it
|
|
55
|
+
* inside the mounted per-app socket dir to start one disposable box per job.
|
|
56
|
+
*/
|
|
57
|
+
export function spawnSocketPath(appId: string): string {
|
|
58
|
+
return `${spawnSocketDir(appId)}/spawn.sock`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Host path of one app's scratch dir
|
|
63
|
+
* (`/opt/realm/worker/spawn/data/<appId>/scratch`). The app stages a build
|
|
64
|
+
* context under a relative key here; the broker renames that one subdir into
|
|
65
|
+
* its per-box back room (see {@link spawnPrivatePath}) and mounts it into the
|
|
66
|
+
* box read-only. Per-app, so one spawn app can't read another's in-flight
|
|
67
|
+
* context. Under `data/` so it shares a filesystem with artifacts +
|
|
68
|
+
* private (the seal's rename must not cross a device boundary).
|
|
69
|
+
*/
|
|
70
|
+
export function spawnScratchPath(appId: string): WorkerHostPath {
|
|
71
|
+
return `${REALM_SPAWN_DIR}/data/${appId}/scratch` as WorkerHostPath;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Host path of one app's artifacts dir
|
|
76
|
+
* (`/opt/realm/worker/spawn/data/<appId>/artifacts`). On a successful build
|
|
77
|
+
* the broker renames the box's output here under a per-box subdir; the app
|
|
78
|
+
* reads the result back out of it (e.g. `output-<boxId>/image.tar`). A sibling
|
|
79
|
+
* of the scratch dir so input (the cloned source the app stages) and output
|
|
80
|
+
* (the built image) live in separate per-app mounts. Under `data/` so it
|
|
81
|
+
* shares a filesystem with scratch + private (the success rename must not
|
|
82
|
+
* cross a device boundary).
|
|
83
|
+
*/
|
|
84
|
+
export function spawnArtifactsPath(appId: string): WorkerHostPath {
|
|
85
|
+
return `${REALM_SPAWN_DIR}/data/${appId}/artifacts` as WorkerHostPath;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Host path of one app's broker-private "back room"
|
|
90
|
+
* (`/opt/realm/worker/spawn/data/<appId>/private`). The broker moves both bind
|
|
91
|
+
* sources (the validated context, and the box's output dir) into a per-box
|
|
92
|
+
* subdir here for the duration of a build — a dir the calling app has NO mount
|
|
93
|
+
* into. That's the seal: the broker re-validates the *moved* inode and
|
|
94
|
+
* runs the box against a path the caller can't reach, so the caller can't swap
|
|
95
|
+
* the validated dir for a symlink between validate and Docker mount.
|
|
96
|
+
*
|
|
97
|
+
* Broker-only: unlike socket / scratch / artifacts this is NOT part of
|
|
98
|
+
* {@link SpawnValue}, never travels the wire, and is never exempted from the
|
|
99
|
+
* absolute-mount gate — no app ever mounts it. It's a sibling of scratch +
|
|
100
|
+
* artifacts under `data/<appId>/` so the seal's renames stay same-filesystem,
|
|
101
|
+
* and so the worker's reconcile reclaims it with the rest of the app's `data/`
|
|
102
|
+
* subtree once the app is off the wire.
|
|
103
|
+
*/
|
|
104
|
+
export function spawnPrivatePath(appId: string): WorkerHostPath {
|
|
105
|
+
return `${REALM_SPAWN_DIR}/data/${appId}/private` as WorkerHostPath;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolved shape of a `host.spawn()` grant. All three fields are per-app paths
|
|
110
|
+
* built at eval (no fixed constant); `SPAWN_PLACEHOLDER` is type-level only,
|
|
111
|
+
* there so `ValueOf` can read this shape off `toValue` (mirrors
|
|
112
|
+
* `HomeApiAccessValue` / `HOME_API_PLACEHOLDER`). `socket` is the per-app
|
|
113
|
+
* DIRECTORY holding the broker socket — the app mounts it and connects to
|
|
114
|
+
* `<mount>/spawn.sock` inside (a file bind-mount of a unix socket doesn't wire
|
|
115
|
+
* across containers on macOS Docker). `scratch` is where the app stages a build
|
|
116
|
+
* context (input); `artifacts` is where it reads a build's output back. There
|
|
117
|
+
* is no network field: the box attaches to the untrusted-zone DMZ broker-side
|
|
118
|
+
* (see spawn-server/spec.ts), and that DMZ has no internal members — an app
|
|
119
|
+
* never puts a service on it, so it has nothing to wire.
|
|
120
|
+
*/
|
|
121
|
+
export interface SpawnValue {
|
|
122
|
+
readonly socket: WorkerMount;
|
|
123
|
+
readonly scratch: WorkerMount;
|
|
124
|
+
readonly artifacts: WorkerMount;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build the resolved `host.spawn` value for one app. `socket` is read-only -
|
|
129
|
+
* the app connects to the broker, it never writes to the socket's directory.
|
|
130
|
+
* `scratch` and `artifacts` are the app's own working storage on both sides of
|
|
131
|
+
* a build (stage a context in, read an image back out), so they stay writable.
|
|
132
|
+
*/
|
|
133
|
+
export function spawnMounts(appId: string): SpawnValue {
|
|
134
|
+
return {
|
|
135
|
+
socket: {
|
|
136
|
+
hostPath: spawnSocketDir(appId),
|
|
137
|
+
containerPath: "/run/realm/spawn-sock",
|
|
138
|
+
readOnly: true,
|
|
139
|
+
},
|
|
140
|
+
scratch: {
|
|
141
|
+
hostPath: spawnScratchPath(appId),
|
|
142
|
+
containerPath: "/run/realm/spawn",
|
|
143
|
+
readOnly: false,
|
|
144
|
+
},
|
|
145
|
+
artifacts: {
|
|
146
|
+
hostPath: spawnArtifactsPath(appId),
|
|
147
|
+
containerPath: "/run/realm/artifacts",
|
|
148
|
+
readOnly: false,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Type-level stand-in for `host.spawn()`'s resolved value, so `ValueOf` can
|
|
155
|
+
* read the shape. Empty paths stand in for "never resolved"; the container paths and the
|
|
156
|
+
* writability are already final, since those don't depend on the app.
|
|
157
|
+
*/
|
|
158
|
+
const UNFILLED = spawnMounts("");
|
|
159
|
+
const blank = (m: WorkerMount): WorkerMount => ({ ...m, hostPath: "" as WorkerHostPath });
|
|
160
|
+
|
|
161
|
+
export const SPAWN_PLACEHOLDER: SpawnValue = {
|
|
162
|
+
socket: blank(UNFILLED.socket),
|
|
163
|
+
scratch: blank(UNFILLED.scratch),
|
|
164
|
+
artifacts: blank(UNFILLED.artifacts),
|
|
165
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { WorkerHostPath } from "./logs-socket-path.ts";
|
|
2
|
+
import type { WorkerMount } from "./worker-mount.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Host directory the worker keeps the shared stats socket inside. This is the
|
|
6
|
+
* path an app mounts (the DIRECTORY, not the socket file): a file bind-mount
|
|
7
|
+
* of a unix socket does NOT wire to the live listener across containers on
|
|
8
|
+
* macOS Docker (connect succeeds, no bytes flow), so every broker socket lives
|
|
9
|
+
* alone in its own directory and the app mounts that directory. The value
|
|
10
|
+
* `r.host.stats()` resolves to (and the path the resolver exempts from the
|
|
11
|
+
* absolute-mount gate).
|
|
12
|
+
*
|
|
13
|
+
* Lives under `/opt/realm/worker/` for the same reason as
|
|
14
|
+
* {@link REALM_LOGS_DIR}: it's the one directory the worker container already
|
|
15
|
+
* shares with the host, so the socket the worker binds is the same inode an
|
|
16
|
+
* app container can bind-mount through the host docker daemon. It also puts
|
|
17
|
+
* the path under the `/opt/realm` entry of `HOST_DIRECTORY_BLOCKLIST`, so an
|
|
18
|
+
* operator can't route a Silo around the `host.stats`
|
|
19
|
+
* gate.
|
|
20
|
+
*/
|
|
21
|
+
export const REALM_STATS_DIR = "/opt/realm/worker/stats" as WorkerHostPath;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Host path of the stats socket, inside {@link REALM_STATS_DIR}. The worker's
|
|
25
|
+
* stats-server binds this; a consumer mounts {@link REALM_STATS_DIR} and
|
|
26
|
+
* connects to the socket at the corresponding path inside the mounted
|
|
27
|
+
* directory.
|
|
28
|
+
*/
|
|
29
|
+
export const REALM_STATS_SOCKET = `${REALM_STATS_DIR}/stats.sock`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* What `r.host.stats()` resolves to: the mount, not the bare path. Read-only
|
|
33
|
+
* for the same reason as {@link REALM_LOGS_MOUNT} - {@link REALM_STATS_DIR} is
|
|
34
|
+
* shared by every app holding the grant.
|
|
35
|
+
*/
|
|
36
|
+
export const REALM_STATS_MOUNT: WorkerMount = {
|
|
37
|
+
hostPath: REALM_STATS_DIR,
|
|
38
|
+
containerPath: "/run/realm/stats",
|
|
39
|
+
readOnly: true,
|
|
40
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// The two files an updater-driven compose project carries beside its `.env`:
|
|
2
|
+
// `.update`, the pending request and the updater's retry token, and
|
|
3
|
+
// `.update.error`, what the last failed attempt said. The worker reads them
|
|
4
|
+
// for its own box's `worker` and `ingress` projects and Home reads its own
|
|
5
|
+
// `etc/home`, so the one reader lives here rather than twice.
|
|
6
|
+
//
|
|
7
|
+
// A read, never a write: the worker writes `.update` (self-update.ts) and the
|
|
8
|
+
// updater writes `.update.error` (apply.rs). This module only says what is on
|
|
9
|
+
// disk right now.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Longest `error` this reader hands back, and so the longest an
|
|
13
|
+
* `update.status` carries: the tail of `.update.error`. It lives beside the
|
|
14
|
+
* reader rather than in the uplink catalogue so this module, on the
|
|
15
|
+
* author-safe barrel, imports nothing from `uplink/`.
|
|
16
|
+
*/
|
|
17
|
+
export const UPDATE_ERROR_MAX_CHARS = 2048;
|
|
18
|
+
|
|
19
|
+
/** The two files an updater-driven project carries beside its `.env`. */
|
|
20
|
+
export interface UpdateFiles {
|
|
21
|
+
/** `.update` exists (the pending request / retry token). */
|
|
22
|
+
pending: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Trimmed `.update.error` content, or null when absent or empty. Capped at
|
|
25
|
+
* `UPDATE_ERROR_MAX_CHARS` from the end, because the last lines of a compose
|
|
26
|
+
* failure are the ones that say why.
|
|
27
|
+
*/
|
|
28
|
+
error: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Anything bigger is junk, not a message: the updater bounds what it writes
|
|
33
|
+
* well under this, so a file over it was put there by something else.
|
|
34
|
+
*/
|
|
35
|
+
const MAX_ERROR_FILE_BYTES = 16 * 1024;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Read `<dir>/.update` and `<dir>/.update.error`.
|
|
39
|
+
*
|
|
40
|
+
* `Deno.*` is touched inside the body only: this module is reachable from the
|
|
41
|
+
* author-safe `worker-protocol` barrel, which an author's realm.ts may import
|
|
42
|
+
* into QuickJS, where `Deno` does not exist (`quickjs-safe.test.ts`).
|
|
43
|
+
*
|
|
44
|
+
* Never throws for a missing file or dir - that is `{ pending: false, error:
|
|
45
|
+
* null }`, the shape of a project with nothing going on. The worker's
|
|
46
|
+
* `.tmp-*` siblings from a tmp-then-rename in flight are not these files and
|
|
47
|
+
* are never read.
|
|
48
|
+
*/
|
|
49
|
+
export async function readUpdateFiles(dir: string): Promise<UpdateFiles> {
|
|
50
|
+
const [pending, error] = await Promise.all([
|
|
51
|
+
isFile(`${dir}/.update`),
|
|
52
|
+
readError(`${dir}/.update.error`),
|
|
53
|
+
]);
|
|
54
|
+
return { pending, error };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function isFile(path: string): Promise<boolean> {
|
|
58
|
+
try {
|
|
59
|
+
return (await Deno.stat(path)).isFile;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function readError(path: string): Promise<string | null> {
|
|
66
|
+
let text: string;
|
|
67
|
+
try {
|
|
68
|
+
const stat = await Deno.stat(path);
|
|
69
|
+
if (!stat.isFile || stat.size > MAX_ERROR_FILE_BYTES) return null;
|
|
70
|
+
text = await Deno.readTextFile(path);
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const trimmed = text.trim();
|
|
75
|
+
if (trimmed === "") return null;
|
|
76
|
+
return trimmed.length > UPDATE_ERROR_MAX_CHARS ? trimmed.slice(-UPDATE_ERROR_MAX_CHARS) : trimmed;
|
|
77
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// The numbers this layer owns, from `todo/ideas/uplink.md` § Constants.
|
|
2
|
+
// That table is the authority; change it there, not here.
|
|
3
|
+
|
|
4
|
+
/** `[len u16][streamId u32][type u8]`. */
|
|
5
|
+
export const FRAME_HEADER_BYTES = 7;
|
|
6
|
+
|
|
7
|
+
/** What `len` can express. `encodeFrame` refuses more; the wire rule is below. */
|
|
8
|
+
export const MAX_FRAME_LEN = 65535;
|
|
9
|
+
|
|
10
|
+
/** OPEN headers and DATA fragments are both capped here, in both directions. */
|
|
11
|
+
export const MAX_PAYLOAD_BYTES = 16 * 1024;
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_LIMITS = {
|
|
14
|
+
fragmentSize: 16 * 1024,
|
|
15
|
+
initialWindow: 256 * 1024,
|
|
16
|
+
maxStreams: 64,
|
|
17
|
+
maxInFlight: 8 * 1024 * 1024,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
/** PING/PONG ride stream 0 and carry 8 opaque bytes. */
|
|
21
|
+
export const CONTROL_STREAM_ID = 0;
|
|
22
|
+
export const PING_PAYLOAD_BYTES = 8;
|
|
23
|
+
|
|
24
|
+
/** WINDOW carries one u32 credit increment. */
|
|
25
|
+
export const WINDOW_PAYLOAD_BYTES = 4;
|
|
26
|
+
|
|
27
|
+
/** The RPC response head is `[u16 headLen][head JSON]`, so the head fits a u16. */
|
|
28
|
+
export const MAX_RPC_HEAD_BYTES = 65535;
|
|
29
|
+
|
|
30
|
+
/** RESET reasons are diagnostics, not payloads; long ones are cut here. */
|
|
31
|
+
export const MAX_RESET_REASON_CHARS = 512;
|
|
32
|
+
|
|
33
|
+
// --- the session below the mux ---------------------------------------
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The Noise prologue, `realm-uplink/1`. It versions the bytes below the RPC -
|
|
37
|
+
* the handshake, the frames, the mux - and a peer on another number fails the
|
|
38
|
+
* handshake. The catalogue above those bytes is versioned separately, by
|
|
39
|
+
* `PROTOCOL_VERSION` in `protocol.ts`, which two peers compare in `hello` and
|
|
40
|
+
* can act on.
|
|
41
|
+
*
|
|
42
|
+
* Those are different failures on purpose. A `PROTOCOL_VERSION` mismatch still
|
|
43
|
+
* gives both sides a session, so Home can tell the box to update over it. A
|
|
44
|
+
* prologue mismatch gives them nothing: there is no session to carry the
|
|
45
|
+
* instruction, and moving that box takes the updater's `.update` file by hand.
|
|
46
|
+
* So this number changes only when the transport itself does.
|
|
47
|
+
*
|
|
48
|
+
* Spelled byte by byte rather than through `encodeUtf8` so it can be a
|
|
49
|
+
* module-load constant: this file is reachable from an author bundle, where
|
|
50
|
+
* `TextEncoder` does not exist.
|
|
51
|
+
*/
|
|
52
|
+
export const UPLINK_PROLOGUE: Uint8Array = Uint8Array.from("realm-uplink/1", (c) =>
|
|
53
|
+
c.charCodeAt(0),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
/** A Noise message is at most 65535 bytes including its 16-byte tag. */
|
|
57
|
+
export const NOISE_MAX_PAYLOAD = 65519;
|
|
58
|
+
|
|
59
|
+
/** Socket open and all three handshake messages, together. */
|
|
60
|
+
export const HANDSHAKE_TIMEOUT_MS = 5000;
|
|
61
|
+
|
|
62
|
+
/** `WebSocket.send` buffers unboundedly; the session stops filling past this. */
|
|
63
|
+
export const SOCKET_HIGH_WATER = 256 * 1024;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Undecrypted messages queued ahead of the mux. A WHATWG socket cannot pause
|
|
67
|
+
* its reads, so a peer that outruns decryption is closed rather than buffered.
|
|
68
|
+
*/
|
|
69
|
+
export const SOCKET_INBOUND_CAP = 16 * 1024 * 1024;
|
|
70
|
+
|
|
71
|
+
// --- liveness ---------------------------------------------------------
|
|
72
|
+
// The session runs none of these. The worker's client loop owns the ping
|
|
73
|
+
// cadence, the pong deadline and the reconnect; Home's session registry owns
|
|
74
|
+
// the idle deadline. They are here so both sides read one number.
|
|
75
|
+
|
|
76
|
+
export const PING_INTERVAL_MS = 25_000;
|
|
77
|
+
export const PONG_TIMEOUT_MS = 10_000;
|
|
78
|
+
export const IDLE_TIMEOUT_MS = 35_000;
|
|
79
|
+
|
|
80
|
+
/** Rekey by reconnecting, so no session holds one key forever. */
|
|
81
|
+
export const REKEY_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
82
|
+
export const REKEY_JITTER_MS = 2 * 60 * 60 * 1000;
|
|
83
|
+
|
|
84
|
+
/** Reconnect backoff: doubling from the base to the cap, full jitter. */
|
|
85
|
+
export const BACKOFF_BASE_MS = 1000;
|
|
86
|
+
export const BACKOFF_CAP_MS = 30_000;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Everything the mux and the RPC layer reject with. One base so a caller
|
|
2
|
+
// can tell an uplink failure from an application one in a single check.
|
|
3
|
+
|
|
4
|
+
export class UplinkError extends Error {}
|
|
5
|
+
|
|
6
|
+
/** The peer violated the framing. The mux closes the session on one of these. */
|
|
7
|
+
export class UplinkProtocolError extends UplinkError {
|
|
8
|
+
constructor(message: string) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "UplinkProtocolError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** The session is gone; every open and pending stream fails with this. */
|
|
15
|
+
export class SessionClosed extends UplinkError {
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
|
|
18
|
+
constructor(reason: string) {
|
|
19
|
+
super(`uplink session closed: ${reason}`);
|
|
20
|
+
this.name = "SessionClosed";
|
|
21
|
+
this.reason = reason;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One stream was aborted, in either direction. Carries the peer's reason. */
|
|
26
|
+
export class StreamReset extends UplinkError {
|
|
27
|
+
readonly reason: string;
|
|
28
|
+
|
|
29
|
+
constructor(reason: string) {
|
|
30
|
+
super(reason === "" ? "stream reset" : `stream reset: ${reason}`);
|
|
31
|
+
this.name = "StreamReset";
|
|
32
|
+
this.reason = reason;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The responder's static key is not the one the worker pinned. Raised after
|
|
38
|
+
* handshake message 2 and before message 3, so the worker's own static never
|
|
39
|
+
* goes out. Not retryable, and it is the worker's loop — not this layer —
|
|
40
|
+
* that must refuse to retry (`todo/ideas/uplink.md` § What it guarantees).
|
|
41
|
+
*/
|
|
42
|
+
export class HomeKeyMismatch extends UplinkError {
|
|
43
|
+
/** Hex, for a log line the operator can compare against the paste. */
|
|
44
|
+
readonly expected: string;
|
|
45
|
+
readonly actual: string;
|
|
46
|
+
|
|
47
|
+
constructor(expected: Uint8Array, actual: Uint8Array) {
|
|
48
|
+
const want = hex(expected);
|
|
49
|
+
const got = hex(actual);
|
|
50
|
+
super(`Home answered with static key ${got}, not the pinned ${want}`);
|
|
51
|
+
this.name = "HomeKeyMismatch";
|
|
52
|
+
this.expected = want;
|
|
53
|
+
this.actual = got;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Not `Uint8Array.prototype.toHex`: this module is reachable from an author
|
|
58
|
+
// bundle, and QuickJS has no such method.
|
|
59
|
+
function hex(bytes: Uint8Array): string {
|
|
60
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
61
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// The uplink frame codec: `[len u16][streamId u32][type u8][payload]`,
|
|
2
|
+
// big-endian, `len` counting payload only. The byte stream underneath may
|
|
3
|
+
// split or coalesce arbitrarily, so `FrameParser` is fed raw chunks and
|
|
4
|
+
// emits whole frames, keeping any partial tail for the next chunk.
|
|
5
|
+
|
|
6
|
+
import { FRAME_HEADER_BYTES, MAX_FRAME_LEN, MAX_PAYLOAD_BYTES } from "./constants.ts";
|
|
7
|
+
import { UplinkProtocolError } from "./errors.ts";
|
|
8
|
+
|
|
9
|
+
export const FrameType = {
|
|
10
|
+
OPEN: 1,
|
|
11
|
+
DATA: 2,
|
|
12
|
+
END: 3,
|
|
13
|
+
RESET: 4,
|
|
14
|
+
WINDOW: 5,
|
|
15
|
+
PING: 6,
|
|
16
|
+
PONG: 7,
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export type FrameType = (typeof FrameType)[keyof typeof FrameType];
|
|
20
|
+
|
|
21
|
+
const FRAME_TYPE_NAMES: Record<number, string> = Object.fromEntries(
|
|
22
|
+
Object.entries(FrameType).map(([name, id]) => [id, name]),
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export function frameTypeName(type: number): string {
|
|
26
|
+
return FRAME_TYPE_NAMES[type] ?? `type ${type}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Frame {
|
|
30
|
+
type: FrameType;
|
|
31
|
+
streamId: number;
|
|
32
|
+
payload: Uint8Array;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const EMPTY_PAYLOAD = new Uint8Array(0);
|
|
36
|
+
|
|
37
|
+
export function encodeFrame(type: FrameType, streamId: number, payload: Uint8Array): Uint8Array {
|
|
38
|
+
if (payload.length > MAX_FRAME_LEN) {
|
|
39
|
+
throw new RangeError(`frame payload ${payload.length} exceeds ${MAX_FRAME_LEN} bytes`);
|
|
40
|
+
}
|
|
41
|
+
const out = new Uint8Array(FRAME_HEADER_BYTES + payload.length);
|
|
42
|
+
const view = new DataView(out.buffer);
|
|
43
|
+
view.setUint16(0, payload.length);
|
|
44
|
+
view.setUint32(2, streamId);
|
|
45
|
+
view.setUint8(6, type);
|
|
46
|
+
out.set(payload, FRAME_HEADER_BYTES);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class FrameParser {
|
|
51
|
+
#pending = EMPTY_PAYLOAD;
|
|
52
|
+
|
|
53
|
+
/** Throws `UplinkProtocolError` on anything the framing forbids. */
|
|
54
|
+
push(chunk: Uint8Array): Frame[] {
|
|
55
|
+
const buf = this.#pending.length === 0 ? chunk : concatBytes(this.#pending, chunk);
|
|
56
|
+
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
57
|
+
const frames: Frame[] = [];
|
|
58
|
+
let offset = 0;
|
|
59
|
+
while (buf.length - offset >= FRAME_HEADER_BYTES) {
|
|
60
|
+
const len = view.getUint16(offset);
|
|
61
|
+
const total = FRAME_HEADER_BYTES + len;
|
|
62
|
+
if (buf.length - offset < total) break;
|
|
63
|
+
const type = view.getUint8(offset + 6);
|
|
64
|
+
if (FRAME_TYPE_NAMES[type] === undefined) {
|
|
65
|
+
throw new UplinkProtocolError(`unknown frame type ${type}`);
|
|
66
|
+
}
|
|
67
|
+
// The `len` field can express 64 KiB, the protocol allows 16.
|
|
68
|
+
if (len > MAX_PAYLOAD_BYTES) {
|
|
69
|
+
throw new UplinkProtocolError(`frame payload ${len} exceeds ${MAX_PAYLOAD_BYTES} bytes`);
|
|
70
|
+
}
|
|
71
|
+
frames.push({
|
|
72
|
+
type: type as FrameType,
|
|
73
|
+
streamId: view.getUint32(offset + 2),
|
|
74
|
+
// Copied: the payload outlives `chunk` once it lands in a stream queue.
|
|
75
|
+
payload: buf.slice(offset + FRAME_HEADER_BYTES, offset + total),
|
|
76
|
+
});
|
|
77
|
+
offset += total;
|
|
78
|
+
}
|
|
79
|
+
this.#pending = offset === buf.length ? EMPTY_PAYLOAD : buf.slice(offset);
|
|
80
|
+
return frames;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function concatBytes(a: Uint8Array, b: Uint8Array): Uint8Array {
|
|
85
|
+
const out = new Uint8Array(a.length + b.length);
|
|
86
|
+
out.set(a);
|
|
87
|
+
out.set(b, a.length);
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function encodeU32(value: number): Uint8Array {
|
|
92
|
+
const out = new Uint8Array(4);
|
|
93
|
+
new DataView(out.buffer).setUint32(0, value);
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function decodeU32(bytes: Uint8Array): number {
|
|
98
|
+
return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Lazy so no Web global is touched at module load: this file is reachable
|
|
102
|
+
// from the SDK graph that gets inlined into QuickJS author bundles.
|
|
103
|
+
let encoder: TextEncoder | undefined;
|
|
104
|
+
let decoder: TextDecoder | undefined;
|
|
105
|
+
|
|
106
|
+
export function encodeUtf8(text: string): Uint8Array {
|
|
107
|
+
encoder ??= new TextEncoder();
|
|
108
|
+
return encoder.encode(text);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function decodeUtf8(bytes: Uint8Array): string {
|
|
112
|
+
decoder ??= new TextDecoder();
|
|
113
|
+
return decoder.decode(bytes);
|
|
114
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// The one text encoding of an uplink static public key, shared by everything
|
|
2
|
+
// that writes or reads one: the `worker.public_key` column, `uplink.pub` on
|
|
3
|
+
// Home's box, `home.pub` in the worker dir, and the enrollment token
|
|
4
|
+
// `{homePublicKey}.{otp}`. Unpadded base64url, 43 chars for 32 bytes.
|
|
5
|
+
|
|
6
|
+
import { KEY_LENGTH } from "@yourrealm/noise";
|
|
7
|
+
|
|
8
|
+
const ENCODED_LENGTH = 43;
|
|
9
|
+
|
|
10
|
+
export function encodePublicKey(key: Uint8Array): string {
|
|
11
|
+
if (key.length !== KEY_LENGTH) {
|
|
12
|
+
throw new RangeError(`public key must be ${KEY_LENGTH} bytes, got ${key.length}`);
|
|
13
|
+
}
|
|
14
|
+
return key.toBase64({ alphabet: "base64url", omitPadding: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Returns null for anything that is not exactly one encoded key. */
|
|
18
|
+
export function decodePublicKey(text: string): Uint8Array | null {
|
|
19
|
+
if (text.length !== ENCODED_LENGTH || !/^[A-Za-z0-9_-]+$/.test(text)) return null;
|
|
20
|
+
try {
|
|
21
|
+
const key = Uint8Array.fromBase64(text, { alphabet: "base64url" });
|
|
22
|
+
return key.length === KEY_LENGTH ? key : null;
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Splits `{homePublicKey}.{otp}`; a bare OTP has no key half. */
|
|
29
|
+
export function parseEnrollmentToken(
|
|
30
|
+
token: string,
|
|
31
|
+
): { homePublicKey: Uint8Array | null; otp: string } | null {
|
|
32
|
+
const trimmed = token.trim();
|
|
33
|
+
if (trimmed === "") return null;
|
|
34
|
+
const dot = trimmed.indexOf(".");
|
|
35
|
+
if (dot === -1) return { homePublicKey: null, otp: trimmed };
|
|
36
|
+
const homePublicKey = decodePublicKey(trimmed.slice(0, dot));
|
|
37
|
+
const otp = trimmed.slice(dot + 1);
|
|
38
|
+
if (homePublicKey === null || otp === "") return null;
|
|
39
|
+
return { homePublicKey, otp };
|
|
40
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @yourrealm/sdk/worker-protocol/uplink — one uplink session and everything
|
|
2
|
+
// that rides it: the Noise handshake over a WebSocket, the mux, the RPC
|
|
3
|
+
// framing and the method catalogue.
|
|
4
|
+
//
|
|
5
|
+
// The layers below the session know nothing about Noise, WebSockets, Home or
|
|
6
|
+
// workers: `Mux` and `Rpc` run over any `ByteDuplex` and own no timers.
|
|
7
|
+
// `connectUplink` / `acceptUplink` are what join them to a socket.
|
|
8
|
+
|
|
9
|
+
export { Mux } from "./mux.ts";
|
|
10
|
+
export type { ByteDuplex, Limits, MuxSide, Stream } from "./mux.ts";
|
|
11
|
+
export { jsonResponse, readJson, Rpc } from "./rpc.ts";
|
|
12
|
+
export type { RpcHandler, RpcRequest, RpcResponse } from "./rpc.ts";
|
|
13
|
+
export { acceptUplink, connectUplink } from "./session.ts";
|
|
14
|
+
export type { SessionOptions, UplinkSession } from "./session.ts";
|
|
15
|
+
export { decodePublicKey, encodePublicKey, parseEnrollmentToken } from "./identity.ts";
|
|
16
|
+
export { PROTOCOL_VERSION } from "./protocol.ts";
|
|
17
|
+
export {
|
|
18
|
+
APPLY_MESSAGE_MAX_CHARS,
|
|
19
|
+
appApplyResultSchema,
|
|
20
|
+
callJson,
|
|
21
|
+
callUiProxy,
|
|
22
|
+
ETAG_HEADER,
|
|
23
|
+
jsonHandler,
|
|
24
|
+
MethodError,
|
|
25
|
+
methods,
|
|
26
|
+
pullState,
|
|
27
|
+
UPDATE_ERROR_MAX_CHARS,
|
|
28
|
+
UPDATE_PROJECTS,
|
|
29
|
+
updateStatusSchema,
|
|
30
|
+
} from "./methods.ts";
|
|
31
|
+
export type {
|
|
32
|
+
AppApplyResult,
|
|
33
|
+
JsonMethod,
|
|
34
|
+
MethodName,
|
|
35
|
+
Params,
|
|
36
|
+
Result,
|
|
37
|
+
StatePull,
|
|
38
|
+
UiProxyResult,
|
|
39
|
+
UpdateProject,
|
|
40
|
+
UpdateStatus,
|
|
41
|
+
} from "./methods.ts";
|
|
42
|
+
export {
|
|
43
|
+
HomeKeyMismatch,
|
|
44
|
+
SessionClosed,
|
|
45
|
+
StreamReset,
|
|
46
|
+
UplinkError,
|
|
47
|
+
UplinkProtocolError,
|
|
48
|
+
} from "./errors.ts";
|
|
49
|
+
// The framing itself stays inside: callers get streams, not frames.
|
|
50
|
+
export {
|
|
51
|
+
BACKOFF_BASE_MS,
|
|
52
|
+
BACKOFF_CAP_MS,
|
|
53
|
+
DEFAULT_LIMITS,
|
|
54
|
+
HANDSHAKE_TIMEOUT_MS,
|
|
55
|
+
IDLE_TIMEOUT_MS,
|
|
56
|
+
MAX_PAYLOAD_BYTES,
|
|
57
|
+
PING_INTERVAL_MS,
|
|
58
|
+
PONG_TIMEOUT_MS,
|
|
59
|
+
REKEY_INTERVAL_MS,
|
|
60
|
+
REKEY_JITTER_MS,
|
|
61
|
+
SOCKET_HIGH_WATER,
|
|
62
|
+
UPLINK_PROLOGUE,
|
|
63
|
+
} from "./constants.ts";
|