@nervur-org/dock 0.4.1 → 0.6.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/README.md +17 -15
- package/api/route.ts +4 -72
- package/beings/avatar.ts +11 -7
- package/beings/carry.ts +129 -116
- package/beings/clock.ts +11 -7
- package/beings/desk.ts +7 -6
- package/beings/doorbell.ts +36 -7
- package/beings/index.ts +17 -4
- package/beings/join.ts +7 -4
- package/beings/link.ts +1 -1
- package/beings/porter.ts +5 -1
- package/beings/presence.ts +15 -5
- package/beings/setup.ts +27 -21
- package/beings/user.ts +26 -17
- package/beings/way.ts +1 -1
- package/{dock → box}/agents.ts +37 -44
- package/{dock → box}/apns.ts +4 -4
- package/box/box.ts +31 -0
- package/box/contract.ts +41 -0
- package/box/dialers.ts +88 -0
- package/box/dock.ts +148 -0
- package/box/envoy.ts +96 -0
- package/box/faculty.ts +111 -0
- package/{dock → box}/fcm.ts +1 -1
- package/box/grants.ts +90 -0
- package/box/http.ts +93 -0
- package/{dock → box}/http2.ts +7 -7
- package/box/index.ts +201 -0
- package/box/join.ts +78 -0
- package/box/local.ts +37 -0
- package/{dock → box}/location.ts +15 -3
- package/{dock → box}/notifications.ts +17 -5
- package/box/push.ts +85 -0
- package/box/ring.ts +85 -0
- package/{dock → box}/service.ts +2 -2
- package/box/timer.ts +150 -0
- package/box/tree.ts +22 -0
- package/{dock → box}/webpush.ts +2 -2
- package/cli/client.ts +1 -1
- package/cli/daemon.ts +101 -103
- package/cli/estate.ts +2 -3
- package/cli/nervur.ts +44 -25
- package/cli/stand.ts +1 -1
- package/dist/api/route.d.ts +1 -2
- package/dist/api/route.js +4 -76
- package/dist/beings/avatar.d.ts +4 -3
- package/dist/beings/avatar.js +9 -6
- package/dist/beings/carry.d.ts +10 -12
- package/dist/beings/carry.js +119 -127
- package/dist/beings/clock.js +10 -7
- package/dist/beings/desk.js +8 -7
- package/dist/beings/doorbell.d.ts +3 -1
- package/dist/beings/doorbell.js +37 -4
- package/dist/beings/index.d.ts +24 -4
- package/dist/beings/index.js +15 -4
- package/dist/beings/join.d.ts +1 -0
- package/dist/beings/join.js +6 -4
- package/dist/beings/link.d.ts +0 -1
- package/dist/beings/link.js +1 -1
- package/dist/beings/porter.js +5 -1
- package/dist/beings/presence.d.ts +1 -0
- package/dist/beings/presence.js +12 -3
- package/dist/beings/setup.d.ts +5 -2
- package/dist/beings/setup.js +16 -15
- package/dist/beings/user.d.ts +4 -3
- package/dist/beings/user.js +18 -11
- package/dist/{dock → box}/agents.d.ts +7 -4
- package/dist/{dock → box}/agents.js +30 -37
- package/dist/{dock → box}/apns.d.ts +1 -1
- package/dist/{dock → box}/apns.js +3 -3
- package/dist/box/box.d.ts +9 -0
- package/dist/box/box.js +18 -0
- package/dist/box/contract.d.ts +6 -0
- package/dist/box/contract.js +35 -0
- package/dist/{dock → box}/dialers.d.ts +7 -4
- package/dist/box/dialers.js +76 -0
- package/dist/box/dock.d.ts +182 -0
- package/dist/box/dock.js +128 -0
- package/dist/box/envoy.d.ts +44 -0
- package/dist/box/envoy.js +75 -0
- package/dist/box/faculty.d.ts +68 -0
- package/dist/box/faculty.js +97 -0
- package/dist/{dock → box}/fcm.d.ts +1 -1
- package/dist/{dock → box}/grants.d.ts +4 -3
- package/dist/box/grants.js +78 -0
- package/dist/box/http.d.ts +26 -0
- package/dist/box/http.js +89 -0
- package/dist/{dock → box}/http2.d.ts +1 -1
- package/dist/{dock → box}/http2.js +6 -6
- package/dist/box/index.d.ts +46 -0
- package/dist/box/index.js +179 -0
- package/dist/box/join.d.ts +19 -0
- package/dist/box/join.js +67 -0
- package/dist/{dock → box}/local.d.ts +3 -2
- package/dist/box/local.js +27 -0
- package/dist/{dock → box}/location.d.ts +3 -0
- package/dist/box/location.js +21 -0
- package/dist/{dock → box}/notifications.d.ts +3 -0
- package/dist/{dock → box}/notifications.js +14 -4
- package/dist/{dock → box}/push.d.ts +12 -18
- package/dist/box/push.js +83 -0
- package/dist/box/ring.d.ts +18 -0
- package/dist/box/ring.js +55 -0
- package/dist/{dock → box}/service.d.ts +1 -1
- package/dist/{dock → box}/service.js +1 -1
- package/dist/{dock → box}/timer.d.ts +2 -0
- package/dist/box/timer.js +127 -0
- package/dist/box/tree.d.ts +5 -0
- package/dist/box/tree.js +14 -0
- package/dist/{dock → box}/webpush.d.ts +1 -1
- package/dist/cli/client.d.ts +1 -0
- package/dist/cli/daemon.d.ts +2 -8
- package/dist/cli/daemon.js +102 -107
- package/dist/cli/estate.js +2 -3
- package/dist/cli/nervur.js +44 -24
- package/dist/cli/stand.d.ts +0 -14
- package/dist/cli/stand.js +1 -1
- package/dist/harbor/browser.d.ts +8 -5
- package/dist/harbor/browser.js +20 -21
- package/dist/harbor/capacitor.d.ts +3 -3
- package/dist/harbor/capacitor.js +9 -8
- package/dist/harbor/disk.d.ts +9 -9
- package/dist/harbor/disk.js +56 -51
- package/dist/harbor/edge/alarm.d.ts +4 -2
- package/dist/harbor/edge/alarm.js +9 -19
- package/dist/harbor/edge/edge.d.ts +7 -6
- package/dist/harbor/edge/edge.js +58 -53
- package/dist/harbor/edge/exercise.js +4 -4
- package/dist/harbor/edge/given.d.ts +0 -1
- package/dist/harbor/edge/given.js +0 -29
- package/dist/harbor/edge/storage.d.ts +0 -2
- package/dist/harbor/edge/storage.js +2 -2
- package/dist/harbor/http.js +4 -2
- package/dist/harbor/sealed.d.ts +0 -5
- package/dist/harbor/sealed.js +0 -39
- package/dist/harbor/tauri.d.ts +2 -2
- package/dist/harbor/tauri.js +13 -10
- package/dist/human/app.d.ts +1 -1
- package/dist/human/app.js +10 -12
- package/dist/human/door.d.ts +0 -1
- package/dist/human/door.js +1 -1
- package/dist/human/html.js +1 -1
- package/dist/human/move.js +1 -1
- package/dist/human/tab.bundle.txt +3629 -2824
- package/dist/human/wake.d.ts +5 -2
- package/dist/human/wake.js +23 -14
- package/dist/human/web.d.ts +0 -1
- package/dist/human/web.js +3 -3
- package/dist/human/worker.bundle.txt +2945 -2118
- package/dist/human/worlds.js +3 -3
- package/dist/mcp/agent.d.ts +0 -1
- package/dist/mcp/agent.js +1 -1
- package/dist/mcp/allow.js +3 -2
- package/dist/mcp/http.d.ts +0 -1
- package/dist/mcp/http.js +1 -1
- package/dist/mcp/oauth.d.ts +3 -4
- package/dist/mcp/oauth.js +4 -4
- package/dist/mcp/route.d.ts +1 -1
- package/dist/mcp/runner.d.ts +0 -1
- package/dist/mcp/runner.js +1 -1
- package/dist/mcp/server.d.ts +0 -1
- package/dist/mcp/server.js +1 -1
- package/harbor/browser.ts +22 -24
- package/harbor/capacitor.ts +9 -8
- package/harbor/disk.ts +53 -48
- package/harbor/edge/alarm.ts +15 -12
- package/harbor/edge/edge.ts +61 -55
- package/harbor/edge/exercise.ts +4 -4
- package/harbor/edge/given.ts +3 -33
- package/harbor/edge/storage.ts +2 -2
- package/harbor/http.ts +5 -3
- package/harbor/sealed.ts +0 -35
- package/harbor/tauri.ts +13 -10
- package/human/app.ts +12 -13
- package/human/door.ts +1 -1
- package/human/html.ts +1 -1
- package/human/local.ts +1 -1
- package/human/move.ts +1 -1
- package/human/tab.bundle.txt +3629 -2824
- package/human/wake.ts +36 -16
- package/human/web.ts +4 -4
- package/human/worker.bundle.txt +2945 -2118
- package/human/worlds.ts +3 -3
- package/mcp/agent.ts +1 -1
- package/mcp/allow.ts +2 -1
- package/mcp/http.ts +1 -1
- package/mcp/oauth.ts +4 -4
- package/mcp/route.ts +1 -1
- package/mcp/runner.ts +1 -1
- package/mcp/server.ts +1 -1
- package/package.json +10 -18
- package/GETTING_STARTED.md +0 -138
- package/GLOSSARY.md +0 -118
- package/WORLDS.md +0 -569
- package/beings/envoy.ts +0 -218
- package/dist/beings/envoy.d.ts +0 -54
- package/dist/beings/envoy.js +0 -211
- package/dist/dock/console.d.ts +0 -6
- package/dist/dock/console.js +0 -20
- package/dist/dock/dialers.js +0 -75
- package/dist/dock/faculty.d.ts +0 -24
- package/dist/dock/faculty.js +0 -142
- package/dist/dock/grants.js +0 -99
- package/dist/dock/index.d.ts +0 -41
- package/dist/dock/index.js +0 -113
- package/dist/dock/local.js +0 -44
- package/dist/dock/location.js +0 -11
- package/dist/dock/push.js +0 -132
- package/dist/dock/timer.js +0 -149
- package/dock/console.ts +0 -23
- package/dock/dialers.ts +0 -85
- package/dock/faculty.ts +0 -148
- package/dock/grants.ts +0 -111
- package/dock/index.ts +0 -133
- package/dock/local.ts +0 -48
- package/dock/push.ts +0 -161
- package/dock/timer.ts +0 -174
- package/quo-api.md +0 -196
- package/quo-app-bells.md +0 -280
- package/quo-dock.md +0 -1945
- package/quo-harbor.md +0 -874
- package/quo-human.md +0 -1042
- package/quo-mcp.md +0 -343
- /package/dist/{dock → box}/fcm.js +0 -0
- /package/dist/{dock → box}/webpush.js +0 -0
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
// it would cost her a line of text rather than her world.
|
|
24
24
|
import { arithmetic } from 'nervur/ward';
|
|
25
25
|
import { Faculty } from './faculty.js';
|
|
26
|
+
import { contract } from './contract.js';
|
|
27
|
+
const ASKS = {
|
|
28
|
+
notify: { description: 'show the human a line', input: { type: 'object', properties: { title: { type: 'string' }, body: { type: 'string' } }, required: ['title'] } },
|
|
29
|
+
banner: { description: "this device's banner key, the public half, which a world seals a notification to", input: { type: 'object' } },
|
|
30
|
+
};
|
|
31
|
+
// What a being needs from a box that can show the human a line.
|
|
32
|
+
export const NOTIFICATIONS = contract('notifications', ASKS);
|
|
26
33
|
// A key is thirty two bytes of the platform's own randomness, as every seed
|
|
27
34
|
// in this kit is.
|
|
28
35
|
const seed = () => globalThis.crypto.getRandomValues(new Uint8Array(32));
|
|
@@ -30,10 +37,13 @@ export class Notifications extends Faculty {
|
|
|
30
37
|
// The banner key, as hex: the seed it was made from, kept so the pair can
|
|
31
38
|
// be made again, and the public half a device hands out.
|
|
32
39
|
static cells = { watched: {}, minted: 0, banner: null };
|
|
33
|
-
static asks =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
static asks = ASKS;
|
|
41
|
+
static fulfils = [NOTIFICATIONS];
|
|
42
|
+
// This body shows nothing, so it stands nowhere: a terrain with a
|
|
43
|
+
// notification center subclasses her and says so.
|
|
44
|
+
static stands() {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
37
47
|
notify(_args) {
|
|
38
48
|
return Promise.resolve({ error: 'this box shows no notifications' });
|
|
39
49
|
}
|
|
@@ -1,23 +1,8 @@
|
|
|
1
1
|
import type { Asker, JsonObject, OccupantRecord } from 'nervur';
|
|
2
|
+
import { type Ring } from './ring.ts';
|
|
2
3
|
import { Faculty } from './faculty.ts';
|
|
3
|
-
export type Token = {
|
|
4
|
-
kind: string;
|
|
5
|
-
bell?: string;
|
|
6
|
-
app?: string;
|
|
7
|
-
} & JsonObject;
|
|
8
|
-
export declare const isToken: (v: unknown) => v is Token;
|
|
9
|
-
export declare function bellOf(token: Token): string | undefined;
|
|
10
|
-
export type Line = {
|
|
11
|
-
title: string;
|
|
12
|
-
body: string;
|
|
13
|
-
};
|
|
14
|
-
export type Ring = {
|
|
15
|
-
ring(token: Token, sealed?: string, line?: Line): Promise<void>;
|
|
16
|
-
};
|
|
17
|
-
export declare const SEAL = 4096;
|
|
18
|
-
export declare function banner(o: JsonObject): Line;
|
|
19
|
-
export declare function sealBanner(line: JsonObject, key: string): Promise<string | null>;
|
|
20
4
|
declare const root: (_occ: OccupantRecord | undefined, asker: Asker) => boolean;
|
|
5
|
+
export declare const PUSH: import("nervur").Blueprint;
|
|
21
6
|
export declare class Push extends Faculty {
|
|
22
7
|
static cells: JsonObject;
|
|
23
8
|
static asks: {
|
|
@@ -54,6 +39,13 @@ export declare class Push extends Faculty {
|
|
|
54
39
|
type: string;
|
|
55
40
|
};
|
|
56
41
|
};
|
|
42
|
+
bell: {
|
|
43
|
+
description: string;
|
|
44
|
+
input: {
|
|
45
|
+
type: string;
|
|
46
|
+
};
|
|
47
|
+
for: typeof root;
|
|
48
|
+
};
|
|
57
49
|
keep: {
|
|
58
50
|
description: string;
|
|
59
51
|
input: {
|
|
@@ -68,7 +60,9 @@ export declare class Push extends Faculty {
|
|
|
68
60
|
for: typeof root;
|
|
69
61
|
};
|
|
70
62
|
};
|
|
71
|
-
|
|
63
|
+
static fulfils: import("nervur").Blueprint[];
|
|
64
|
+
static stands(): boolean;
|
|
65
|
+
bell(): Promise<JsonObject>;
|
|
72
66
|
get key(): JsonWebKey | null;
|
|
73
67
|
get service(): string | undefined;
|
|
74
68
|
keep(args: JsonObject): JsonObject;
|
package/dist/box/push.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Waking a device that is not reachable, the one way its platform allows: a
|
|
3
|
+
// ring carrying the device's token and a banner sealed to its key. A token
|
|
4
|
+
// names its kind and a kind is one ring. The rings are live objects and not
|
|
5
|
+
// values, so a terrain that can ring brings a class that builds its own; this
|
|
6
|
+
// body rings nothing and stands nowhere.
|
|
7
|
+
//
|
|
8
|
+
// What she keeps: the world's doorbell key, a P-256 JWK she mints herself the
|
|
9
|
+
// first time a terrain asks, and the bell service a phone's ring is handed
|
|
10
|
+
// to when its token names none, a row the root keeps.
|
|
11
|
+
import { OWNER } from 'nervur';
|
|
12
|
+
import { SEAL, isToken } from './ring.js';
|
|
13
|
+
import { mintVapid, publicOf } from './webpush.js';
|
|
14
|
+
import { contract } from './contract.js';
|
|
15
|
+
import { Faculty } from './faculty.js';
|
|
16
|
+
const root = (_occ, asker) => asker.id === OWNER;
|
|
17
|
+
// What a holder is owed: a ring, and which kinds this box can ring.
|
|
18
|
+
const OWED = {
|
|
19
|
+
ring: { description: 'wake a device: its token, the banner sealed to it, or the line itself where the ground encrypts it', input: { type: 'object', properties: { token: { type: 'object' }, sealed: { type: 'string' }, line: { type: 'object', properties: { title: { type: 'string' }, body: { type: 'string' } }, required: ['title', 'body'] } }, required: ['token'] } },
|
|
20
|
+
kinds: { description: 'which kinds this box can ring', input: { type: 'object' } },
|
|
21
|
+
};
|
|
22
|
+
// What a being needs from a box that can wake a device.
|
|
23
|
+
export const PUSH = contract('push', OWED);
|
|
24
|
+
export class Push extends Faculty {
|
|
25
|
+
static cells = { watched: {}, minted: 0, key: null, service: null };
|
|
26
|
+
static asks = { ...OWED, bell: { description: 'the public half of this box’s doorbell key, minted at the first ask and kept in her cells', input: { type: 'object' }, for: root }, keep: { description: 'the bell service a phone is rung through when its token names none; null forgets it', input: { type: 'object', properties: { service: { type: ['string', 'null'] } }, required: ['service'] }, for: root } };
|
|
27
|
+
static fulfils = [PUSH];
|
|
28
|
+
static stands() {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// The doorbell key, minted at the root's first ask and kept; nobody mints
|
|
32
|
+
// it from outside. What crosses is the public half, which every page's
|
|
33
|
+
// config carries.
|
|
34
|
+
async bell() {
|
|
35
|
+
if (this.cells.key === null)
|
|
36
|
+
this.cells.key = (await mintVapid()).key;
|
|
37
|
+
return { bell: publicOf(this.cells.key) };
|
|
38
|
+
}
|
|
39
|
+
get key() {
|
|
40
|
+
return this.cells.key;
|
|
41
|
+
}
|
|
42
|
+
get service() {
|
|
43
|
+
return typeof this.cells.service === 'string' ? this.cells.service : undefined;
|
|
44
|
+
}
|
|
45
|
+
keep(args) {
|
|
46
|
+
const service = args.service;
|
|
47
|
+
if (service !== null && (typeof service !== 'string' || !/^https?:\/\/\S+$/.test(service)))
|
|
48
|
+
return { error: 'a bell service is a URL over http or https, or null' };
|
|
49
|
+
this.cells.service = service === null ? null : service.replace(/\/$/, '');
|
|
50
|
+
return { service: this.cells.service };
|
|
51
|
+
}
|
|
52
|
+
// What this box can ring. The base rings nothing.
|
|
53
|
+
get rings() {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
kinds() {
|
|
57
|
+
return { kinds: Object.keys(this.rings) };
|
|
58
|
+
}
|
|
59
|
+
async ring(args) {
|
|
60
|
+
const token = args.token;
|
|
61
|
+
if (!isToken(token))
|
|
62
|
+
return { error: 'a token names its kind' };
|
|
63
|
+
const sealed = args.sealed;
|
|
64
|
+
if (sealed !== undefined && typeof sealed !== 'string')
|
|
65
|
+
return { error: 'a seal is text' };
|
|
66
|
+
if (typeof sealed === 'string' && sealed.length > SEAL)
|
|
67
|
+
return { error: `a seal is at most ${SEAL} bytes` };
|
|
68
|
+
const said = args.line;
|
|
69
|
+
if (said !== undefined && (said === null || typeof said !== 'object' || Array.isArray(said) || typeof said.title !== 'string' || typeof said.body !== 'string'))
|
|
70
|
+
return { error: 'a line is a title and a body' };
|
|
71
|
+
const line = said;
|
|
72
|
+
const r = Object.hasOwn(this.rings, token.kind) ? this.rings[token.kind] : undefined;
|
|
73
|
+
if (!r)
|
|
74
|
+
return { error: `this box cannot ring a ${token.kind}` };
|
|
75
|
+
try {
|
|
76
|
+
await r.ring(token, sealed, line);
|
|
77
|
+
return { rang: token.kind };
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
return { error: e instanceof Error ? e.message : 'the ring threw' };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JsonObject } from 'nervur';
|
|
2
|
+
export type Token = {
|
|
3
|
+
kind: string;
|
|
4
|
+
bell?: string;
|
|
5
|
+
app?: string;
|
|
6
|
+
} & JsonObject;
|
|
7
|
+
export declare const isToken: (v: unknown) => v is Token;
|
|
8
|
+
export declare function bellOf(token: Token): string | undefined;
|
|
9
|
+
export type Line = {
|
|
10
|
+
title: string;
|
|
11
|
+
body: string;
|
|
12
|
+
};
|
|
13
|
+
export type Ring = {
|
|
14
|
+
ring(token: Token, sealed?: string, line?: Line): Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export declare const SEAL = 4096;
|
|
17
|
+
export declare function banner(o: JsonObject): Line;
|
|
18
|
+
export declare function sealBanner(line: JsonObject, key: string): Promise<string | null>;
|
package/dist/box/ring.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// What waking a device is made of, beside the push faculty: the token a
|
|
3
|
+
// device hands a world, the ring a platform rings through, the banner a
|
|
4
|
+
// device shows, and the seal only the device can open. A token names its
|
|
5
|
+
// kind, and a kind is one ring: `web` is Web Push under a keypair a world
|
|
6
|
+
// mints itself, `apns` and `fcm` are the two stores' own, handed on to the
|
|
7
|
+
// publisher's bell service by a world that holds no platform key.
|
|
8
|
+
import { arithmetic } from 'nervur/ward';
|
|
9
|
+
export const isToken = (v) => v !== null && typeof v === 'object' && !Array.isArray(v) && typeof v.kind === 'string';
|
|
10
|
+
// The bell a token names, when it names one a ring can reach.
|
|
11
|
+
export function bellOf(token) {
|
|
12
|
+
if (typeof token.bell !== 'string')
|
|
13
|
+
return undefined;
|
|
14
|
+
try {
|
|
15
|
+
const u = new URL(token.bell);
|
|
16
|
+
return u.protocol === 'https:' || u.protocol === 'http:' ? token.bell : undefined;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// What a platform will carry, so a seal too big is refused where a human
|
|
23
|
+
// can still read why rather than two hops later by a service nobody reads.
|
|
24
|
+
export const SEAL = 4096;
|
|
25
|
+
// The line a device shows when it was woken while nobody was looking,
|
|
26
|
+
// composed from one object. A pushed object is whatever the far being sent,
|
|
27
|
+
// so the two names a notification has are taken when they are there and the
|
|
28
|
+
// object is shown as it is when they are not.
|
|
29
|
+
export function banner(o) {
|
|
30
|
+
const text = (v) => (typeof v === 'string' && v.trim() ? v.trim() : '');
|
|
31
|
+
const title = text(o.title) || text(o.from) || 'Nervur';
|
|
32
|
+
const body = text(o.body) ||
|
|
33
|
+
text(o.said) ||
|
|
34
|
+
Object.entries(o)
|
|
35
|
+
.filter(([k]) => k !== 'title' && k !== 'from')
|
|
36
|
+
.map(([k, v]) => `${k}: ${typeof v === 'string' ? v : JSON.stringify(v)}`)
|
|
37
|
+
.join(', ');
|
|
38
|
+
return { title, body };
|
|
39
|
+
}
|
|
40
|
+
// A banner sealed to a device's banner key, as hex, or null when it will
|
|
41
|
+
// not seal or will not fit. The world seals and the process that draws the
|
|
42
|
+
// banner opens it; no doorbell, no bell service and no platform between
|
|
43
|
+
// them holds either half, which is why a ring may carry a line at all.
|
|
44
|
+
export async function sealBanner(line, key) {
|
|
45
|
+
if (!/^[0-9a-f]{64}$/.test(key))
|
|
46
|
+
return null;
|
|
47
|
+
try {
|
|
48
|
+
const boxed = await arithmetic.box(new TextEncoder().encode(JSON.stringify(line)), arithmetic.unhex(key), globalThis.crypto.getRandomValues(new Uint8Array(32)));
|
|
49
|
+
const wire = arithmetic.hex(boxed.bytes);
|
|
50
|
+
return wire.length > SEAL ? null : wire;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Ring } from './
|
|
1
|
+
import { type Ring } from './ring.ts';
|
|
2
2
|
export declare function routed(fallback?: string): Ring;
|
|
3
3
|
export declare function handedTo(at: string): Ring;
|
|
4
4
|
export declare function ringing(rings: Record<string, Ring>, apps?: readonly string[]): (request: Request) => Promise<Response>;
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// own. An account here would be a registry of worlds, which is the one
|
|
27
27
|
// thing the app is built not to have. The day this is abused it wants a
|
|
28
28
|
// rate limit at the edge, which is the terrain's and not a word of Quo.
|
|
29
|
-
import { bellOf } from './
|
|
29
|
+
import { bellOf } from './ring.js';
|
|
30
30
|
// The ring a world holds for a store's kind: the token names its bell and
|
|
31
31
|
// the ring goes there, so a phone running an app this world never shipped
|
|
32
32
|
// is rung through that app's own service; a token naming none goes to the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JsonObject } from 'nervur';
|
|
2
2
|
import { Faculty, type Woke } from './faculty.ts';
|
|
3
|
+
export declare const TIMER: import("nervur").Blueprint;
|
|
3
4
|
export declare class Timer extends Faculty {
|
|
4
5
|
#private;
|
|
5
6
|
static asks: {
|
|
@@ -40,6 +41,7 @@ export declare class Timer extends Faculty {
|
|
|
40
41
|
};
|
|
41
42
|
};
|
|
42
43
|
};
|
|
44
|
+
static fulfils: import("nervur").Blueprint[];
|
|
43
45
|
get armed(): number | null;
|
|
44
46
|
due(args: JsonObject): JsonObject;
|
|
45
47
|
protected welcomed(id: string): Promise<void>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { contract } from './contract.js';
|
|
2
|
+
import { Faculty, WATCH } from './faculty.js';
|
|
3
|
+
const ASKS = {
|
|
4
|
+
watch: WATCH,
|
|
5
|
+
due: { description: 'bring my next round forward to this moment', input: { type: 'object', properties: { at: { type: 'number' } }, required: ['at'] } },
|
|
6
|
+
now: { description: 'this box’s moment', input: { type: 'object' } },
|
|
7
|
+
};
|
|
8
|
+
// What a being needs from a box that can wake her at a moment.
|
|
9
|
+
export const TIMER = contract('timer', ASKS);
|
|
10
|
+
const MINUTE = 60_000;
|
|
11
|
+
// A timeout further out than this is not trusted, so the arming is capped and
|
|
12
|
+
// made again when it goes off.
|
|
13
|
+
const DAY = 86_400_000;
|
|
14
|
+
export class Timer extends Faculty {
|
|
15
|
+
static asks = ASKS;
|
|
16
|
+
static fulfils = [TIMER];
|
|
17
|
+
#timeout;
|
|
18
|
+
#stopped = false;
|
|
19
|
+
#armed = null;
|
|
20
|
+
#round = null;
|
|
21
|
+
// The moment she is armed for, or null while she is asleep.
|
|
22
|
+
get armed() {
|
|
23
|
+
return this.#armed;
|
|
24
|
+
}
|
|
25
|
+
// A watcher says she is due sooner than the box knew. It only ever brings
|
|
26
|
+
// the round forward.
|
|
27
|
+
due(args) {
|
|
28
|
+
const at = args.at;
|
|
29
|
+
if (typeof at !== 'number' || !Number.isFinite(at))
|
|
30
|
+
return { error: 'a due carries the moment: { at }' };
|
|
31
|
+
const when = moment(at);
|
|
32
|
+
if (this.#armed === null || when < this.#armed)
|
|
33
|
+
this.#arm(when);
|
|
34
|
+
return { armed: this.#armed };
|
|
35
|
+
}
|
|
36
|
+
// Whoever was just filed is ticked at once, and what she answers is what
|
|
37
|
+
// the box arms for, so she is never waiting for a round nobody scheduled.
|
|
38
|
+
async welcomed(id) {
|
|
39
|
+
await this.round(this.box.now(), id);
|
|
40
|
+
}
|
|
41
|
+
// The arming went off. A moment still well ahead is the day's cap, so it is
|
|
42
|
+
// armed again rather than fired.
|
|
43
|
+
async rang() {
|
|
44
|
+
if (this.#stopped)
|
|
45
|
+
return [];
|
|
46
|
+
if (this.#armed !== null && this.#armed - this.box.now() > 1_000) {
|
|
47
|
+
this.arm(this.#armed);
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return this.fire(this.box.now());
|
|
51
|
+
}
|
|
52
|
+
// The device's own call at a moment it names: how a suite proves a wake
|
|
53
|
+
// without waiting for one and how an operator makes a round happen now.
|
|
54
|
+
fire(now) {
|
|
55
|
+
return this.round(now);
|
|
56
|
+
}
|
|
57
|
+
// One round, and never two at once.
|
|
58
|
+
round(now, only) {
|
|
59
|
+
if (this.#round)
|
|
60
|
+
return this.#round;
|
|
61
|
+
const mine = this.#run(now, only);
|
|
62
|
+
this.#round = mine;
|
|
63
|
+
return mine.finally(() => {
|
|
64
|
+
if (this.#round === mine)
|
|
65
|
+
this.#round = null;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async #run(now, only) {
|
|
69
|
+
if (only === undefined)
|
|
70
|
+
this.#arm(null);
|
|
71
|
+
const woke = await this.woke((w) => ({ ...w.args, now }), only);
|
|
72
|
+
if (woke === null) {
|
|
73
|
+
this.stop();
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
// A moment already gone is no arming.
|
|
77
|
+
const named = soonest(woke);
|
|
78
|
+
const said = named !== null && named > this.box.now() ? named : null;
|
|
79
|
+
const held = this.#armed;
|
|
80
|
+
const at = said === null ? held : held === null ? moment(said) : Math.min(held, moment(said));
|
|
81
|
+
if (at !== null && at !== this.#armed)
|
|
82
|
+
this.#arm(at);
|
|
83
|
+
else if (at === null && this.#armed !== null)
|
|
84
|
+
this.#arm(null);
|
|
85
|
+
return woke;
|
|
86
|
+
}
|
|
87
|
+
#arm(when) {
|
|
88
|
+
if (this.#stopped)
|
|
89
|
+
return;
|
|
90
|
+
this.#armed = when;
|
|
91
|
+
this.arm(when);
|
|
92
|
+
}
|
|
93
|
+
// The one thing a terrain says about itself: be woken at this moment, or at
|
|
94
|
+
// no moment at all. Here a timeout, capped at a day, unreferenced so the box
|
|
95
|
+
// stays up for its work and never for this.
|
|
96
|
+
arm(when) {
|
|
97
|
+
clearTimeout(this.#timeout);
|
|
98
|
+
this.#timeout = undefined;
|
|
99
|
+
if (when === null)
|
|
100
|
+
return;
|
|
101
|
+
this.#timeout = setTimeout(() => void this.rang(), Math.min(Math.max(when - this.box.now(), 0), DAY));
|
|
102
|
+
this.#timeout.unref?.();
|
|
103
|
+
}
|
|
104
|
+
// The device puts the box down.
|
|
105
|
+
stop() {
|
|
106
|
+
this.#stopped = true;
|
|
107
|
+
this.#armed = null;
|
|
108
|
+
this.arm(null);
|
|
109
|
+
}
|
|
110
|
+
now() {
|
|
111
|
+
return { now: this.box.now() };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const moment = (at) => Math.max(at, Math.floor(at / MINUTE) * MINUTE + 500);
|
|
115
|
+
function soonest(woke) {
|
|
116
|
+
let at = null;
|
|
117
|
+
for (const w of woke) {
|
|
118
|
+
if (w.out === null || typeof w.out !== 'object')
|
|
119
|
+
continue;
|
|
120
|
+
const next = w.out.next;
|
|
121
|
+
if (typeof next !== 'number' || !Number.isFinite(next))
|
|
122
|
+
continue;
|
|
123
|
+
if (at === null || next < at)
|
|
124
|
+
at = next;
|
|
125
|
+
}
|
|
126
|
+
return at;
|
|
127
|
+
}
|
package/dist/box/tree.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Every class in a tree, keyed by where it lives: the folders down to it and
|
|
2
|
+
// its export name, `box/Dock`. The key is the class's identity in a ward,
|
|
3
|
+
// as a being's key is hers, and it moves only when the class moves.
|
|
4
|
+
export function keyed(tree, at = '') {
|
|
5
|
+
const out = {};
|
|
6
|
+
for (const [name, v] of Object.entries(tree)) {
|
|
7
|
+
const key = at ? `${at}/${name}` : name;
|
|
8
|
+
if (typeof v === 'function' && 'prototype' in v)
|
|
9
|
+
out[key] = v;
|
|
10
|
+
else if (v !== null && typeof v === 'object' && !Array.isArray(v))
|
|
11
|
+
Object.assign(out, keyed(v, key));
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
package/dist/cli/client.d.ts
CHANGED
package/dist/cli/daemon.d.ts
CHANGED
|
@@ -2,9 +2,8 @@ import { DiskHarbor, type Hosted } from '../harbor/disk.ts';
|
|
|
2
2
|
import type { Harbor } from 'nervur/harbor';
|
|
3
3
|
import type { Avatar } from '../beings/index.ts';
|
|
4
4
|
export { avatarOf, letIn, find } from '../beings/join.ts';
|
|
5
|
-
import { type Ring } from '../
|
|
6
|
-
import { Grants } from '../
|
|
7
|
-
import { Agents } from '../dock/agents.ts';
|
|
5
|
+
import { type Ring } from '../box/index.ts';
|
|
6
|
+
import { Agents, Grants } from '../box/index.ts';
|
|
8
7
|
import type { OAuth } from '../mcp/oauth.ts';
|
|
9
8
|
import { Exchange } from '../mcp/allow.ts';
|
|
10
9
|
import { McpHttp } from '../mcp/http.ts';
|
|
@@ -43,8 +42,3 @@ export declare function admit(harbor: Harbor, hosted: Hosted, identity: string,
|
|
|
43
42
|
error?: string;
|
|
44
43
|
}>;
|
|
45
44
|
export declare function serve(dir: string, options?: Options): Promise<Serving>;
|
|
46
|
-
export declare const DOMAIN: RegExp;
|
|
47
|
-
export declare function routesFor(domain: string): Required<Routes>;
|
|
48
|
-
export declare function writeRoutes(dir: string, domain: string): Promise<Required<Routes>>;
|
|
49
|
-
export declare function readRoutes(dir: string): Promise<Routes | null>;
|
|
50
|
-
export declare function readApps(dir: string): Promise<Apps | null>;
|