@capybara-hq/kernel 0.1.0-rc.12 → 0.1.0-rc.13
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/admin/admin.schemas.d.ts +13 -0
- package/dist/agents/agent-lookup.d.ts +5 -0
- package/dist/agents/agent-lookup.js +15 -0
- package/dist/background/background.module.js +2 -2
- package/dist/background/launch-lock.d.ts +1 -1
- package/dist/background/launch-lock.js +1 -1
- package/dist/background/poll.job.d.ts +2 -2
- package/dist/background/poll.job.js +10 -10
- package/dist/background/stop.d.ts +1 -1
- package/dist/background/stop.js +3 -3
- package/dist/background/store.d.ts +5 -5
- package/dist/background/store.js +5 -5
- package/dist/background/tasks.service.d.ts +6 -6
- package/dist/background/tasks.service.js +13 -13
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/channels/channels.module.js +2 -2
- package/dist/channels/inbound.d.ts +8 -8
- package/dist/channels/inbound.js +31 -31
- package/dist/channels/phrases.d.ts +27 -27
- package/dist/channels/phrases.js +27 -27
- package/dist/config/docs.js +1 -1
- package/dist/config/schema.d.ts +2 -2
- package/dist/config/schema.js +21 -21
- package/dist/conversations/conversations.controller.d.ts +11 -11
- package/dist/conversations/conversations.controller.js +15 -15
- package/dist/conversations/conversations.module.d.ts +1 -1
- package/dist/conversations/conversations.module.js +7 -7
- package/dist/conversations/conversations.schemas.js +1 -1
- package/dist/conversations/conversations.service.d.ts +14 -14
- package/dist/conversations/conversations.service.js +19 -19
- package/dist/conversations/{environment-status.d.ts → device-status.d.ts} +9 -9
- package/dist/conversations/{environment-status.js → device-status.js} +18 -18
- package/dist/conversations/event-log.d.ts +1 -1
- package/dist/conversations/event-log.js +1 -1
- package/dist/conversations/events.d.ts +1 -1
- package/dist/conversations/events.js +2 -2
- package/dist/ctx/create-ctx.d.ts +10 -1
- package/dist/ctx/create-ctx.js +86 -25
- package/dist/db/kernel-schema.d.ts +26 -23
- package/dist/{environments → devices}/access.d.ts +19 -19
- package/dist/{environments → devices}/access.js +28 -28
- package/dist/devices/conversation-devices.d.ts +75 -0
- package/dist/{environments/conversation-environments.js → devices/conversation-devices.js} +71 -72
- package/dist/devices/devices.controller.d.ts +36 -0
- package/dist/{environments/environments.controller.js → devices/devices.controller.js} +37 -37
- package/dist/devices/devices.module.d.ts +5 -13
- package/dist/devices/devices.module.js +16 -29
- package/dist/devices/devices.schemas.d.ts +3 -0
- package/dist/devices/devices.schemas.js +14 -0
- package/dist/{environments/environments.service.d.ts → devices/devices.service.d.ts} +70 -70
- package/dist/{environments/environments.service.js → devices/devices.service.js} +122 -122
- package/dist/devices/dto.d.ts +13 -54
- package/dist/devices/dto.js +7 -39
- package/dist/devices/label.d.ts +11 -0
- package/dist/{environments → devices}/label.js +7 -7
- package/dist/{environments → devices}/legacy-cleanup.job.d.ts +3 -3
- package/dist/{environments → devices}/legacy-cleanup.job.js +3 -3
- package/dist/errors.d.ts +25 -25
- package/dist/errors.js +25 -25
- package/dist/evals/recorded-tools.d.ts +2 -2
- package/dist/evals/recorded-tools.js +6 -6
- package/dist/extensions/admin-ops.js +1 -0
- package/dist/extensions/admin-view.js +2 -0
- package/dist/extensions/effective-config.d.ts +6 -0
- package/dist/extensions/effective-config.js +18 -7
- package/dist/extensions/host.d.ts +13 -2
- package/dist/extensions/host.js +24 -3
- package/dist/extensions/instance.d.ts +12 -1
- package/dist/extensions/instance.js +13 -0
- package/dist/extensions/permission-view.d.ts +10 -1
- package/dist/files/previewers.d.ts +1 -1
- package/dist/files/previewers.js +1 -1
- package/dist/http/api.module.js +10 -6
- package/dist/http/gateway-routes.d.ts +1 -1
- package/dist/http/gateway-routes.js +1 -1
- package/dist/identity/settings.d.ts +32 -0
- package/dist/identity/settings.js +26 -2
- package/dist/infra/config/kernel-config.service.js +4 -4
- package/dist/kernel.module.js +3 -3
- package/dist/{devices → local-devices}/device-auth.controller.d.ts +6 -6
- package/dist/{devices → local-devices}/device-auth.controller.js +5 -5
- package/dist/{devices → local-devices}/device-auth.d.ts +20 -20
- package/dist/{devices → local-devices}/device-auth.js +51 -53
- package/dist/local-devices/device-auth.module.d.ts +7 -0
- package/dist/{devices → local-devices}/device-auth.module.js +5 -5
- package/dist/{devices → local-devices}/device-routes.d.ts +3 -3
- package/dist/{devices → local-devices}/device-routes.js +2 -2
- package/dist/local-devices/dto.d.ts +66 -0
- package/dist/local-devices/dto.js +44 -0
- package/dist/{devices → local-devices}/keys.d.ts +11 -11
- package/dist/{devices → local-devices}/keys.js +11 -11
- package/dist/{devices/device-gateway.d.ts → local-devices/local-device-gateway.d.ts} +14 -14
- package/dist/{devices/device-gateway.js → local-devices/local-device-gateway.js} +44 -44
- package/dist/{devices/device-provider.d.ts → local-devices/local-device-provider.d.ts} +15 -15
- package/dist/{devices/device-provider.js → local-devices/local-device-provider.js} +23 -23
- package/dist/local-devices/local-devices.module.d.ts +16 -0
- package/dist/local-devices/local-devices.module.js +44 -0
- package/dist/{devices → local-devices}/machines.d.ts +26 -26
- package/dist/{devices → local-devices}/machines.js +71 -71
- package/dist/{devices → local-devices}/relay.d.ts +15 -15
- package/dist/{devices → local-devices}/relay.js +55 -55
- package/dist/{devices → local-devices}/targets.d.ts +8 -8
- package/dist/{devices → local-devices}/targets.js +14 -14
- package/dist/orgs/offboarding.d.ts +6 -6
- package/dist/orgs/offboarding.js +12 -12
- package/dist/orgs/settings.service.d.ts +28 -0
- package/dist/orgs/settings.service.js +20 -1
- package/dist/platform/exclusions.js +38 -3
- package/dist/platform/operation.js +6 -2
- package/dist/platform/ops/admin.ops.js +8 -2
- package/dist/platform/ops/conversations.ops.d.ts +3 -3
- package/dist/platform/ops/conversations.ops.js +9 -9
- package/dist/platform/ops/devices.ops.d.ts +26 -0
- package/dist/platform/ops/devices.ops.js +311 -0
- package/dist/platform/ops/org-settings.ops.js +2 -2
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/ui.ops.d.ts +8 -0
- package/dist/platform/ops/ui.ops.js +41 -0
- package/dist/platform/platform-ops.module.d.ts +3 -2
- package/dist/platform/platform-ops.module.js +8 -5
- package/dist/platform/route-schemas.js +6 -3
- package/dist/roles/gateway-role.d.ts +3 -3
- package/dist/roles/gateway-role.js +4 -4
- package/dist/roles/gateway.module.d.ts +1 -1
- package/dist/roles/gateway.module.js +5 -5
- package/dist/roles/worker.module.js +1 -1
- package/dist/runtime/context/build.d.ts +1 -1
- package/dist/runtime/context/build.js +6 -6
- package/dist/runtime/context/compaction.js +1 -1
- package/dist/runtime/context/{environment-paths.d.ts → device-paths.d.ts} +11 -11
- package/dist/runtime/context/{environment-paths.js → device-paths.js} +13 -13
- package/dist/runtime/maintenance.d.ts +1 -1
- package/dist/sandbox/jobs.d.ts +13 -13
- package/dist/sandbox/jobs.js +20 -20
- package/dist/sandbox/leases.d.ts +9 -9
- package/dist/sandbox/leases.js +14 -14
- package/dist/sandbox/providers.d.ts +8 -8
- package/dist/sandbox/providers.js +8 -8
- package/dist/sandbox/sandbox.module.d.ts +1 -1
- package/dist/sandbox/sandbox.module.js +1 -1
- package/dist/sandbox/sandbox.service.d.ts +76 -76
- package/dist/sandbox/sandbox.service.js +230 -230
- package/dist/tools/builtins/builtin-tools.d.ts +2 -2
- package/dist/tools/builtins/builtin-tools.js +5 -5
- package/dist/tools/builtins/platform.js +1 -1
- package/dist/tools/builtins/sandbox/bash.js +6 -6
- package/dist/tools/builtins/sandbox/common.d.ts +25 -25
- package/dist/tools/builtins/sandbox/common.js +51 -51
- package/dist/tools/builtins/sandbox/edit.js +2 -2
- package/dist/tools/builtins/sandbox/index.d.ts +1 -1
- package/dist/tools/builtins/sandbox/index.js +1 -1
- package/dist/tools/builtins/sandbox/task-output.d.ts +6 -6
- package/dist/tools/builtins/sandbox/task-output.js +10 -10
- package/dist/tools/builtins/sandbox/task-stop.js +2 -2
- package/dist/tools/builtins/sandbox/write.js +2 -2
- package/dist/tools/builtins/skill-load.d.ts +1 -1
- package/dist/tools/builtins/skill-load.js +2 -2
- package/dist/tools/pipeline.js +4 -3
- package/dist/tools/registry.d.ts +2 -2
- package/dist/tools/tools.module.js +3 -3
- package/dist/ui/admin-ui.controller.d.ts +56 -0
- package/dist/ui/admin-ui.controller.js +192 -0
- package/dist/ui/arrangement.d.ts +74 -0
- package/dist/ui/arrangement.js +98 -0
- package/dist/ui/embed-token.d.ts +2 -2
- package/dist/ui/embed-token.js +30 -5
- package/dist/ui/registry.d.ts +29 -14
- package/dist/ui/registry.js +31 -10
- package/dist/ui/slots.controller.d.ts +11 -0
- package/dist/ui/slots.controller.js +25 -0
- package/dist/ui/ui-stream.d.ts +20 -3
- package/dist/ui/ui-stream.js +25 -7
- package/dist/ui/ui.schemas.d.ts +66 -0
- package/dist/ui/ui.schemas.js +65 -0
- package/dist/ui/views.controller.d.ts +35 -49
- package/dist/ui/views.controller.js +48 -70
- package/dist/ui/views.d.ts +84 -18
- package/dist/ui/views.js +201 -38
- package/package.json +3 -3
- package/src/migrations/0105_devices_rename.sql +136 -0
- package/dist/devices/device-auth.module.d.ts +0 -7
- package/dist/environments/conversation-environments.d.ts +0 -75
- package/dist/environments/dto.d.ts +0 -25
- package/dist/environments/dto.js +0 -12
- package/dist/environments/environments.controller.d.ts +0 -36
- package/dist/environments/environments.module.d.ts +0 -8
- package/dist/environments/environments.module.js +0 -31
- package/dist/environments/environments.schemas.d.ts +0 -3
- package/dist/environments/environments.schemas.js +0 -14
- package/dist/environments/label.d.ts +0 -11
- package/dist/platform/ops/environments.ops.d.ts +0 -26
- package/dist/platform/ops/environments.ops.js +0 -311
|
@@ -9,7 +9,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
9
9
|
};
|
|
10
10
|
import { randomBytes } from "node:crypto";
|
|
11
11
|
import { Inject, Injectable } from "@nestjs/common";
|
|
12
|
-
import {
|
|
12
|
+
import { DevicesService } from "../devices/devices.service.js";
|
|
13
13
|
import { ApiError } from "../errors.js";
|
|
14
14
|
import { audit } from "../identity/audit.js";
|
|
15
15
|
import { sha256Hex } from "../identity/hash.js";
|
|
@@ -19,8 +19,8 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
|
19
19
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
20
20
|
import { KERNEL_LOGGER, REDIS } from "../infra/tokens.js";
|
|
21
21
|
import { authorizeOrgAction } from "../orgs/org.guard.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
22
|
+
import { LOCAL_PROVIDER } from "../sandbox/providers.js";
|
|
23
|
+
import { deviceNotFound } from "../sandbox/sandbox.service.js";
|
|
24
24
|
import { MachinesService } from "./machines.js";
|
|
25
25
|
/** The 20 consonants a user code is made of (no vowels: no words; no look-alike letters). */
|
|
26
26
|
export const USER_CODE_ALPHABET = "BCDFGHJKLMNPQRSTVWXZ";
|
|
@@ -31,8 +31,8 @@ const DEVICE_CODE_RE = /^cap_dvc_[A-Za-z0-9_-]{43}$/;
|
|
|
31
31
|
export const SLOW_DOWN_STEP_SECONDS = 5;
|
|
32
32
|
/** Polls this much earlier than the interval are still on time (network jitter). */
|
|
33
33
|
const POLL_JITTER_MS = 500;
|
|
34
|
-
/** The web page that confirms a code: `<http.publicUrl>/
|
|
35
|
-
export const DEVICE_PAGE_PATH = "/
|
|
34
|
+
/** The web page that confirms a code: `<http.publicUrl>/devices/link`. */
|
|
35
|
+
export const DEVICE_PAGE_PATH = "/devices/link";
|
|
36
36
|
/** The machine's WebSocket route: `ws(s)://<http.publicUrl host>/device/connect`. */
|
|
37
37
|
export const DEVICE_CONNECT_PATH = "/device/connect";
|
|
38
38
|
/** The sign-in state of one device code: `capk:device-auth:<sha256(deviceCode)>` (a hash). */
|
|
@@ -78,7 +78,7 @@ redis.call('HSET', KEYS[1], 'status', ARGV[2])
|
|
|
78
78
|
if ARGV[3] then redis.call('HSET', KEYS[1], ARGV[3], ARGV[4]) end
|
|
79
79
|
return 1
|
|
80
80
|
`;
|
|
81
|
-
const codeInvalid = () => new ApiError("
|
|
81
|
+
const codeInvalid = () => new ApiError("device_sign_in.code_invalid", "This code is unknown or has expired; check the code shown by capybara-device, or run capybara-device login again");
|
|
82
82
|
/** A user code as typed → `XXXX-XXXX`, or null when it cannot be one. */
|
|
83
83
|
export function normalizeUserCode(input) {
|
|
84
84
|
const s = input.toUpperCase().replace(/[\s-]/g, "");
|
|
@@ -107,7 +107,7 @@ function newUserCode() {
|
|
|
107
107
|
* for a code pair (`authorize`), the signed-in person confirms the user code on the web page
|
|
108
108
|
* `/device` (`pending`, `confirm` or `deny`), and the program polls `token` until it gets the
|
|
109
109
|
* machine credential, once. State lives only in Redis, both keys with the code's TTL
|
|
110
|
-
* (`
|
|
110
|
+
* (`devices.local.authCodeTtlSeconds`): `capk:device-auth:<sha256(deviceCode)>` (a hash:
|
|
111
111
|
* `info` JSON, `status`, `interval`, `lastPollAt`, the parked `grant`) and
|
|
112
112
|
* `capk:device-user:<userCode>` → the device code's hash. The device code itself is never stored.
|
|
113
113
|
*/
|
|
@@ -116,22 +116,22 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
116
116
|
txHost;
|
|
117
117
|
redis;
|
|
118
118
|
config;
|
|
119
|
-
|
|
119
|
+
devices;
|
|
120
120
|
machines;
|
|
121
121
|
log;
|
|
122
|
-
constructor(db, txHost, redis, config,
|
|
122
|
+
constructor(db, txHost, redis, config, devices, machines, log) {
|
|
123
123
|
this.db = db;
|
|
124
124
|
this.txHost = txHost;
|
|
125
125
|
this.redis = redis;
|
|
126
126
|
this.config = config;
|
|
127
|
-
this.
|
|
127
|
+
this.devices = devices;
|
|
128
128
|
this.machines = machines;
|
|
129
129
|
this.log = log;
|
|
130
130
|
}
|
|
131
131
|
/** A new code pair for a machine. Needs `http.publicUrl` (the page and the connection URL). */
|
|
132
132
|
async authorize(machine) {
|
|
133
133
|
const web = this.publicUrl();
|
|
134
|
-
const { authCodeTtlSeconds: ttl, pollIntervalSeconds: interval } = this.config.get().
|
|
134
|
+
const { authCodeTtlSeconds: ttl, pollIntervalSeconds: interval } = this.config.get().devices.local;
|
|
135
135
|
const deviceCode = DEVICE_CODE_PREFIX + randomBytes(32).toString("base64url");
|
|
136
136
|
const hash = sha256Hex(deviceCode);
|
|
137
137
|
for (let attempt = 0;; attempt++) {
|
|
@@ -169,8 +169,8 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
171
171
|
* The program's poll: the grant once the person confirmed (the code is consumed), else
|
|
172
|
-
* `
|
|
173
|
-
* interval), `
|
|
172
|
+
* `device_sign_in.authorization_pending`, `device_sign_in.slow_down` (details `[{ interval }]`, the grown
|
|
173
|
+
* interval), `device_sign_in.denied` or `device_sign_in.expired` (also for an unknown code).
|
|
174
174
|
*/
|
|
175
175
|
async token(deviceCode) {
|
|
176
176
|
const connectUrl = this.connectUrl();
|
|
@@ -180,21 +180,21 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
180
180
|
switch (r[0]) {
|
|
181
181
|
case "approved": {
|
|
182
182
|
const grant = JSON.parse(r[1]);
|
|
183
|
-
const
|
|
183
|
+
const local = this.config.get().devices.local;
|
|
184
184
|
return {
|
|
185
185
|
...grant,
|
|
186
186
|
connectUrl,
|
|
187
|
-
reconnectGraceSeconds:
|
|
188
|
-
maxFrameBytes:
|
|
187
|
+
reconnectGraceSeconds: local.reconnectGraceSeconds,
|
|
188
|
+
maxFrameBytes: local.maxFrameBytes,
|
|
189
189
|
};
|
|
190
190
|
}
|
|
191
191
|
case "slow_down":
|
|
192
|
-
throw new ApiError("
|
|
192
|
+
throw new ApiError("device_sign_in.slow_down", `Polling too fast; wait ${r[1]} seconds between polls`, [{ interval: Number(r[1]) }]);
|
|
193
193
|
case "denied":
|
|
194
|
-
throw new ApiError("
|
|
194
|
+
throw new ApiError("device_sign_in.denied", "The sign-in was denied in the browser");
|
|
195
195
|
case "pending":
|
|
196
196
|
case "claimed":
|
|
197
|
-
throw new ApiError("
|
|
197
|
+
throw new ApiError("device_sign_in.authorization_pending", "Waiting for the code to be confirmed in the browser");
|
|
198
198
|
default:
|
|
199
199
|
throw expired();
|
|
200
200
|
}
|
|
@@ -206,10 +206,10 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
206
206
|
}
|
|
207
207
|
/**
|
|
208
208
|
* The person confirms a user code: in an org where they may use agents (`agents.use`), into
|
|
209
|
-
* one of their own live
|
|
210
|
-
* while the name is taken). One transaction creates the
|
|
211
|
-
* machine (revoking the
|
|
212
|
-
* `
|
|
209
|
+
* one of their own live local devices or a new one named `name` (` 2`, ` 3`, … appended
|
|
210
|
+
* while the name is taken). One transaction creates the device if needed, issues the
|
|
211
|
+
* machine (revoking the device's previous one) and audits
|
|
212
|
+
* `device.machine_connected` (retried once when a concurrent confirmation took the new
|
|
213
213
|
* name); after the commit the credential is parked for the poll ({@link park}). The code is
|
|
214
214
|
* claimed first, so two confirmations of one code cannot both issue a machine; a confirmation
|
|
215
215
|
* that fails before the commit leaves it pending.
|
|
@@ -223,9 +223,7 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
223
223
|
try {
|
|
224
224
|
issued = await this.confirmIn(principal.account.id, input, info).catch((e) => {
|
|
225
225
|
// a concurrent confirmation took the same new name between our read and our insert
|
|
226
|
-
if (input.name !== undefined &&
|
|
227
|
-
e instanceof ApiError &&
|
|
228
|
-
e.code === "environment.name_taken") {
|
|
226
|
+
if (input.name !== undefined && e instanceof ApiError && e.code === "device.name_taken") {
|
|
229
227
|
return this.confirmIn(principal.account.id, input, info);
|
|
230
228
|
}
|
|
231
229
|
throw e;
|
|
@@ -236,12 +234,12 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
236
234
|
throw e;
|
|
237
235
|
}
|
|
238
236
|
await this.park(hash, info, issued);
|
|
239
|
-
return {
|
|
237
|
+
return { deviceId: issued.deviceId };
|
|
240
238
|
}
|
|
241
239
|
/**
|
|
242
240
|
* Parks the committed machine's credential for the poll. When that fails (the code expired
|
|
243
241
|
* meanwhile, or Redis failed) the machine is revoked again, so no credential nobody holds stays
|
|
244
|
-
* valid, and the person is told to start over: `
|
|
242
|
+
* valid, and the person is told to start over: `device_sign_in.expired` / `device_sign_in.unavailable`.
|
|
245
243
|
*/
|
|
246
244
|
async park(hash, info, issued) {
|
|
247
245
|
let parked;
|
|
@@ -260,16 +258,16 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
260
258
|
return;
|
|
261
259
|
}
|
|
262
260
|
this.log.warn("device credential could not be parked; the machine is revoked", {
|
|
263
|
-
|
|
261
|
+
deviceId: issued.deviceId,
|
|
264
262
|
machineId: issued.machineId,
|
|
265
263
|
err: failure,
|
|
266
264
|
});
|
|
267
265
|
await this.abandon(issued);
|
|
268
|
-
// the program's next poll then ends with
|
|
266
|
+
// the program's next poll then ends with device_sign_in.expired instead of waiting out the TTL
|
|
269
267
|
await this.redis.del(authKeyOf(hash), deviceUserKey(info.userCode)).catch(() => undefined);
|
|
270
268
|
if (failure === undefined)
|
|
271
269
|
throw expired();
|
|
272
|
-
throw new ApiError("
|
|
270
|
+
throw new ApiError("device_sign_in.unavailable", "The machine could not be connected; run capybara-device login again");
|
|
273
271
|
}
|
|
274
272
|
/** Revokes a machine whose credential never reached the program (see {@link park}). */
|
|
275
273
|
async abandon(issued) {
|
|
@@ -279,12 +277,12 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
279
277
|
await audit(this.db.db, {
|
|
280
278
|
actorAccountId: issued.accountId,
|
|
281
279
|
orgId: grant.orgId,
|
|
282
|
-
action: "
|
|
283
|
-
target: grant.
|
|
280
|
+
action: "device.machine_revoked",
|
|
281
|
+
target: grant.deviceId,
|
|
284
282
|
detail: { machineId, reason: "sign_in_incomplete" },
|
|
285
283
|
});
|
|
286
284
|
}
|
|
287
|
-
/** The person refuses a user code: the program's next poll gets `
|
|
285
|
+
/** The person refuses a user code: the program's next poll gets `device_sign_in.denied`. */
|
|
288
286
|
async deny(userCode) {
|
|
289
287
|
const { info, hash } = await this.lookup(userCode, "pending");
|
|
290
288
|
if (!(await this.transition(hash, "pending", "denied")))
|
|
@@ -293,53 +291,53 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
293
291
|
}
|
|
294
292
|
async confirmIn(accountId, input, info) {
|
|
295
293
|
const { orgId } = input;
|
|
296
|
-
let
|
|
294
|
+
let deviceId;
|
|
297
295
|
if (input.name !== undefined) {
|
|
298
296
|
const name = await this.freeName(orgId, accountId, input.name);
|
|
299
|
-
|
|
297
|
+
deviceId = (await this.devices.createLocal(orgId, accountId, name)).id;
|
|
300
298
|
}
|
|
301
299
|
else {
|
|
302
300
|
const env = await this.db
|
|
303
|
-
.selectFrom("
|
|
301
|
+
.selectFrom("devices")
|
|
304
302
|
.select("id")
|
|
305
|
-
.where("id", "=", input.
|
|
303
|
+
.where("id", "=", input.deviceId)
|
|
306
304
|
.where("org_id", "=", orgId)
|
|
307
305
|
.where("owner_kind", "=", "personal")
|
|
308
306
|
.where("owner_account_id", "=", accountId)
|
|
309
|
-
.where("source", "=", "
|
|
310
|
-
.where("provider", "=",
|
|
307
|
+
.where("source", "=", "local")
|
|
308
|
+
.where("provider", "=", LOCAL_PROVIDER)
|
|
311
309
|
.where("deleted_at", "is", null)
|
|
312
310
|
.forNoKeyUpdate()
|
|
313
311
|
.executeTakeFirst();
|
|
314
312
|
if (!env)
|
|
315
|
-
throw
|
|
316
|
-
|
|
313
|
+
throw deviceNotFound();
|
|
314
|
+
deviceId = env.id;
|
|
317
315
|
}
|
|
318
316
|
const { machine } = info;
|
|
319
317
|
const { machineId, credential } = await this.machines.issue({
|
|
320
318
|
orgId,
|
|
321
|
-
|
|
319
|
+
deviceId,
|
|
322
320
|
accountId,
|
|
323
321
|
...machine,
|
|
324
322
|
});
|
|
325
323
|
await audit(this.db.db, {
|
|
326
324
|
actorAccountId: accountId,
|
|
327
325
|
orgId,
|
|
328
|
-
action: "
|
|
329
|
-
target:
|
|
326
|
+
action: "device.machine_connected",
|
|
327
|
+
target: deviceId,
|
|
330
328
|
detail: { machineId, ...machine },
|
|
331
329
|
});
|
|
332
330
|
return {
|
|
333
|
-
|
|
331
|
+
deviceId,
|
|
334
332
|
machineId,
|
|
335
333
|
accountId,
|
|
336
|
-
grant: { credential, orgId,
|
|
334
|
+
grant: { credential, orgId, deviceId },
|
|
337
335
|
};
|
|
338
336
|
}
|
|
339
337
|
/** `name`, or `name 2`, `name 3`, … : the first that is none of the person's live names. */
|
|
340
338
|
async freeName(orgId, accountId, name) {
|
|
341
339
|
const taken = new Set((await this.db
|
|
342
|
-
.selectFrom("
|
|
340
|
+
.selectFrom("devices")
|
|
343
341
|
.select("name")
|
|
344
342
|
.where("org_id", "=", orgId)
|
|
345
343
|
.where("owner_account_id", "=", accountId)
|
|
@@ -353,7 +351,7 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
353
351
|
}
|
|
354
352
|
return candidate;
|
|
355
353
|
}
|
|
356
|
-
/** The code behind a user code with status `status`, else `
|
|
354
|
+
/** The code behind a user code with status `status`, else `device_sign_in.code_invalid`. */
|
|
357
355
|
async lookup(userCode, status) {
|
|
358
356
|
const code = normalizeUserCode(userCode);
|
|
359
357
|
if (code === null)
|
|
@@ -370,11 +368,11 @@ let DeviceAuthService = class DeviceAuthService {
|
|
|
370
368
|
const args = grant === undefined ? [from, to] : [from, to, "grant", grant];
|
|
371
369
|
return (await this.redis.eval(TRANSITION, 1, authKeyOf(hash), ...args)) === 1;
|
|
372
370
|
}
|
|
373
|
-
/** `http.publicUrl` without trailing slashes; unset → `
|
|
371
|
+
/** `http.publicUrl` without trailing slashes; unset → `device.unavailable`. */
|
|
374
372
|
publicUrl() {
|
|
375
373
|
const url = this.config.get().http.publicUrl;
|
|
376
374
|
if (!url) {
|
|
377
|
-
throw new ApiError("
|
|
375
|
+
throw new ApiError("device.unavailable", "Connecting machines needs http.publicUrl to be configured; ask the super admin");
|
|
378
376
|
}
|
|
379
377
|
return url.replace(/\/+$/, "");
|
|
380
378
|
}
|
|
@@ -395,9 +393,9 @@ DeviceAuthService = __decorate([
|
|
|
395
393
|
__param(1, Inject(TransactionHost)),
|
|
396
394
|
__param(2, Inject(REDIS)),
|
|
397
395
|
__param(3, Inject(KernelConfigService)),
|
|
398
|
-
__param(4, Inject(
|
|
396
|
+
__param(4, Inject(DevicesService)),
|
|
399
397
|
__param(5, Inject(MachinesService)),
|
|
400
398
|
__param(6, Inject(KERNEL_LOGGER))
|
|
401
399
|
], DeviceAuthService);
|
|
402
400
|
export { DeviceAuthService };
|
|
403
|
-
const expired = () => new ApiError("
|
|
401
|
+
const expired = () => new ApiError("device_sign_in.expired", "This sign-in code has expired or was already used; run capybara-device login again");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device sign-in (environments spec §13.3): {@link DeviceAuthService} behind
|
|
3
|
+
* `DeviceAuthController`. Kept apart from {@link LocalDevicesModule}, which `DevicesModule`
|
|
4
|
+
* imports (deleting a device revokes its machine), while sign-in needs both.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DeviceAuthModule {
|
|
7
|
+
}
|
|
@@ -5,19 +5,19 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { Module } from "@nestjs/common";
|
|
8
|
-
import {
|
|
8
|
+
import { DevicesModule } from "../devices/devices.module.js";
|
|
9
9
|
import { DeviceAuthService } from "./device-auth.js";
|
|
10
|
-
import {
|
|
10
|
+
import { LocalDevicesModule } from "./local-devices.module.js";
|
|
11
11
|
/**
|
|
12
12
|
* Device sign-in (environments spec §13.3): {@link DeviceAuthService} behind
|
|
13
|
-
* `DeviceAuthController`. Kept apart from {@link
|
|
14
|
-
* imports (deleting
|
|
13
|
+
* `DeviceAuthController`. Kept apart from {@link LocalDevicesModule}, which `DevicesModule`
|
|
14
|
+
* imports (deleting a device revokes its machine), while sign-in needs both.
|
|
15
15
|
*/
|
|
16
16
|
let DeviceAuthModule = class DeviceAuthModule {
|
|
17
17
|
};
|
|
18
18
|
DeviceAuthModule = __decorate([
|
|
19
19
|
Module({
|
|
20
|
-
imports: [
|
|
20
|
+
imports: [LocalDevicesModule, DevicesModule],
|
|
21
21
|
providers: [DeviceAuthService],
|
|
22
22
|
exports: [DeviceAuthService],
|
|
23
23
|
})
|
|
@@ -5,14 +5,14 @@ import type { MachineIdentity } from "./machines.js";
|
|
|
5
5
|
export declare const DEVICE_CONNECT_ROUTE = "/device/connect";
|
|
6
6
|
/** The header carrying the program's protocol version. */
|
|
7
7
|
export declare const DEVICE_PROTOCOL_HEADER = "x-capybara-device-protocol";
|
|
8
|
-
/** What the route needs from the gateway (`
|
|
8
|
+
/** What the route needs from the gateway (`LocalDeviceGateway`). */
|
|
9
9
|
export interface DeviceRoutesGateway {
|
|
10
10
|
authenticate(credential: string): Promise<MachineIdentity | null>;
|
|
11
11
|
accept(socket: WebSocket, identity: MachineIdentity, credential: string): void;
|
|
12
12
|
}
|
|
13
13
|
export interface DeviceRoutesOptions {
|
|
14
14
|
gateway: DeviceRoutesGateway;
|
|
15
|
-
/** `
|
|
15
|
+
/** `devices.local.maxFrameBytes`: a larger incoming frame closes the socket (1009). */
|
|
16
16
|
maxFrameBytes: number;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
@@ -24,7 +24,7 @@ export declare function isWebSocketUpgrade(req: FastifyRequest): boolean;
|
|
|
24
24
|
* Registers `GET /device/connect` (the gateway role only) in its own encapsulated scope with
|
|
25
25
|
* `@fastify/websocket`: a raw Fastify route outside Nest, so neither the API's `AuthGuard` nor its
|
|
26
26
|
* CSRF check applies; the route checks the machine credential itself, before the upgrade (401
|
|
27
|
-
* `device.credential_invalid`), then the protocol header (426 `
|
|
27
|
+
* `device.credential_invalid`), then the protocol header (426 `device.program_outdated`).
|
|
28
28
|
* Must be called before Nest `app.init()`, on the root instance.
|
|
29
29
|
*
|
|
30
30
|
* `@fastify/websocket` sends every upgrade request of the server through the Fastify router; the
|
|
@@ -36,7 +36,7 @@ function bearer(req) {
|
|
|
36
36
|
* Registers `GET /device/connect` (the gateway role only) in its own encapsulated scope with
|
|
37
37
|
* `@fastify/websocket`: a raw Fastify route outside Nest, so neither the API's `AuthGuard` nor its
|
|
38
38
|
* CSRF check applies; the route checks the machine credential itself, before the upgrade (401
|
|
39
|
-
* `device.credential_invalid`), then the protocol header (426 `
|
|
39
|
+
* `device.credential_invalid`), then the protocol header (426 `device.program_outdated`).
|
|
40
40
|
* Must be called before Nest `app.init()`, on the root instance.
|
|
41
41
|
*
|
|
42
42
|
* `@fastify/websocket` sends every upgrade request of the server through the Fastify router; the
|
|
@@ -73,7 +73,7 @@ export async function registerDeviceConnectRoute(fastify, opts) {
|
|
|
73
73
|
if (req.headers[DEVICE_PROTOCOL_HEADER] !== String(DEVICE_PROTOCOL_VERSION)) {
|
|
74
74
|
return reply
|
|
75
75
|
.code(426)
|
|
76
|
-
.send(errorBody("
|
|
76
|
+
.send(errorBody("device.program_outdated", `This kernel speaks device protocol ${DEVICE_PROTOCOL_VERSION}. ` +
|
|
77
77
|
"Update capybara-device."));
|
|
78
78
|
}
|
|
79
79
|
identities.set(req, { identity, cred });
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** `POST /api/v1/device/authorize`: the machine as the program describes it. */
|
|
3
|
+
export declare const DeviceAuthorizeBody: z.ZodObject<{
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
os: z.ZodEnum<{
|
|
6
|
+
darwin: "darwin";
|
|
7
|
+
linux: "linux";
|
|
8
|
+
}>;
|
|
9
|
+
arch: z.ZodString;
|
|
10
|
+
programVersion: z.ZodString;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
/** `POST /api/v1/device/token`: the secret device code from `authorize`. */
|
|
13
|
+
export declare const DeviceTokenBody: z.ZodObject<{
|
|
14
|
+
deviceCode: z.ZodString;
|
|
15
|
+
}, z.core.$strict>;
|
|
16
|
+
export declare const DeviceUserCodeParams: z.ZodObject<{
|
|
17
|
+
userCode: z.ZodString;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export declare const DeviceUserCodeBody: z.ZodObject<{
|
|
20
|
+
userCode: z.ZodString;
|
|
21
|
+
}, z.core.$strict>;
|
|
22
|
+
/**
|
|
23
|
+
* `POST /api/v1/device/confirm`: the org and exactly one of an existing local device of the
|
|
24
|
+
* person's (`deviceId`) or the name of a new one (`name`).
|
|
25
|
+
*/
|
|
26
|
+
export declare const DeviceConfirmBody: z.ZodObject<{
|
|
27
|
+
userCode: z.ZodString;
|
|
28
|
+
orgId: z.ZodString;
|
|
29
|
+
deviceId: z.ZodOptional<z.ZodUUID>;
|
|
30
|
+
name: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strict>;
|
|
32
|
+
declare const DeviceAuthorizeBodyDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
|
|
33
|
+
name: z.ZodString;
|
|
34
|
+
os: z.ZodEnum<{
|
|
35
|
+
darwin: "darwin";
|
|
36
|
+
linux: "linux";
|
|
37
|
+
}>;
|
|
38
|
+
arch: z.ZodString;
|
|
39
|
+
programVersion: z.ZodString;
|
|
40
|
+
}, z.core.$strict>>;
|
|
41
|
+
export declare class DeviceAuthorizeBodyDto extends DeviceAuthorizeBodyDto_base {
|
|
42
|
+
}
|
|
43
|
+
declare const DeviceTokenBodyDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
|
|
44
|
+
deviceCode: z.ZodString;
|
|
45
|
+
}, z.core.$strict>>;
|
|
46
|
+
export declare class DeviceTokenBodyDto extends DeviceTokenBodyDto_base {
|
|
47
|
+
}
|
|
48
|
+
declare const DeviceUserCodeParamsDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
|
|
49
|
+
userCode: z.ZodString;
|
|
50
|
+
}, z.core.$strip>>;
|
|
51
|
+
export declare class DeviceUserCodeParamsDto extends DeviceUserCodeParamsDto_base {
|
|
52
|
+
}
|
|
53
|
+
declare const DeviceUserCodeBodyDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
|
|
54
|
+
userCode: z.ZodString;
|
|
55
|
+
}, z.core.$strict>>;
|
|
56
|
+
export declare class DeviceUserCodeBodyDto extends DeviceUserCodeBodyDto_base {
|
|
57
|
+
}
|
|
58
|
+
declare const DeviceConfirmBodyDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
|
|
59
|
+
userCode: z.ZodString;
|
|
60
|
+
orgId: z.ZodString;
|
|
61
|
+
deviceId: z.ZodOptional<z.ZodUUID>;
|
|
62
|
+
name: z.ZodOptional<z.ZodString>;
|
|
63
|
+
}, z.core.$strict>>;
|
|
64
|
+
export declare class DeviceConfirmBodyDto extends DeviceConfirmBodyDto_base {
|
|
65
|
+
}
|
|
66
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DeviceName } from "../devices/dto.js";
|
|
3
|
+
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
4
|
+
/** `POST /api/v1/device/authorize`: the machine as the program describes it. */
|
|
5
|
+
export const DeviceAuthorizeBody = z
|
|
6
|
+
.object({
|
|
7
|
+
name: z.string().trim().min(1).max(64),
|
|
8
|
+
os: z.enum(["darwin", "linux"]),
|
|
9
|
+
arch: z.string().trim().min(1).max(32),
|
|
10
|
+
programVersion: z.string().trim().min(1).max(64),
|
|
11
|
+
})
|
|
12
|
+
.strict();
|
|
13
|
+
/** `POST /api/v1/device/token`: the secret device code from `authorize`. */
|
|
14
|
+
export const DeviceTokenBody = z.object({ deviceCode: z.string().min(1).max(200) }).strict();
|
|
15
|
+
/** A user code as typed (`XXXX-XXXX`; case, spaces and the dash are forgiven). */
|
|
16
|
+
const UserCode = z.string().min(1).max(32);
|
|
17
|
+
export const DeviceUserCodeParams = z.object({ userCode: UserCode });
|
|
18
|
+
export const DeviceUserCodeBody = z.object({ userCode: UserCode }).strict();
|
|
19
|
+
/**
|
|
20
|
+
* `POST /api/v1/device/confirm`: the org and exactly one of an existing local device of the
|
|
21
|
+
* person's (`deviceId`) or the name of a new one (`name`).
|
|
22
|
+
*/
|
|
23
|
+
export const DeviceConfirmBody = z
|
|
24
|
+
.object({
|
|
25
|
+
userCode: UserCode,
|
|
26
|
+
orgId: z.string().min(1).max(100),
|
|
27
|
+
deviceId: z.uuid().optional(),
|
|
28
|
+
name: DeviceName.optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict()
|
|
31
|
+
.refine((b) => (b.deviceId === undefined) !== (b.name === undefined), {
|
|
32
|
+
message: "Give exactly one of deviceId and name",
|
|
33
|
+
path: ["deviceId"],
|
|
34
|
+
});
|
|
35
|
+
export class DeviceAuthorizeBodyDto extends createZodDto(DeviceAuthorizeBody, "DeviceAuthorizeBody") {
|
|
36
|
+
}
|
|
37
|
+
export class DeviceTokenBodyDto extends createZodDto(DeviceTokenBody, "DeviceTokenBody") {
|
|
38
|
+
}
|
|
39
|
+
export class DeviceUserCodeParamsDto extends createZodDto(DeviceUserCodeParams, "DeviceUserCodeParams") {
|
|
40
|
+
}
|
|
41
|
+
export class DeviceUserCodeBodyDto extends createZodDto(DeviceUserCodeBody, "DeviceUserCodeBody") {
|
|
42
|
+
}
|
|
43
|
+
export class DeviceConfirmBodyDto extends createZodDto(DeviceConfirmBody, "DeviceConfirmBody") {
|
|
44
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Redis keys and bus topics of the connected-machine relay (environments spec §6, §13). Every key
|
|
3
|
-
* is per
|
|
3
|
+
* is per device (or per request); writers set a TTL on each.
|
|
4
4
|
*/
|
|
5
|
-
/** The lease of the replica holding the machine's connection: `capk:device-lease:<
|
|
6
|
-
export declare const deviceLeaseKey: (
|
|
7
|
-
/** The list of requests waiting for the machine: `capk:device-req:<
|
|
8
|
-
export declare const deviceReqKey: (
|
|
5
|
+
/** The lease of the replica holding the machine's connection: `capk:device-lease:<deviceId>`. */
|
|
6
|
+
export declare const deviceLeaseKey: (deviceId: string) => string;
|
|
7
|
+
/** The list of requests waiting for the machine: `capk:device-req:<deviceId>`. */
|
|
8
|
+
export declare const deviceReqKey: (deviceId: string) => string;
|
|
9
9
|
/** The result of one request: `capk:device-res:<requestId>`. */
|
|
10
10
|
export declare const deviceResKey: (requestId: string) => string;
|
|
11
11
|
/** The prefix of {@link deviceTakenKey} (the holder's Lua script builds the key from it). */
|
|
12
12
|
export declare const DEVICE_TAKEN_PREFIX = "capk:device-taken:";
|
|
13
13
|
/**
|
|
14
|
-
* A request the holder forwarded to a machine: `capk:device-taken:<requestId>` → the
|
|
15
|
-
* id. Only a result for a taken request of the socket's own
|
|
14
|
+
* A request the holder forwarded to a machine: `capk:device-taken:<requestId>` → the device
|
|
15
|
+
* id. Only a result for a taken request of the socket's own device is accepted.
|
|
16
16
|
*/
|
|
17
17
|
export declare const deviceTakenKey: (requestId: string) => string;
|
|
18
|
-
/** A new request for the machine of the
|
|
19
|
-
export declare const deviceReqTopic: (
|
|
18
|
+
/** A new request for the machine of the device. */
|
|
19
|
+
export declare const deviceReqTopic: (deviceId: string) => string;
|
|
20
20
|
/** The result of one request is ready. */
|
|
21
21
|
export declare const deviceResTopic: (requestId: string) => string;
|
|
22
22
|
/** Another replica took the machine's connection over. */
|
|
23
|
-
export declare const deviceTakeoverTopic: (
|
|
23
|
+
export declare const deviceTakeoverTopic: (deviceId: string) => string;
|
|
24
24
|
/** The machine's credential was revoked: its connection is closed. */
|
|
25
|
-
export declare const deviceRevokedTopic: (
|
|
25
|
+
export declare const deviceRevokedTopic: (deviceId: string) => string;
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Redis keys and bus topics of the connected-machine relay (environments spec §6, §13). Every key
|
|
3
|
-
* is per
|
|
3
|
+
* is per device (or per request); writers set a TTL on each.
|
|
4
4
|
*/
|
|
5
|
-
/** The lease of the replica holding the machine's connection: `capk:device-lease:<
|
|
6
|
-
export const deviceLeaseKey = (
|
|
7
|
-
/** The list of requests waiting for the machine: `capk:device-req:<
|
|
8
|
-
export const deviceReqKey = (
|
|
5
|
+
/** The lease of the replica holding the machine's connection: `capk:device-lease:<deviceId>`. */
|
|
6
|
+
export const deviceLeaseKey = (deviceId) => `capk:device-lease:${deviceId}`;
|
|
7
|
+
/** The list of requests waiting for the machine: `capk:device-req:<deviceId>`. */
|
|
8
|
+
export const deviceReqKey = (deviceId) => `capk:device-req:${deviceId}`;
|
|
9
9
|
/** The result of one request: `capk:device-res:<requestId>`. */
|
|
10
10
|
export const deviceResKey = (requestId) => `capk:device-res:${requestId}`;
|
|
11
11
|
/** The prefix of {@link deviceTakenKey} (the holder's Lua script builds the key from it). */
|
|
12
12
|
export const DEVICE_TAKEN_PREFIX = "capk:device-taken:";
|
|
13
13
|
/**
|
|
14
|
-
* A request the holder forwarded to a machine: `capk:device-taken:<requestId>` → the
|
|
15
|
-
* id. Only a result for a taken request of the socket's own
|
|
14
|
+
* A request the holder forwarded to a machine: `capk:device-taken:<requestId>` → the device
|
|
15
|
+
* id. Only a result for a taken request of the socket's own device is accepted.
|
|
16
16
|
*/
|
|
17
17
|
export const deviceTakenKey = (requestId) => `${DEVICE_TAKEN_PREFIX}${requestId}`;
|
|
18
|
-
/** A new request for the machine of the
|
|
19
|
-
export const deviceReqTopic = (
|
|
18
|
+
/** A new request for the machine of the device. */
|
|
19
|
+
export const deviceReqTopic = (deviceId) => `device.req.${deviceId}`;
|
|
20
20
|
/** The result of one request is ready. */
|
|
21
21
|
export const deviceResTopic = (requestId) => `device.res.${requestId}`;
|
|
22
22
|
/** Another replica took the machine's connection over. */
|
|
23
|
-
export const deviceTakeoverTopic = (
|
|
23
|
+
export const deviceTakeoverTopic = (deviceId) => `device.takeover.${deviceId}`;
|
|
24
24
|
/** The machine's credential was revoked: its connection is closed. */
|
|
25
|
-
export const deviceRevokedTopic = (
|
|
25
|
+
export const deviceRevokedTopic = (deviceId) => `device.revoked.${deviceId}`;
|
|
@@ -9,13 +9,13 @@ import { type MachineIdentity, MachinesService } from "./machines.js";
|
|
|
9
9
|
export declare const DEVICE_CLOSE: {
|
|
10
10
|
/** The kernel is shutting down: reconnect (another gateway replica takes over). */
|
|
11
11
|
readonly shutdown: 1001;
|
|
12
|
-
/** A frame larger than `
|
|
12
|
+
/** A frame larger than `devices.local.maxFrameBytes` (sent by `ws` itself). */
|
|
13
13
|
readonly tooBig: 1009;
|
|
14
14
|
/** The kernel failed (e.g. the database): reconnect. */
|
|
15
15
|
readonly internal: 1011;
|
|
16
16
|
/** The lease was lost (renewal kept failing or the key was taken): reconnect. */
|
|
17
17
|
readonly leaseLost: 4001;
|
|
18
|
-
/** A newer connection of the
|
|
18
|
+
/** A newer connection of the device took over: the program exits. */
|
|
19
19
|
readonly replaced: 4002;
|
|
20
20
|
/** The machine's credential was revoked: the program exits. */
|
|
21
21
|
readonly revoked: 4003;
|
|
@@ -55,17 +55,17 @@ declare class DeviceConnection {
|
|
|
55
55
|
private chain;
|
|
56
56
|
/** Resolves once the lease is handed back (or was never held). */
|
|
57
57
|
private released;
|
|
58
|
-
constructor(gw:
|
|
59
|
-
get
|
|
58
|
+
constructor(gw: LocalDeviceGateway, socket: WebSocket, identity: MachineIdentity, credential: string);
|
|
59
|
+
get deviceId(): string;
|
|
60
60
|
start(): void;
|
|
61
61
|
private onFrame;
|
|
62
62
|
/**
|
|
63
63
|
* Stores one result and announces it (the relay's wire, relay.ts), only for a request taken for
|
|
64
|
-
* this
|
|
64
|
+
* this device (ruling R20); anything else is dropped.
|
|
65
65
|
*/
|
|
66
66
|
private store;
|
|
67
67
|
/**
|
|
68
|
-
* Claims the
|
|
68
|
+
* Claims the device's lease; true when this connection holds it. The newest connection
|
|
69
69
|
* wins (ruling R21): when another connection holds the lease, a takeover is published (the
|
|
70
70
|
* holder yields to any takeover) and the lease retried every {@link CLAIM_RETRY_MS} for
|
|
71
71
|
* {@link CLAIM_TIMEOUT_MS}, then 4409 (the program reconnects). A claimer never yields.
|
|
@@ -79,7 +79,7 @@ declare class DeviceConnection {
|
|
|
79
79
|
/** The next ping (a chain of timeouts; the lease renewal is LockService's, not this timer's). */
|
|
80
80
|
private schedulePing;
|
|
81
81
|
private ping;
|
|
82
|
-
/** The credential still stands (membership, org and
|
|
82
|
+
/** The credential still stands (membership, org and device are read live). */
|
|
83
83
|
private reauthenticate;
|
|
84
84
|
private traffic;
|
|
85
85
|
/** Closes the socket with `code` and hands the lease back; idempotent. */
|
|
@@ -90,17 +90,17 @@ declare class DeviceConnection {
|
|
|
90
90
|
/**
|
|
91
91
|
* The gateway role's half of the device relay (environments spec §13.4): holds the WebSocket
|
|
92
92
|
* connections of connected machines (`GET /device/connect`, device-routes.ts). One connection per
|
|
93
|
-
*
|
|
94
|
-
* connection wins: it publishes `device.takeover.<
|
|
95
|
-
* holder drains `capk:device-req:<
|
|
96
|
-
* once after claiming and on every ping, marking each forwarded request taken for its
|
|
93
|
+
* device across replicas, under the lease `capk:device-lease:<deviceId>` (the newest
|
|
94
|
+
* connection wins: it publishes `device.takeover.<deviceId>` and the holder lets go). The
|
|
95
|
+
* holder drains `capk:device-req:<deviceId>` on every `device.req.<deviceId>` nudge,
|
|
96
|
+
* once after claiming and on every ping, marking each forwarded request taken for its device
|
|
97
97
|
* (`capk:device-taken:<id>`, TTL the reconnect grace plus the request's deadline plus a margin).
|
|
98
|
-
* A `res` is accepted only for a request taken for the socket's
|
|
98
|
+
* A `res` is accepted only for a request taken for the socket's device: it is stored under
|
|
99
99
|
* `capk:device-res:<id>` for the rest of that TTL and published on `device.res.<id>`.
|
|
100
|
-
* `device.revoked.<
|
|
100
|
+
* `device.revoked.<deviceId>` closes the connection of that machine only (ruling R16). The
|
|
101
101
|
* sockets are this process's own; everything other replicas need is in Redis.
|
|
102
102
|
*/
|
|
103
|
-
export declare class
|
|
103
|
+
export declare class LocalDeviceGateway {
|
|
104
104
|
readonly machines: Pick<MachinesService, "authenticate" | "recordHello" | "touch">;
|
|
105
105
|
readonly locks: Pick<LockService, "hold">;
|
|
106
106
|
readonly bus: ExtensionContext["bus"];
|