@songsid/agend 2.3.2-beta.5 → 2.3.2-beta.7
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/backend/kiro-identity.d.ts +11 -0
- package/dist/backend/kiro-identity.js +16 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro-retired-model.d.ts +62 -0
- package/dist/backend/kiro-retired-model.js +160 -0
- package/dist/backend/kiro-retired-model.js.map +1 -0
- package/dist/backend/kiro.d.ts +17 -0
- package/dist/backend/kiro.js +77 -1
- package/dist/backend/kiro.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +11 -0
- package/dist/channel/adapters/discord.js +64 -10
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/config-validator.js +25 -3
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +5 -0
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +21 -5
- package/dist/fleet-manager.js +154 -133
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +14 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +14 -1
- package/dist/instance-lifecycle.d.ts +2 -0
- package/dist/instance-lifecycle.js +23 -8
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +16 -0
- package/dist/locale.js.map +1 -1
- package/dist/notice-delivery.d.ts +7 -6
- package/dist/notice-delivery.js +16 -18
- package/dist/notice-delivery.js.map +1 -1
- package/dist/office-share-page.d.ts +13 -0
- package/dist/office-share-page.js +82 -0
- package/dist/office-share-page.js.map +1 -0
- package/dist/office-share-service.d.ts +89 -0
- package/dist/office-share-service.js +172 -0
- package/dist/office-share-service.js.map +1 -0
- package/dist/office-share.d.ts +228 -0
- package/dist/office-share.js +489 -0
- package/dist/office-share.js.map +1 -0
- package/dist/public-web-gateway.js +6 -0
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/quickstart.js +0 -2
- package/dist/quickstart.js.map +1 -1
- package/dist/settings-api.js +1 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +48 -5
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.js +4 -3
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +0 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/types.d.ts +12 -3
- package/dist/ui/fleet-office-pack.js +7 -2
- package/dist/ui/fleet-office-skin.js +5 -4
- package/dist/ui/fleet-office-zones.js +17 -6
- package/dist/ui/fleet-office.js +20 -10
- package/dist/ui/office-share-dialog.js +145 -0
- package/dist/ui/office-share-page.js +116 -0
- package/dist/ui/panel-settings.js +12 -8
- package/dist/ui/settings-registry.js +2 -2
- package/dist/ui/settings-strings.js +6 -6
- package/dist/ui/shared/app.css +28 -0
- package/dist/web-api.d.ts +14 -0
- package/dist/web-api.js +91 -0
- package/dist/web-api.js.map +1 -1
- package/dist/web-office-skins.d.ts +2 -1
- package/dist/web-office-skins.js +2 -1
- package/dist/web-office-skins.js.map +1 -1
- package/dist/web-settings-patch.js +1 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/working-recovery.d.ts +2 -2
- package/dist/working-recovery.js +43 -8
- package/dist/working-recovery.js.map +1 -1
- package/dist/working-state-journal.d.ts +2 -0
- package/dist/working-state-journal.js +16 -1
- package/dist/working-state-journal.js.map +1 -1
- package/package.json +1 -1
- package/templates/windows-shortcuts.ps1 +151 -147
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2636 part 3: Office share links, running — the shares (office-share.ts OfficeShares), their gateway, and the quick
|
|
3
|
+
* tunnel they share. Design: #2636, confirmed with the leader's two additions (2026-10-25):
|
|
4
|
+
*
|
|
5
|
+
* - The tunnel starts with the first share and stops when the last one ends (expired, revoked, its owner no longer an
|
|
6
|
+
* admin, the feature turned off). trycloudflare.com limits new quick tunnels: a start it refuses (429) fails that one
|
|
7
|
+
* share with "try again later" — nothing retries on its own. A share is only handed out once the tunnel is up.
|
|
8
|
+
* - No step beyond the admin check (decision a94a1bab): whoever may make one gets the link at once.
|
|
9
|
+
* - Its own manager and lease (OFFICE_SHARE_TUNNEL_DIR): never the fleet's one tunnel lane (/web, /login) nor the
|
|
10
|
+
* public link's preview tunnel. A tunnel that goes away unexpectedly ends every share (their address was its host).
|
|
11
|
+
*/
|
|
12
|
+
import { ALL_OFFICES, OfficeShareLimiter, OfficeShares, createOfficeShareGateway, shareMaxMinutes } from "./office-share.js";
|
|
13
|
+
import { OFFICE_SHARE_PAGE, OFFICE_SHARE_PAGE_CSP, officeShareStatic } from "./office-share-page.js";
|
|
14
|
+
import { failureOf } from "./public-link-progress.js";
|
|
15
|
+
import { newTunnelSid } from "./tunnel/manager.js";
|
|
16
|
+
/** Where the share tunnel keeps its own lease (never the fleet's `tunnel.lease`). */
|
|
17
|
+
export const OFFICE_SHARE_TUNNEL_DIR = "office-share-tunnel";
|
|
18
|
+
const QUICK_TUNNEL_HOST = /^[a-z0-9-]+\.trycloudflare\.com$/;
|
|
19
|
+
/** The lease's own end: none — the tunnel ends with its last share, not on a clock. */
|
|
20
|
+
const NO_EXPIRY_WALL = Number.MAX_SAFE_INTEGER;
|
|
21
|
+
/** How often open shares are checked against who may still have them. */
|
|
22
|
+
export const SWEEP_MS = 30_000;
|
|
23
|
+
export class OfficeShareService {
|
|
24
|
+
deps;
|
|
25
|
+
shares;
|
|
26
|
+
gateway = null;
|
|
27
|
+
origin = null;
|
|
28
|
+
starting = null;
|
|
29
|
+
stopping = null;
|
|
30
|
+
abort = null;
|
|
31
|
+
unsubscribe;
|
|
32
|
+
limiter = new OfficeShareLimiter();
|
|
33
|
+
constructor(deps) {
|
|
34
|
+
this.deps = deps;
|
|
35
|
+
this.shares = deps.shares ?? new OfficeShares({ changed: list => this.onChanged(list) });
|
|
36
|
+
}
|
|
37
|
+
enabled() { return this.deps.config()?.enabled !== false; }
|
|
38
|
+
list() { return this.shares.list(); }
|
|
39
|
+
/**
|
|
40
|
+
* A new share and its URL, once the tunnel is up. `minutes` 15–max_hours×60 (60 if not given); `office` ALL_OFFICES
|
|
41
|
+
* or a stored office's id. A tunnel that cannot start ends the share at once and says why.
|
|
42
|
+
*/
|
|
43
|
+
async create(opts) {
|
|
44
|
+
if (!this.enabled())
|
|
45
|
+
return { ok: false, error: "disabled" };
|
|
46
|
+
const made = this.shares.create({ ...opts, maxMinutes: shareMaxMinutes(this.deps.config()?.max_hours), exists: id => this.deps.officeExists(id) });
|
|
47
|
+
if (!made.ok)
|
|
48
|
+
return { ok: false, error: made.error };
|
|
49
|
+
const up = await this.up();
|
|
50
|
+
if (!up.ok) {
|
|
51
|
+
this.shares.revoke(made.share.id);
|
|
52
|
+
return { ok: false, error: "tunnel", reason: up.reason };
|
|
53
|
+
}
|
|
54
|
+
// Ended while the tunnel came up (revoked, or the feature turned off meanwhile): not handed out.
|
|
55
|
+
if (!this.shares.find(made.token))
|
|
56
|
+
return { ok: false, error: "tunnel", reason: "closed" };
|
|
57
|
+
return { ok: true, share: made.share, url: `${up.origin}/o/${made.token}` };
|
|
58
|
+
}
|
|
59
|
+
revoke(id) { return this.shares.revoke(id); }
|
|
60
|
+
/** Shares no longer allowed: the feature off (all), an owner no longer an admin, an office deleted. */
|
|
61
|
+
sweep() {
|
|
62
|
+
if (!this.enabled())
|
|
63
|
+
return this.shares.revoke("all");
|
|
64
|
+
return this.shares.endWhere(s => !this.deps.ownerCurrent(s.owner) || (s.office !== ALL_OFFICES && !this.deps.officeExists(s.office)));
|
|
65
|
+
}
|
|
66
|
+
/** The fleet is stopping: every share ends and the tunnel goes. */
|
|
67
|
+
async close() {
|
|
68
|
+
this.shares.revoke("all");
|
|
69
|
+
await this.down("fleet stopping");
|
|
70
|
+
}
|
|
71
|
+
onChanged(list) {
|
|
72
|
+
const open = new Set(list.map(s => s.id));
|
|
73
|
+
for (const id of this.known)
|
|
74
|
+
if (!open.has(id))
|
|
75
|
+
this.limiter.forget(id);
|
|
76
|
+
this.known = open;
|
|
77
|
+
// While any is open, who may still have it is asked again every SWEEP_MS (an admin removed, the feature turned
|
|
78
|
+
// off, an office deleted end theirs without anyone looking).
|
|
79
|
+
if (list.length && !this.sweeper) {
|
|
80
|
+
this.sweeper = setInterval(() => this.sweep(), SWEEP_MS);
|
|
81
|
+
this.sweeper.unref?.();
|
|
82
|
+
}
|
|
83
|
+
if (!list.length && this.sweeper) {
|
|
84
|
+
clearInterval(this.sweeper);
|
|
85
|
+
this.sweeper = null;
|
|
86
|
+
}
|
|
87
|
+
if (!list.length)
|
|
88
|
+
void this.down("no shares");
|
|
89
|
+
try {
|
|
90
|
+
this.deps.changed?.(list);
|
|
91
|
+
}
|
|
92
|
+
catch { /* a listener never keeps a share or the tunnel */ }
|
|
93
|
+
}
|
|
94
|
+
known = new Set();
|
|
95
|
+
sweeper = null;
|
|
96
|
+
/** The tunnel, up or coming up (one start at a time; never retried here). */
|
|
97
|
+
up() {
|
|
98
|
+
if (this.origin)
|
|
99
|
+
return Promise.resolve({ ok: true, origin: this.origin });
|
|
100
|
+
return this.starting ??= this.start().finally(() => { this.starting = null; });
|
|
101
|
+
}
|
|
102
|
+
async start() {
|
|
103
|
+
await this.stopping?.catch(() => { });
|
|
104
|
+
const abort = this.abort = new AbortController();
|
|
105
|
+
const gateway = (this.deps.createGateway ?? createOfficeShareGateway)({
|
|
106
|
+
shares: this.shares, source: this.deps.source, limiter: this.limiter, statics: officeShareStatic,
|
|
107
|
+
page: () => ({ body: OFFICE_SHARE_PAGE, csp: OFFICE_SHARE_PAGE_CSP }),
|
|
108
|
+
});
|
|
109
|
+
this.gateway = gateway;
|
|
110
|
+
const fail = (reason, detail) => {
|
|
111
|
+
this.deps.log?.(`tunnel ${reason}`, detail);
|
|
112
|
+
if (this.gateway === gateway) {
|
|
113
|
+
gateway.close();
|
|
114
|
+
this.gateway = null;
|
|
115
|
+
}
|
|
116
|
+
void this.deps.manager.stop(`start failed: ${reason}`).catch(() => { });
|
|
117
|
+
return { ok: false, reason };
|
|
118
|
+
};
|
|
119
|
+
try {
|
|
120
|
+
const binary = await this.deps.ensure(abort.signal);
|
|
121
|
+
if (abort.signal.aborted)
|
|
122
|
+
return fail("closed");
|
|
123
|
+
const origin = await gateway.listen();
|
|
124
|
+
if (abort.signal.aborted)
|
|
125
|
+
return fail("closed");
|
|
126
|
+
const result = await this.deps.manager.start(this.deps.provider(binary.path), {
|
|
127
|
+
sid: newTunnelSid(), origin, pagePath: "/ready", readinessMarker: gateway.readinessMarker, expiresAt: NO_EXPIRY_WALL,
|
|
128
|
+
signal: abort.signal, onCandidateHost: host => { if (!abort.signal.aborted)
|
|
129
|
+
gateway.setHost(host); },
|
|
130
|
+
});
|
|
131
|
+
if (!result.ok)
|
|
132
|
+
return fail(failureOf(result.errorKind), result.message);
|
|
133
|
+
if (abort.signal.aborted)
|
|
134
|
+
return fail("closed");
|
|
135
|
+
const published = new URL(result.handle.pageUrl);
|
|
136
|
+
if (published.protocol !== "https:" || published.pathname !== "/ready" || !QUICK_TUNNEL_HOST.test(published.host))
|
|
137
|
+
return fail("bad-address");
|
|
138
|
+
gateway.setHost(published.host);
|
|
139
|
+
this.origin = published.origin;
|
|
140
|
+
// Gone by itself: every share's address was this host — they end, and the next share starts a new tunnel.
|
|
141
|
+
this.unsubscribe = result.handle.onUnexpectedExit(() => {
|
|
142
|
+
this.deps.log?.("tunnel exited");
|
|
143
|
+
this.origin = null;
|
|
144
|
+
this.shares.revoke("all");
|
|
145
|
+
});
|
|
146
|
+
this.deps.log?.("tunnel ready");
|
|
147
|
+
return { ok: true, origin: published.origin };
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
return fail(abort.signal.aborted ? "closed" : "unknown", err?.message);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
down(reason) {
|
|
154
|
+
if (!this.origin && !this.starting && !this.gateway)
|
|
155
|
+
return this.stopping ?? Promise.resolve();
|
|
156
|
+
this.origin = null;
|
|
157
|
+
this.abort?.abort();
|
|
158
|
+
this.abort = null;
|
|
159
|
+
this.unsubscribe?.();
|
|
160
|
+
this.unsubscribe = undefined;
|
|
161
|
+
this.gateway?.setHost(null);
|
|
162
|
+
this.gateway?.close();
|
|
163
|
+
this.gateway = null;
|
|
164
|
+
const stopping = this.stopping = (async () => {
|
|
165
|
+
await this.starting?.catch(() => { });
|
|
166
|
+
return this.deps.manager.stop(reason);
|
|
167
|
+
})().catch(() => { }).finally(() => { if (this.stopping === stopping)
|
|
168
|
+
this.stopping = null; });
|
|
169
|
+
return stopping;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=office-share-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"office-share-service.js","sourceRoot":"","sources":["../src/office-share-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { createServer, type Server } from "node:http";
|
|
2
|
+
import { type OfficeAct } from "./web-office-acts.js";
|
|
3
|
+
import type { Office } from "./web-office-store.js";
|
|
4
|
+
export declare const OFFICE_SHARE_LIMITS: {
|
|
5
|
+
/** Shares open at once, fleet-wide. */
|
|
6
|
+
readonly links: 10;
|
|
7
|
+
/** Lifetime: 1 h unless asked, 15 min to `web.office_share.max_hours` (24 at most). */
|
|
8
|
+
readonly defaultMinutes: 60;
|
|
9
|
+
readonly minMinutes: 15;
|
|
10
|
+
readonly maxHours: 24;
|
|
11
|
+
/** Per share, a sliding minute: requests, and bytes answered. */
|
|
12
|
+
readonly requestsPerMinute: 120;
|
|
13
|
+
readonly bytesPerMinute: number;
|
|
14
|
+
/** The page's static files (its code, the same for every share), all shares together. */
|
|
15
|
+
readonly staticRequestsPerMinute: 1200;
|
|
16
|
+
readonly staticBytesPerMinute: number;
|
|
17
|
+
};
|
|
18
|
+
/** "All offices": every stored office (and whoever is in none); otherwise a stored office's id. */
|
|
19
|
+
export declare const ALL_OFFICES = "all";
|
|
20
|
+
/**
|
|
21
|
+
* An office's members after the share's cut, when every one it named is out of the share: a team no chart has (a team's
|
|
22
|
+
* name has no control character), so the page finds nobody — never [], which the page reads as everyone.
|
|
23
|
+
*/
|
|
24
|
+
export declare const NOBODY = "team:\0";
|
|
25
|
+
/** An ended (or unknown) share's page: the same words whichever it is. */
|
|
26
|
+
export declare const ENDED_PAGE: string;
|
|
27
|
+
export interface OfficeShare {
|
|
28
|
+
/** A short handle for lists and revoking (never the token). */
|
|
29
|
+
id: string;
|
|
30
|
+
/** ALL_OFFICES, or a stored office's id. */
|
|
31
|
+
office: string;
|
|
32
|
+
/** Agents' own lines (office_set `say`) are shown. */
|
|
33
|
+
text: boolean;
|
|
34
|
+
/** Who made it (for the list and General's announcement). */
|
|
35
|
+
owner: string;
|
|
36
|
+
/** Wall-clock ms, for display; the expiry itself runs on the monotonic clock. */
|
|
37
|
+
createdAt: number;
|
|
38
|
+
expiresAt: number;
|
|
39
|
+
}
|
|
40
|
+
export type OfficeShareCreate = {
|
|
41
|
+
ok: true;
|
|
42
|
+
share: OfficeShare;
|
|
43
|
+
token: string;
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
error: "too_many" | "duration" | "office";
|
|
47
|
+
};
|
|
48
|
+
export declare const hashShareToken: (token: string) => string;
|
|
49
|
+
/** The longest a share may live, in minutes: `web.office_share.max_hours` (whole hours, 1–24), 24 by default. */
|
|
50
|
+
export declare function shareMaxMinutes(maxHours: unknown): number;
|
|
51
|
+
/**
|
|
52
|
+
* The open shares. Each ends at its deadline (monotonic; a timer tells `changed` then, and find() refuses it from that
|
|
53
|
+
* moment whatever the timer did), when revoked, or when its owner is no longer allowed (endWhere). `changed` hears every
|
|
54
|
+
* change with the list as it is now — the tunnel follows it (none left: stop).
|
|
55
|
+
*/
|
|
56
|
+
export declare class OfficeShares {
|
|
57
|
+
private readonly deps;
|
|
58
|
+
private readonly byHash;
|
|
59
|
+
private readonly now;
|
|
60
|
+
private readonly wall;
|
|
61
|
+
constructor(deps?: {
|
|
62
|
+
/** Monotonic ms (performance.now by default). */
|
|
63
|
+
now?(): number;
|
|
64
|
+
/** Wall-clock ms (Date.now by default): only what is shown. */
|
|
65
|
+
wall?(): number;
|
|
66
|
+
changed?(shares: OfficeShare[]): void;
|
|
67
|
+
random?(n: number): Buffer;
|
|
68
|
+
setTimer?(fn: () => void, ms: number): ReturnType<typeof setTimeout> | null;
|
|
69
|
+
clearTimer?(t: ReturnType<typeof setTimeout>): void;
|
|
70
|
+
});
|
|
71
|
+
/** A new share: `minutes` from 15 to `maxMinutes`; `office` ALL_OFFICES or an id `exists` knows. */
|
|
72
|
+
create(opts: {
|
|
73
|
+
office: string;
|
|
74
|
+
minutes?: number;
|
|
75
|
+
text?: boolean;
|
|
76
|
+
owner: string;
|
|
77
|
+
maxMinutes?: number;
|
|
78
|
+
exists?(office: string): boolean;
|
|
79
|
+
}): OfficeShareCreate;
|
|
80
|
+
/** The open share a token names, or null (unknown, malformed, ended). */
|
|
81
|
+
find(token: string): OfficeShare | null;
|
|
82
|
+
/** The open shares, soonest to end first. */
|
|
83
|
+
list(): OfficeShare[];
|
|
84
|
+
/** End one share by id, or every one (ALL_OFFICES is not an id: "all" ends all). Returns how many ended. */
|
|
85
|
+
revoke(id: string): number;
|
|
86
|
+
/** End every share `test` picks (an owner no longer a fleet admin, an office deleted, the feature turned off). */
|
|
87
|
+
endWhere(test: (share: OfficeShare) => boolean): number;
|
|
88
|
+
private drop;
|
|
89
|
+
private prune;
|
|
90
|
+
private announce;
|
|
91
|
+
}
|
|
92
|
+
/** Per key (a share's id): requests and bytes in a sliding minute (monotonic, memory only). */
|
|
93
|
+
export declare class OfficeShareLimiter {
|
|
94
|
+
private readonly now;
|
|
95
|
+
private readonly limits;
|
|
96
|
+
private readonly seen;
|
|
97
|
+
constructor(now?: () => number, limits?: {
|
|
98
|
+
requests: number;
|
|
99
|
+
bytes: number;
|
|
100
|
+
});
|
|
101
|
+
/** A request arrives: false when the key has had its requests or bytes this minute. */
|
|
102
|
+
admit(id: string): boolean;
|
|
103
|
+
/** What the admitted request answered, in bytes. */
|
|
104
|
+
sent(id: string, bytes: number): void;
|
|
105
|
+
forget(id: string): void;
|
|
106
|
+
private window;
|
|
107
|
+
}
|
|
108
|
+
/** Where a share's picture is read from: the fleet's own reads, as they are now. */
|
|
109
|
+
export interface OfficeShareSource {
|
|
110
|
+
/** The status frame (getUiStatus): its `instances`. */
|
|
111
|
+
status(): unknown;
|
|
112
|
+
/** The org chart (orgChart): `general` and `teams`. */
|
|
113
|
+
org(): {
|
|
114
|
+
general?: unknown;
|
|
115
|
+
teams?: unknown;
|
|
116
|
+
};
|
|
117
|
+
/** The stored offices (web-office-store.ts list()). */
|
|
118
|
+
offices(): {
|
|
119
|
+
rev: number;
|
|
120
|
+
offices: Office[];
|
|
121
|
+
};
|
|
122
|
+
/** The live acts (OfficeActs live()). */
|
|
123
|
+
acts(): OfficeAct[];
|
|
124
|
+
/** /ui/traffic's snapshot. */
|
|
125
|
+
traffic(): {
|
|
126
|
+
now: string;
|
|
127
|
+
items: unknown[];
|
|
128
|
+
delegations?: unknown[];
|
|
129
|
+
};
|
|
130
|
+
/** The instances "Needs you" names now (one entry per item). */
|
|
131
|
+
needs(): string[];
|
|
132
|
+
/** cross_instance_visibility "hidden" now. */
|
|
133
|
+
hidden(name: string): boolean;
|
|
134
|
+
/** The skin packs the host keeps (web-office-skins.ts). */
|
|
135
|
+
skins?: {
|
|
136
|
+
list(): {
|
|
137
|
+
packs: Array<{
|
|
138
|
+
id: string;
|
|
139
|
+
}>;
|
|
140
|
+
} & Record<string, unknown>;
|
|
141
|
+
get(id: string): unknown;
|
|
142
|
+
picture(id: string, sha: string): Buffer | null;
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* One share's picture, read once from `src`: who is in it (`scope`), and each read the page makes, cut to them.
|
|
147
|
+
* `offices`: the shared office, or every stored office; `null` when the shared office is gone (the share then shows
|
|
148
|
+
* nothing and its owner is told it ended).
|
|
149
|
+
*/
|
|
150
|
+
export declare class OfficeShareView {
|
|
151
|
+
private readonly share;
|
|
152
|
+
private readonly src;
|
|
153
|
+
readonly offices: Office[] | null;
|
|
154
|
+
readonly scope: ReadonlySet<string>;
|
|
155
|
+
private readonly people;
|
|
156
|
+
/** fleet.yaml's teams with someone shown, each with only its shown members. */
|
|
157
|
+
private readonly teams;
|
|
158
|
+
constructor(share: Pick<OfficeShare, "office" | "text"> & Partial<Pick<OfficeShare, "expiresAt">>, src: OfficeShareSource);
|
|
159
|
+
/** The status frame as the page's app store takes it, and the chart's structure (no descriptions, threads or info). */
|
|
160
|
+
state(): Record<string, unknown>;
|
|
161
|
+
/**
|
|
162
|
+
* /ui/offices's answer, the shared office (or all) only, each cut to the share (#2637 review): its members only those
|
|
163
|
+
* shown (an instance, or a team with someone shown; NOBODY when none is), and a seat kept for someone not shown keeps
|
|
164
|
+
* its place but not the name. The stored offices are never changed.
|
|
165
|
+
*/
|
|
166
|
+
officeList(): {
|
|
167
|
+
rev: number;
|
|
168
|
+
offices: Office[];
|
|
169
|
+
readable: boolean;
|
|
170
|
+
};
|
|
171
|
+
/** /ui/office-acts's answer: the acts of the people shown, in a shown office, without their line unless shared. */
|
|
172
|
+
acts(): {
|
|
173
|
+
acts: OfficeAct[];
|
|
174
|
+
now: number;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* A pack's answer as this share gets it (#2637 review): its manifest's instance names cut to the share — `byInstance`
|
|
178
|
+
* only for people shown, a furniture item's `activeFor` only them (none left: [], which watches nobody, never the
|
|
179
|
+
* missing key, which watches everyone). A copy: the kept pack is never changed.
|
|
180
|
+
*/
|
|
181
|
+
pack(answer: unknown): unknown;
|
|
182
|
+
/** The packs the shown offices are drawn in (a pack no shown office names is not offered). */
|
|
183
|
+
skinIds(): Set<string>;
|
|
184
|
+
}
|
|
185
|
+
export interface OfficeShareGateway {
|
|
186
|
+
readonly server: Server;
|
|
187
|
+
readonly readinessMarker: string;
|
|
188
|
+
listen(): Promise<URL>;
|
|
189
|
+
setHost(host: string | null): void;
|
|
190
|
+
close(): void;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The share tunnel's gateway. GET and HEAD only, on this tunnel's own Host; `/ready` for the tunnel's probe; then
|
|
194
|
+
* `/o/<token>` and its reads, for an open share within its limits:
|
|
195
|
+
*
|
|
196
|
+
* /o/<t> the page (`page`; 404 while there is none)
|
|
197
|
+
* /o/<t>/state OfficeShareView.state()
|
|
198
|
+
* /o/<t>/offices OfficeShareView.officeList()
|
|
199
|
+
* /o/<t>/office-acts OfficeShareView.acts()
|
|
200
|
+
* /o/<t>/office-skins the packs the shown offices use
|
|
201
|
+
* /o/<t>/office-skins/<id> one of them, its instance names cut to the share (never cached)
|
|
202
|
+
* /o/<t>/office-skins/<id>/img/<sha>.png its picture (immutable)
|
|
203
|
+
*
|
|
204
|
+
* and, with no token, the page's static files at the dashboard's own paths (`statics`: `/assets/<name>`, `/ui/js/<name>`;
|
|
205
|
+
* office-share-page.ts names them), limited together (OFFICE_SHARE_LIMITS.static*).
|
|
206
|
+
*
|
|
207
|
+
* Anything else, an unknown or ended token, an office since deleted: an empty 404 (no word on why: a stranger learns
|
|
208
|
+
* nothing). Over the limit: 429. The share is asked again before each answer is sent. No cookie is read or set and
|
|
209
|
+
* nothing is logged per request.
|
|
210
|
+
*/
|
|
211
|
+
export declare function createOfficeShareGateway(opts: {
|
|
212
|
+
shares: Pick<OfficeShares, "find">;
|
|
213
|
+
source: OfficeShareSource;
|
|
214
|
+
limiter?: OfficeShareLimiter;
|
|
215
|
+
/** The page for a share (HTML), or null while there is none. */
|
|
216
|
+
page?(share: OfficeShare, token: string): {
|
|
217
|
+
body: string;
|
|
218
|
+
csp: string;
|
|
219
|
+
} | null;
|
|
220
|
+
/** A static file of the page by its path (`/assets/…`, `/ui/js/…`), or null. */
|
|
221
|
+
statics?(path: string): {
|
|
222
|
+
body: Buffer;
|
|
223
|
+
type: string;
|
|
224
|
+
} | null;
|
|
225
|
+
staticLimiter?: OfficeShareLimiter;
|
|
226
|
+
create?: typeof createServer;
|
|
227
|
+
marker?: string;
|
|
228
|
+
}): OfficeShareGateway;
|