@cxxi-lab/hub-sdk 0.0.0-stage → 1.12.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/LICENSE +21 -0
- package/frame/index.ts +8 -0
- package/frame/module.tsx +56 -0
- package/frame/runtime.css +16 -0
- package/frame/sdk.ts +113 -0
- package/package.json +36 -5
- package/protocol.ts +40 -0
- package/shared.json +10 -0
- package/types/index.d.ts +1089 -0
- package/version.json +3 -0
- package/README.md +0 -3
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
// @cxxi-lab/hub-sdk 1.12.0: generated by app/scripts/emit-sdk-types.mjs from app/src/sdk — do not edit.
|
|
2
|
+
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
|
|
3
|
+
import { ComponentType, HTMLInputTypeAttribute, MouseEventHandler, ReactNode, TdHTMLAttributes, ThHTMLAttributes } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The platform contract: everything the shared UI needs from its host shell.
|
|
7
|
+
*
|
|
8
|
+
* `app/` depends only on this file; each shell provides an implementation
|
|
9
|
+
* (`electron.ts` today, a Capacitor one later). The domain types below are the
|
|
10
|
+
* single source of truth — the Electron preload imports them from here
|
|
11
|
+
* (`@hub/app/platform`) so `window.api` cannot drift from this contract.
|
|
12
|
+
*/
|
|
13
|
+
export type VpnStatus = "disabled" | "starting" | "connected" | "error";
|
|
14
|
+
export interface VpnInfo {
|
|
15
|
+
status: VpnStatus;
|
|
16
|
+
port: number | null;
|
|
17
|
+
error: string | null;
|
|
18
|
+
endpoint: string | null;
|
|
19
|
+
}
|
|
20
|
+
export type UpdatePhase = "available" | "downloading" | "verifying" | "installing" | "error";
|
|
21
|
+
export interface UpdateState {
|
|
22
|
+
phase: UpdatePhase;
|
|
23
|
+
version: string;
|
|
24
|
+
currentVersion: string;
|
|
25
|
+
progress: {
|
|
26
|
+
percent: number;
|
|
27
|
+
transferred: number;
|
|
28
|
+
total: number;
|
|
29
|
+
bytesPerSecond: number;
|
|
30
|
+
} | null;
|
|
31
|
+
error: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface IntegrationCookie {
|
|
34
|
+
name: string;
|
|
35
|
+
value: string;
|
|
36
|
+
}
|
|
37
|
+
export interface IntegrationReferer {
|
|
38
|
+
url: string;
|
|
39
|
+
cdnHostPattern: string;
|
|
40
|
+
}
|
|
41
|
+
export interface WebsiteIntegration {
|
|
42
|
+
cookies?: IntegrationCookie[];
|
|
43
|
+
referer?: IntegrationReferer;
|
|
44
|
+
}
|
|
45
|
+
export type CompanionEngineStatus = "off" | "starting" | "on" | "error";
|
|
46
|
+
export interface CompanionEngineInfo {
|
|
47
|
+
status: CompanionEngineStatus;
|
|
48
|
+
external: boolean;
|
|
49
|
+
error: string | null;
|
|
50
|
+
}
|
|
51
|
+
/** One line of a conversation's activity log: what happened in the background. */
|
|
52
|
+
export interface CompanionLogEntry {
|
|
53
|
+
id: number;
|
|
54
|
+
at: number;
|
|
55
|
+
level: "info" | "warn" | "error";
|
|
56
|
+
text: string;
|
|
57
|
+
/** raw text behind the line (reply, notes, scene, tags…) */
|
|
58
|
+
detail?: string;
|
|
59
|
+
}
|
|
60
|
+
/** What the companion is doing right now: writing, recording a voice message or taking a photo. */
|
|
61
|
+
export type CompanionActivity = "typing" | "recording" | "photo" | null;
|
|
62
|
+
export interface CompanionProfile {
|
|
63
|
+
/** conversation id ('main' for the first one) */
|
|
64
|
+
id: string;
|
|
65
|
+
name: string;
|
|
66
|
+
/** name shown in the app (`name` unless the profile sets another one) */
|
|
67
|
+
label: string;
|
|
68
|
+
userName: string;
|
|
69
|
+
/** media file of the profile picture */
|
|
70
|
+
avatar: string | null;
|
|
71
|
+
}
|
|
72
|
+
export interface CompanionMessage {
|
|
73
|
+
id: number;
|
|
74
|
+
role: "user" | "companion";
|
|
75
|
+
kind: "text" | "image" | "audio";
|
|
76
|
+
content: string;
|
|
77
|
+
mediaPath: string | null;
|
|
78
|
+
createdAt: number;
|
|
79
|
+
readAt: number | null;
|
|
80
|
+
proactive: boolean;
|
|
81
|
+
userReaction: string | null;
|
|
82
|
+
userReactionAt: number | null;
|
|
83
|
+
companionReaction: string | null;
|
|
84
|
+
replyId: number | null;
|
|
85
|
+
}
|
|
86
|
+
/** A module's window as the host knows it (its component stays in the module). */
|
|
87
|
+
export interface ModuleWindowInfo {
|
|
88
|
+
moduleId: string;
|
|
89
|
+
id: string;
|
|
90
|
+
title: string;
|
|
91
|
+
width?: number;
|
|
92
|
+
height?: number;
|
|
93
|
+
menu?: {
|
|
94
|
+
label: string;
|
|
95
|
+
accelerator?: string;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/** Unsubscribe handle returned by every `on…` listener. */
|
|
99
|
+
export type Unsubscribe = () => void;
|
|
100
|
+
/**
|
|
101
|
+
* A module as published or installed: its catalogue entry (app/scripts/build-modules.mjs), enough
|
|
102
|
+
* to list it before it is installed.
|
|
103
|
+
*/
|
|
104
|
+
export interface ModulePackage {
|
|
105
|
+
id: string;
|
|
106
|
+
version: string;
|
|
107
|
+
/** the SDK range it needs (^x.y.z) */
|
|
108
|
+
sdk: string;
|
|
109
|
+
css: boolean;
|
|
110
|
+
/** it ships models.json (its collections' models); absent in entries from before the flag */
|
|
111
|
+
models?: boolean;
|
|
112
|
+
label: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
/** its icon's [width, height, svg path] */
|
|
115
|
+
icon?: [
|
|
116
|
+
number,
|
|
117
|
+
number,
|
|
118
|
+
string
|
|
119
|
+
] | null;
|
|
120
|
+
local?: boolean;
|
|
121
|
+
requires?: string[];
|
|
122
|
+
requiresCapabilities?: string[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A restricted module (offered only to the accounts given it), as the server lists it for this
|
|
126
|
+
* account: its catalogue entry (JSON, with its files) signed alone with the update key, and a
|
|
127
|
+
* signed, expiring URL per file.
|
|
128
|
+
*/
|
|
129
|
+
export interface RestrictedModulePackage {
|
|
130
|
+
entry: string;
|
|
131
|
+
/** base64 */
|
|
132
|
+
signature: string;
|
|
133
|
+
urls: Record<string, string>;
|
|
134
|
+
/** an entry signed by its publisher's key, not the official one: that key's certificate */
|
|
135
|
+
publisher?: PublisherCertificate;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* A key allowed to sign one module's entries (a third party's): `certificate` is the JSON text
|
|
139
|
+
* {"module": <id>, "key": <its Ed25519 public key, SPKI DER, base64>}, `signature` the official
|
|
140
|
+
* key's (base64). Trusted for that module only.
|
|
141
|
+
*/
|
|
142
|
+
export interface PublisherCertificate {
|
|
143
|
+
certificate: string;
|
|
144
|
+
signature: string;
|
|
145
|
+
}
|
|
146
|
+
/** The modules of this device: installed from the server's catalogue (signed), on demand. */
|
|
147
|
+
export interface ModuleStoreApi {
|
|
148
|
+
/**
|
|
149
|
+
* The modules this app can have, one entry each: the installed ones, and the published ones
|
|
150
|
+
* compatible with this app (the newest). Without the server (VPN down), the installed ones and
|
|
151
|
+
* the last catalogue read.
|
|
152
|
+
*/
|
|
153
|
+
list(): Promise<{
|
|
154
|
+
modules: ModulePackage[];
|
|
155
|
+
installed: string[];
|
|
156
|
+
}>;
|
|
157
|
+
/** Installs a module (its newest compatible version), usable at once. Already installed: at once. */
|
|
158
|
+
install(id: string): Promise<ModulePackage>;
|
|
159
|
+
/** Removes a module's files from this device (a loaded one stays until the app restarts). */
|
|
160
|
+
uninstall(id: string): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* The restricted modules this account was given (the server's list, renderer side: it needs the
|
|
163
|
+
* session): the store checks each signature, lists and installs them like the others.
|
|
164
|
+
*/
|
|
165
|
+
setRestricted(modules: RestrictedModulePackage[]): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* The newer versions already downloaded than what runs ("id version"). A module's version is
|
|
168
|
+
* decided at start; these wait for activate() (or the next start).
|
|
169
|
+
*/
|
|
170
|
+
pending(): Promise<string[]>;
|
|
171
|
+
/** Runs them from now on: the app reloads its page right after. Returns what changed. */
|
|
172
|
+
activate(): Promise<string[]>;
|
|
173
|
+
}
|
|
174
|
+
/** One of the app's notifications, as the system shows it. */
|
|
175
|
+
export interface SystemNotification {
|
|
176
|
+
id: number;
|
|
177
|
+
title: string;
|
|
178
|
+
body: string | null;
|
|
179
|
+
/** a state's count (null: an event); shown again when it grows */
|
|
180
|
+
count: number | null;
|
|
181
|
+
/** where a click leads in the app */
|
|
182
|
+
link: string | null;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The app's notifications shown by the system (desktop: its notification center, the dock marks
|
|
186
|
+
* hub as having some). The page hands over its whole list at each change.
|
|
187
|
+
*/
|
|
188
|
+
export interface SystemNotificationsApi {
|
|
189
|
+
sync(items: SystemNotification[]): void;
|
|
190
|
+
/** a click on one: the app opens its link */
|
|
191
|
+
onOpen(cb: (link: string) => void): Unsubscribe;
|
|
192
|
+
}
|
|
193
|
+
/** A document of a module's store (docs/plan-module-storage.md), local or on the server. */
|
|
194
|
+
export interface StoredDocument {
|
|
195
|
+
collection: string;
|
|
196
|
+
id: string;
|
|
197
|
+
data: unknown;
|
|
198
|
+
/** milliseconds: the last write wins */
|
|
199
|
+
updatedAt: number;
|
|
200
|
+
}
|
|
201
|
+
export interface StoredFile {
|
|
202
|
+
path: string;
|
|
203
|
+
size: number;
|
|
204
|
+
sha256: string;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The modules' store on this device (desktop: SQLite in the main process): the scopes the account
|
|
208
|
+
* keeps local. Every call names the account (a hash), the module and the scope.
|
|
209
|
+
*/
|
|
210
|
+
export interface LocalStoreApi {
|
|
211
|
+
list(account: string, module: string, scope: string, collection?: string): Promise<StoredDocument[]>;
|
|
212
|
+
get(account: string, module: string, scope: string, collection: string, id: string): Promise<StoredDocument | null>;
|
|
213
|
+
put(account: string, module: string, scope: string, documents: StoredDocument[]): Promise<void>;
|
|
214
|
+
delete(account: string, module: string, scope: string, collection: string, id: string): Promise<void>;
|
|
215
|
+
purge(account: string, module: string, scope: string): Promise<void>;
|
|
216
|
+
files(account: string, module: string, scope: string): Promise<StoredFile[]>;
|
|
217
|
+
putFile(account: string, module: string, scope: string, path: string, body: Uint8Array): Promise<void>;
|
|
218
|
+
getFile(account: string, module: string, scope: string, path: string): Promise<Uint8Array | null>;
|
|
219
|
+
deleteFile(account: string, module: string, scope: string, path: string): Promise<void>;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* The app's notifications by push (Android: Firebase Cloud Messaging): the server sends each change,
|
|
223
|
+
* the shell shows it as a system notification, even with the app closed.
|
|
224
|
+
*/
|
|
225
|
+
export interface PushApi {
|
|
226
|
+
/** Asks the permission to notify (once) and returns this device's token for the server; null: no push. */
|
|
227
|
+
register(): Promise<{
|
|
228
|
+
token: string;
|
|
229
|
+
platform: "android";
|
|
230
|
+
} | null>;
|
|
231
|
+
/** a notification tapped: the app opens its link */
|
|
232
|
+
onOpen(cb: (link: string) => void): Unsubscribe;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* The AI companion. Desktop-only today (local Ollama); a shell without it
|
|
236
|
+
* reports `capabilities.companion === false` and the UI hides the module.
|
|
237
|
+
*/
|
|
238
|
+
export interface CompanionApi {
|
|
239
|
+
getEngine(): Promise<CompanionEngineInfo>;
|
|
240
|
+
setEngine(on: boolean): void;
|
|
241
|
+
onEngineChange(cb: (info: CompanionEngineInfo) => void): Unsubscribe;
|
|
242
|
+
listConversations(): Promise<CompanionProfile[]>;
|
|
243
|
+
getProfile(conversationId: string): Promise<CompanionProfile>;
|
|
244
|
+
listMessages(conversationId: string, opts: {
|
|
245
|
+
limit: number;
|
|
246
|
+
beforeId?: number;
|
|
247
|
+
}): Promise<CompanionMessage[]>;
|
|
248
|
+
send(conversationId: string, content: string): Promise<CompanionMessage>;
|
|
249
|
+
react(conversationId: string, id: number, emoji: string | null): Promise<CompanionMessage | null>;
|
|
250
|
+
regenerate(conversationId: string): Promise<number[] | null>;
|
|
251
|
+
onMessagesDeleted(cb: (conversationId: string, ids: number[]) => void): Unsubscribe;
|
|
252
|
+
onMessageUpdate(cb: (conversationId: string, message: CompanionMessage) => void): Unsubscribe;
|
|
253
|
+
listImages(conversationId: string): Promise<CompanionMessage[]>;
|
|
254
|
+
onMessage(cb: (conversationId: string, message: CompanionMessage) => void): Unsubscribe;
|
|
255
|
+
onActivity(cb: (conversationId: string, activity: CompanionActivity) => void): Unsubscribe;
|
|
256
|
+
onError(cb: (conversationId: string, error: string) => void): Unsubscribe;
|
|
257
|
+
getActivity(conversationId: string): Promise<CompanionActivity>;
|
|
258
|
+
listLog(conversationId: string): Promise<CompanionLogEntry[]>;
|
|
259
|
+
onLog(cb: (conversationId: string, entry: CompanionLogEntry) => void): Unsubscribe;
|
|
260
|
+
getUnread(): Promise<Record<string, number>>;
|
|
261
|
+
onUnreadChange(cb: (counts: Record<string, number>) => void): Unsubscribe;
|
|
262
|
+
markRead(conversationId: string): void;
|
|
263
|
+
}
|
|
264
|
+
/** What the web layer hands the native player: desktop VideoPlayerModal's webview inputs. */
|
|
265
|
+
export interface NativePlayerOptions {
|
|
266
|
+
url: string;
|
|
267
|
+
/** Referer header for the first load (embed mode), null for none */
|
|
268
|
+
referrer: string | null;
|
|
269
|
+
/** script run once the DOM is ready; the player is ready when its promise settles (null: ready on load) */
|
|
270
|
+
bootstrap: string | null;
|
|
271
|
+
/** shown while the page loads */
|
|
272
|
+
poster: string | null;
|
|
273
|
+
/** swipe neighbours: whether there is one each way, and its poster (slides in while swiping) */
|
|
274
|
+
hasPrev: boolean;
|
|
275
|
+
hasNext: boolean;
|
|
276
|
+
prevPoster: string | null;
|
|
277
|
+
nextPoster: string | null;
|
|
278
|
+
/** posters to fetch ahead (videos further away), so fast swipes find them ready */
|
|
279
|
+
preloadPosters: string[];
|
|
280
|
+
}
|
|
281
|
+
/** A Font Awesome icon as drawn natively: viewBox size + SVG path data. */
|
|
282
|
+
export interface NativeIcon {
|
|
283
|
+
width: number;
|
|
284
|
+
height: number;
|
|
285
|
+
path: string;
|
|
286
|
+
}
|
|
287
|
+
/** One of the player's actions, behind its gear button. Taps come back through `onAction`. */
|
|
288
|
+
export interface NativePlayerAction {
|
|
289
|
+
id: string;
|
|
290
|
+
label: string;
|
|
291
|
+
icon: NativeIcon;
|
|
292
|
+
/** danger: a destructive action awaiting its confirming second tap */
|
|
293
|
+
tone: "default" | "danger";
|
|
294
|
+
/** running: shown dimmed, taps ignored */
|
|
295
|
+
busy: boolean;
|
|
296
|
+
}
|
|
297
|
+
/** Overlay over the video (tags, info); tapping it closes it. */
|
|
298
|
+
export interface NativePlayerPanel {
|
|
299
|
+
chips?: string[];
|
|
300
|
+
rows?: Array<[
|
|
301
|
+
label: string,
|
|
302
|
+
value: string
|
|
303
|
+
]>;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Fullscreen native player above the app (Capacitor). Stands in for the
|
|
307
|
+
* <webview> of the video player modal on shells without `capabilities.webview`;
|
|
308
|
+
* only reached when `capabilities.nativePlayer` is true.
|
|
309
|
+
*/
|
|
310
|
+
export interface NativePlayerApi {
|
|
311
|
+
open(options: NativePlayerOptions): Promise<void>;
|
|
312
|
+
exec(script: string): Promise<void>;
|
|
313
|
+
reload(): Promise<void>;
|
|
314
|
+
close(): Promise<void>;
|
|
315
|
+
/** the actions behind the gear (the whole list each time), and the icons of the gear and close buttons */
|
|
316
|
+
setActions(actions: NativePlayerAction[], icons: {
|
|
317
|
+
menu: NativeIcon;
|
|
318
|
+
close: NativeIcon;
|
|
319
|
+
}): Promise<void>;
|
|
320
|
+
showPanel(panel: NativePlayerPanel | null): Promise<void>;
|
|
321
|
+
onReady(cb: () => void): Unsubscribe;
|
|
322
|
+
onClosed(cb: () => void): Unsubscribe;
|
|
323
|
+
onAction(cb: (id: string) => void): Unsubscribe;
|
|
324
|
+
/** a swipe went through: the web side switches to that neighbour and opens it */
|
|
325
|
+
onNavigate(cb: (direction: "prev" | "next") => void): Unsubscribe;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* What the host shell can do. The UI reads these to hide what a shell lacks
|
|
329
|
+
* (e.g. the Capacitor shell has no native windows and no companion engine).
|
|
330
|
+
*/
|
|
331
|
+
export interface PlatformCapabilities {
|
|
332
|
+
/** AI companion engine (local LLM) */
|
|
333
|
+
companion: boolean;
|
|
334
|
+
/** integrated WireGuard tunnel controllable from the UI */
|
|
335
|
+
vpn: boolean;
|
|
336
|
+
/** modules hidden behind a reveal shortcut (Privacy) */
|
|
337
|
+
privateMode: boolean;
|
|
338
|
+
/** in-app self-update */
|
|
339
|
+
updates: boolean;
|
|
340
|
+
/** per-site cookie/referer injection for the embedded browser/player */
|
|
341
|
+
integrations: boolean;
|
|
342
|
+
/** embedded <webview> (the integrated browser and the external player) */
|
|
343
|
+
webview: boolean;
|
|
344
|
+
/** fullscreen native player (`platform.player`) instead of an embedded <webview> */
|
|
345
|
+
nativePlayer: boolean;
|
|
346
|
+
/** separate native windows (Settings/History/About) vs in-app pages */
|
|
347
|
+
nativeWindows: boolean;
|
|
348
|
+
/** global audio mute toggle */
|
|
349
|
+
mute: boolean;
|
|
350
|
+
/** developer "local server" toggle */
|
|
351
|
+
devMode: boolean;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* The host shell, as seen by the shared UI. Method names mirror the Electron
|
|
355
|
+
* preload so the Electron adapter is a thin pass-through; other shells
|
|
356
|
+
* implement what they can and advertise the rest through `capabilities`.
|
|
357
|
+
*/
|
|
358
|
+
export interface Platform {
|
|
359
|
+
readonly name: "electron" | "capacitor";
|
|
360
|
+
/**
|
|
361
|
+
* Value sent in the `X-Hub-Client` header on the server's cookie-authenticated
|
|
362
|
+
* auth routes. Must be one of the server's accepted client values
|
|
363
|
+
* (SecurityController::CLIENT_HEADER_VALUES).
|
|
364
|
+
*/
|
|
365
|
+
readonly client: "desktop" | "android";
|
|
366
|
+
readonly capabilities: PlatformCapabilities;
|
|
367
|
+
/** Host runtime info for the About screen (OS + engine versions). */
|
|
368
|
+
readonly runtime: {
|
|
369
|
+
os: string;
|
|
370
|
+
versions: Record<string, string>;
|
|
371
|
+
};
|
|
372
|
+
ping(): Promise<string>;
|
|
373
|
+
getDevMode(): Promise<boolean>;
|
|
374
|
+
onDevModeChange(cb: (value: boolean) => void): Unsubscribe;
|
|
375
|
+
notifyAuthState(authenticated: boolean): void;
|
|
376
|
+
/**
|
|
377
|
+
* The server just set a new refresh-token cookie (login, refresh: each
|
|
378
|
+
* refresh revokes the previous token). The shell makes it durable at once,
|
|
379
|
+
* or an app killed before its cookie store flushes comes back with a revoked
|
|
380
|
+
* token and a forced re-login.
|
|
381
|
+
*/
|
|
382
|
+
persistSession(): void;
|
|
383
|
+
/**
|
|
384
|
+
* The hidden modules revealed this session (Privacy: their shortcut), shared by the app's
|
|
385
|
+
* windows; forgotten at restart. Hidden again all at once by Ctrl+Alt+Q.
|
|
386
|
+
*/
|
|
387
|
+
getRevealedModules(): Promise<string[]>;
|
|
388
|
+
/**
|
|
389
|
+
* A secret kept by this device only, encrypted by the system (desktop: the OS keyring through
|
|
390
|
+
* safeStorage; Android: the Keystore): the account's data key (core/crypto). null: none.
|
|
391
|
+
*/
|
|
392
|
+
getDeviceSecret(name: string): Promise<string | null>;
|
|
393
|
+
/** null: forgotten. */
|
|
394
|
+
setDeviceSecret(name: string, value: string | null): Promise<void>;
|
|
395
|
+
setRevealedModules(ids: string[]): void;
|
|
396
|
+
onRevealedModulesChange(cb: (ids: string[]) => void): Unsubscribe;
|
|
397
|
+
onWebviewKey(cb: (event: {
|
|
398
|
+
key: string;
|
|
399
|
+
ctrl: boolean;
|
|
400
|
+
alt: boolean;
|
|
401
|
+
}) => void): Unsubscribe;
|
|
402
|
+
showMessage(message: string): Promise<void>;
|
|
403
|
+
getMuted(): Promise<boolean>;
|
|
404
|
+
setMuted(value: boolean): void;
|
|
405
|
+
onMutedChange(cb: (value: boolean) => void): Unsubscribe;
|
|
406
|
+
getVpnStatus(): Promise<VpnInfo>;
|
|
407
|
+
startVpn(): Promise<void>;
|
|
408
|
+
stopVpn(): Promise<void>;
|
|
409
|
+
onVpnStatusChange(cb: (info: VpnInfo) => void): Unsubscribe;
|
|
410
|
+
replaceVpnConfig(): Promise<{
|
|
411
|
+
ok: boolean;
|
|
412
|
+
path: string | null;
|
|
413
|
+
error?: string;
|
|
414
|
+
}>;
|
|
415
|
+
/** Opens the Settings window, on a screen when given (e.g. 'security'). */
|
|
416
|
+
openSettings(target?: string): Promise<void>;
|
|
417
|
+
/** In the Settings window: a screen another window asked for. */
|
|
418
|
+
onSettingsGoto(cb: (target: string) => void): Unsubscribe;
|
|
419
|
+
/**
|
|
420
|
+
* Opens an OAuth consent page in an app window; resolves with the URL the provider redirected to
|
|
421
|
+
* (`redirectUri` + the code), caught before it loads, or null when the window was closed.
|
|
422
|
+
*/
|
|
423
|
+
authorizeOAuth(url: string, redirectUri: string): Promise<string | null>;
|
|
424
|
+
/** Opens a module's window (ModuleManifest.windows), registered beforehand. */
|
|
425
|
+
openModuleWindow(moduleId: string, windowId: string): Promise<void>;
|
|
426
|
+
/** The windows of the active modules: what the app menu offers (replaces the previous list). */
|
|
427
|
+
registerModuleWindows(windows: ModuleWindowInfo[]): Promise<void>;
|
|
428
|
+
openAbout(): Promise<void>;
|
|
429
|
+
getAppVersion(): Promise<string>;
|
|
430
|
+
getUpdateState(): Promise<UpdateState | null>;
|
|
431
|
+
onUpdateStateChange(cb: (state: UpdateState) => void): Unsubscribe;
|
|
432
|
+
installUpdate(): Promise<void>;
|
|
433
|
+
skipUpdate(): Promise<void>;
|
|
434
|
+
quitApp(): Promise<void>;
|
|
435
|
+
listIntegrations(): Promise<Array<{
|
|
436
|
+
host: string;
|
|
437
|
+
updatedAt: number;
|
|
438
|
+
}>>;
|
|
439
|
+
applyIntegration(payload: {
|
|
440
|
+
host: string;
|
|
441
|
+
integration: WebsiteIntegration;
|
|
442
|
+
updatedAt: number;
|
|
443
|
+
}): Promise<void>;
|
|
444
|
+
removeIntegration(host: string): Promise<void>;
|
|
445
|
+
/**
|
|
446
|
+
* Hosts the embedded browser / video player blocks (ads, trackers), compiled
|
|
447
|
+
* by the back. The shell persists the list; the version lets the UI skip a
|
|
448
|
+
* download when it already holds the latest one.
|
|
449
|
+
*/
|
|
450
|
+
getPlayerBlocklistVersion(): Promise<string | null>;
|
|
451
|
+
setPlayerBlocklist(version: string, hosts: string[]): Promise<void>;
|
|
452
|
+
readonly companion: CompanionApi;
|
|
453
|
+
readonly player: NativePlayerApi;
|
|
454
|
+
/** null: the shell shows none (Android: they come by push, `push`) */
|
|
455
|
+
readonly systemNotifications: SystemNotificationsApi | null;
|
|
456
|
+
/** null: no push (desktop: the open app shows them itself) */
|
|
457
|
+
readonly push: PushApi | null;
|
|
458
|
+
/** null: no local store here (Android, for now): a scope kept local isn't reachable on this device */
|
|
459
|
+
readonly localStore: LocalStoreApi | null;
|
|
460
|
+
/** null: the modules come from their sources (development), all of them, nothing to install */
|
|
461
|
+
readonly moduleStore: ModuleStoreApi | null;
|
|
462
|
+
/**
|
|
463
|
+
* Where an isolated hubby's frame loads from (its own origin: desktop hubby://<id>/, Android
|
|
464
|
+
* https://<id>.hubby.localhost/); null where hubbies can't run isolated (yet).
|
|
465
|
+
*/
|
|
466
|
+
hubbyUrl(module: string): string | null;
|
|
467
|
+
}
|
|
468
|
+
export declare const platform: Platform;
|
|
469
|
+
/**
|
|
470
|
+
* Cross-module "timeline" capability. A module MAY expose dated items it owns (e.g.
|
|
471
|
+
* workout sessions) that any calendar/agenda surface can display, plus an optional
|
|
472
|
+
* self-contained detail view opened when an item is interacted with.
|
|
473
|
+
*
|
|
474
|
+
* Fully agnostic: no producer or consumer is named here. `payload` is opaque data the
|
|
475
|
+
* PRODUCING module attaches and reads back in its own renderer — consumers never touch it.
|
|
476
|
+
*/
|
|
477
|
+
export interface TimelineItem {
|
|
478
|
+
/** globally unique, namespaced by the source module, e.g. 'workout:123' */
|
|
479
|
+
id: string;
|
|
480
|
+
date: Date;
|
|
481
|
+
title: string;
|
|
482
|
+
/** the module that produced it (for grouping / coloring) */
|
|
483
|
+
moduleId: string;
|
|
484
|
+
/** where a consumer can send the user for the full item (deep-link fallback) */
|
|
485
|
+
href: string;
|
|
486
|
+
/** optional presentation hint: a CSS color used as the chip accent (generic) */
|
|
487
|
+
color?: string;
|
|
488
|
+
/** opaque module-private data, read only by the producing module's renderDetail */
|
|
489
|
+
payload?: unknown;
|
|
490
|
+
}
|
|
491
|
+
/** Fetch a module's dated items overlapping [start, end). */
|
|
492
|
+
export type TimelineItemsProvider = (start: Date, end: Date) => Promise<TimelineItem[]>;
|
|
493
|
+
export interface TimelineDetailContext {
|
|
494
|
+
onClose: () => void;
|
|
495
|
+
/** call after a mutation so the consumer can refresh its data */
|
|
496
|
+
onChanged: () => void;
|
|
497
|
+
}
|
|
498
|
+
/** Render a self-contained detail view (e.g. a modal) for one of the module's items. */
|
|
499
|
+
export type TimelineDetailRenderer = (item: TimelineItem, ctx: TimelineDetailContext) => ReactNode;
|
|
500
|
+
/** Planner's calendar: dated items in a range, and how to show one of them. */
|
|
501
|
+
export interface TimelineContribution {
|
|
502
|
+
items: TimelineItemsProvider;
|
|
503
|
+
renderDetail?: TimelineDetailRenderer;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* What a module can offer the others, by contribution point. A module consuming a point never
|
|
507
|
+
* knows who contributes to it; a contribution lives only while its module is enabled and shown
|
|
508
|
+
* (a private module's contributions disappear with it outside private mode). New points are added
|
|
509
|
+
* here, as part of the SDK contract.
|
|
510
|
+
*/
|
|
511
|
+
export interface ContributionPoints {
|
|
512
|
+
/** Planner's calendar: items dated by another module */
|
|
513
|
+
timeline: TimelineContribution;
|
|
514
|
+
}
|
|
515
|
+
export type ContributionPoint = keyof ContributionPoints;
|
|
516
|
+
/** One module's contribution to a point. */
|
|
517
|
+
export interface Contribution<P extends ContributionPoint> {
|
|
518
|
+
moduleId: string;
|
|
519
|
+
value: ContributionPoints[P];
|
|
520
|
+
}
|
|
521
|
+
export interface ModuleNavItem {
|
|
522
|
+
to: string;
|
|
523
|
+
label: string;
|
|
524
|
+
/** optional live label (e.g. a name read at runtime + a badge), rendered instead of `label` */
|
|
525
|
+
Label?: ComponentType;
|
|
526
|
+
/** the block of the sidebar it sits in, under this title (consecutive items share it) */
|
|
527
|
+
section?: string;
|
|
528
|
+
/** its icon in the phone's bottom bar (default: its section's, then the module's) */
|
|
529
|
+
icon?: IconDefinition;
|
|
530
|
+
/** the page the module opens on (default: the first item) */
|
|
531
|
+
home?: boolean;
|
|
532
|
+
}
|
|
533
|
+
export interface ModuleWindow {
|
|
534
|
+
/** unique in the module ([a-z0-9-]) */
|
|
535
|
+
id: string;
|
|
536
|
+
title: string;
|
|
537
|
+
Component: ComponentType;
|
|
538
|
+
width?: number;
|
|
539
|
+
height?: number;
|
|
540
|
+
/** an entry of the app menu (Window) opening it */
|
|
541
|
+
menu?: {
|
|
542
|
+
label: string;
|
|
543
|
+
accelerator?: string;
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
export interface ModuleRoute {
|
|
547
|
+
path: string;
|
|
548
|
+
Component: ComponentType;
|
|
549
|
+
/** kept mounted (KeepAlive) so page state survives navigation */
|
|
550
|
+
persistent?: boolean;
|
|
551
|
+
/** also answers the paths under it (an isolated hubby: /<id>/… all go to its frame) */
|
|
552
|
+
prefix?: boolean;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Desktop-side module manifest. The desktop owns the module catalogue (which modules
|
|
556
|
+
* exist, their nav and pages); the server advertises which it can serve + which the
|
|
557
|
+
* user enabled. `id` must match the server module slug (singular, e.g. 'workout').
|
|
558
|
+
*/
|
|
559
|
+
/**
|
|
560
|
+
* A shared feature a module may support:
|
|
561
|
+
* - 'notifications': its notifications (the top bar's dot, the bell, its settings section);
|
|
562
|
+
* - 'vpn': the VPN tunnel, run only while the module is open (its pages wait for it);
|
|
563
|
+
* - 'amounts': it shows amounts (money…): its Privacy can blur them (the SDK's Amount).
|
|
564
|
+
*/
|
|
565
|
+
export type ModuleSupport = "notifications" | "vpn" | "amounts";
|
|
566
|
+
/** A scope of a module's data whose place the account chooses (docs/plan-module-storage.md). */
|
|
567
|
+
export interface StorageScope {
|
|
568
|
+
id: string;
|
|
569
|
+
/** shown in its settings (Data) */
|
|
570
|
+
label: string;
|
|
571
|
+
modes: Array<"local" | "server">;
|
|
572
|
+
default: "local" | "server";
|
|
573
|
+
/** each item may live elsewhere than the scope says (notes kept local…) */
|
|
574
|
+
perItem?: boolean;
|
|
575
|
+
}
|
|
576
|
+
export interface ModuleManifest {
|
|
577
|
+
id: string;
|
|
578
|
+
label: string;
|
|
579
|
+
/** its card on the home page */
|
|
580
|
+
icon?: IconDefinition;
|
|
581
|
+
/** one line under its name on the home page */
|
|
582
|
+
description?: string;
|
|
583
|
+
/** its own part of its settings screen (Modules > the module), under the common one */
|
|
584
|
+
Settings?: ComponentType;
|
|
585
|
+
/**
|
|
586
|
+
* Runs entirely on this machine with no server counterpart: always shown, outside the
|
|
587
|
+
* server's enabled list and the front/server integrity diff.
|
|
588
|
+
*/
|
|
589
|
+
local?: boolean;
|
|
590
|
+
/**
|
|
591
|
+
* Host capabilities this module needs (see PlatformCapabilities). A shell that
|
|
592
|
+
* lacks any of them hides the module entirely — e.g. 'companion' and 'webview'
|
|
593
|
+
* are desktop-only, so AI never appears in the mobile shell.
|
|
594
|
+
*/
|
|
595
|
+
requiresCapabilities?: Array<keyof PlatformCapabilities>;
|
|
596
|
+
navItems: ModuleNavItem[];
|
|
597
|
+
/**
|
|
598
|
+
* Runs isolated, in a sandboxed frame (docs/plan-module-architecture.md § 4 ter): none of its
|
|
599
|
+
* code is loaded in the app; its navigation is the one its frame announces.
|
|
600
|
+
*/
|
|
601
|
+
isolated?: boolean;
|
|
602
|
+
/** the icons of its nav sections (by title): a section is a tab of the phone's bottom bar */
|
|
603
|
+
navSections?: Record<string, IconDefinition>;
|
|
604
|
+
routes: ModuleRoute[];
|
|
605
|
+
/**
|
|
606
|
+
* Modules this one cannot work without (ids): it is only active while they all are, and
|
|
607
|
+
* the Modules screen says so. For a feature that only makes sense with another module, prefer
|
|
608
|
+
* a contribution of that module (`contributes`).
|
|
609
|
+
*/
|
|
610
|
+
requires?: string[];
|
|
611
|
+
/**
|
|
612
|
+
* The shared features it supports (as its server module does): each brings its part of the
|
|
613
|
+
* module's settings and its plumbing in the app, e.g. 'notifications' (the top bar's badge).
|
|
614
|
+
*/
|
|
615
|
+
supports?: ModuleSupport[];
|
|
616
|
+
/** The scopes of its data that can live on the server or on each device (Data in its settings). */
|
|
617
|
+
storage?: {
|
|
618
|
+
scopes: StorageScope[];
|
|
619
|
+
};
|
|
620
|
+
/** What this module offers the others, by contribution point (see ContributionPoints). */
|
|
621
|
+
contributes?: Partial<ContributionPoints>;
|
|
622
|
+
/**
|
|
623
|
+
* Contexts this module needs around the whole app while it is active (e.g. a job followed from
|
|
624
|
+
* any page), outermost first.
|
|
625
|
+
*/
|
|
626
|
+
providers?: Array<ComponentType<{
|
|
627
|
+
children: ReactNode;
|
|
628
|
+
}>>;
|
|
629
|
+
/** Windows of its own (desktop only), opened with platform.openModuleWindow(moduleId, id). */
|
|
630
|
+
windows?: ModuleWindow[];
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* A module's module.json: its version, the SDK it needs, and how it is described before it is
|
|
634
|
+
* installed (the store lists modules from these, see scripts/build-modules.mjs). `icon` is a Font
|
|
635
|
+
* Awesome solid icon name, the one its manifest shows.
|
|
636
|
+
*/
|
|
637
|
+
export interface ModuleInfo {
|
|
638
|
+
version: string;
|
|
639
|
+
sdk: string;
|
|
640
|
+
label: string;
|
|
641
|
+
description?: string;
|
|
642
|
+
icon?: string;
|
|
643
|
+
local?: boolean;
|
|
644
|
+
requires?: string[];
|
|
645
|
+
requiresCapabilities?: string[];
|
|
646
|
+
/** shared features (ModuleSupport) */
|
|
647
|
+
supports?: string[];
|
|
648
|
+
/** the scopes of its data the account places (local or server) */
|
|
649
|
+
storage?: {
|
|
650
|
+
scopes: StorageScope[];
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
export type Described = Pick<ModuleManifest, "label" | "description" | "local" | "requires" | "requiresCapabilities" | "supports" | "storage">;
|
|
654
|
+
/** The manifest's descriptive part, from its module.json (one source for both). */
|
|
655
|
+
export declare function describe(info: ModuleInfo): Described;
|
|
656
|
+
/**
|
|
657
|
+
* What the active modules offer at a contribution point (none outside the main window): only
|
|
658
|
+
* those the account allowed it (their `module:` scope).
|
|
659
|
+
*/
|
|
660
|
+
export declare function useContributions<P extends ContributionPoint>(point: P): Contribution<P>[];
|
|
661
|
+
/** Whether a module's amounts are blurred (its Privacy setting). */
|
|
662
|
+
export declare function useAmountsBlurred(module: string): boolean;
|
|
663
|
+
/** Whether a module shows: not hidden, or revealed this session. */
|
|
664
|
+
export declare function useModuleVisible(id: string): boolean;
|
|
665
|
+
/**
|
|
666
|
+
* Whether a hidden module is revealed this session (the former "private mode"): the players pause
|
|
667
|
+
* when it turns false (Ctrl+Alt+Q). Kept for the modules built before useModuleVisible.
|
|
668
|
+
*/
|
|
669
|
+
export declare function usePrivateMode(): boolean;
|
|
670
|
+
/**
|
|
671
|
+
* An amount of a module (money, a balance…): blurred when the module's Privacy asks for it
|
|
672
|
+
* (`supports: ['amounts']`), a click shows that one until it is clicked again, the page is left or
|
|
673
|
+
* Ctrl+Alt+Q. Not for an amount being typed (a field): only shown ones.
|
|
674
|
+
*/
|
|
675
|
+
export declare function Amount({ module, children, className }: {
|
|
676
|
+
module: string;
|
|
677
|
+
children: ReactNode;
|
|
678
|
+
className?: string;
|
|
679
|
+
}): import("react").JSX.Element;
|
|
680
|
+
/**
|
|
681
|
+
* The modules' generic store (docs/plan-module-storage.md). A module declares scopes of its data
|
|
682
|
+
* (module.json `storage`); the account chooses, scope by scope, where each one lives:
|
|
683
|
+
* - 'server': on the server (/api/data/<module>/_store, App\Service\Storage\DataStore), shared by its devices;
|
|
684
|
+
* - 'local': on each device (platform.localStore), never sent anywhere.
|
|
685
|
+
* The module writes the same way in both (openStore). A scope declared `perItem` lets an item live
|
|
686
|
+
* elsewhere than the scope says: a list merges both places.
|
|
687
|
+
*/
|
|
688
|
+
export type StorageMode = "local" | "server";
|
|
689
|
+
/** A document as the module sees it: where it lives, with its data. */
|
|
690
|
+
export interface StoreItem<T = unknown> {
|
|
691
|
+
id: string;
|
|
692
|
+
collection: string;
|
|
693
|
+
data: T;
|
|
694
|
+
updatedAt: number;
|
|
695
|
+
mode: StorageMode;
|
|
696
|
+
}
|
|
697
|
+
/** Not reachable here: the scope is local and this device has no local store (Android, for now). */
|
|
698
|
+
export declare class StoreUnavailableError extends Error {
|
|
699
|
+
constructor();
|
|
700
|
+
}
|
|
701
|
+
/** Re-renders when a module's modes change (read, or a move). */
|
|
702
|
+
export declare function useStorageModes(module: string): number;
|
|
703
|
+
export interface Store {
|
|
704
|
+
/** where the scope lives now */
|
|
705
|
+
readonly mode: StorageMode;
|
|
706
|
+
list<T = unknown>(collection: string): Promise<Array<StoreItem<T>>>;
|
|
707
|
+
get<T = unknown>(collection: string, id: string): Promise<StoreItem<T> | null>;
|
|
708
|
+
/** writes an item (where the scope lives, or `mode` for a perItem scope) */
|
|
709
|
+
put<T = unknown>(collection: string, id: string, data: T, options?: {
|
|
710
|
+
mode?: StorageMode;
|
|
711
|
+
}): Promise<void>;
|
|
712
|
+
delete(collection: string, id: string): Promise<void>;
|
|
713
|
+
putFile(path: string, body: Uint8Array | Blob): Promise<void>;
|
|
714
|
+
/** a URL an <img>/<audio> can load, null when there is no such file */
|
|
715
|
+
fileUrl(path: string): Promise<string | null>;
|
|
716
|
+
deleteFile(path: string): Promise<void>;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* A scope of a module's store, where the account placed it. Read the mode again on each call: a
|
|
720
|
+
* move between places changes it.
|
|
721
|
+
*/
|
|
722
|
+
export declare function openStore(module: string, scope: string): Store;
|
|
723
|
+
/**
|
|
724
|
+
* Non-2xx response. `message` stays `HTTP <status>` (callers match on it); `detail`
|
|
725
|
+
* carries the server's problem+json explanation when there is one.
|
|
726
|
+
*/
|
|
727
|
+
export declare class ApiError extends Error {
|
|
728
|
+
readonly status: number;
|
|
729
|
+
readonly detail: string | null;
|
|
730
|
+
constructor(status: number, detail: string | null);
|
|
731
|
+
}
|
|
732
|
+
/** Best human-readable message for a caught error: server detail first. */
|
|
733
|
+
export declare function errorMessage(err: unknown, fallback?: string): string;
|
|
734
|
+
export declare function api<T>(path: string, options?: RequestInit, retry?: boolean): Promise<T>;
|
|
735
|
+
/**
|
|
736
|
+
* The server's base URL. Given the path called, the routes of the modules reached through the VPN
|
|
737
|
+
* (their data, their capabilities, their media: as they declare, core/module/declarations.ts) go to
|
|
738
|
+
* the name only reachable through it (prod): the desktop routes that name through the tunnel, the
|
|
739
|
+
* phone routes everything through it in private mode. Development: one local server.
|
|
740
|
+
*/
|
|
741
|
+
export declare function getApiBaseUrl(path?: string): string;
|
|
742
|
+
export type StreamEventHandler = (event: string, data: unknown) => void;
|
|
743
|
+
/**
|
|
744
|
+
* Opens an authenticated server-sent event stream — over fetch, so it can POST a
|
|
745
|
+
* body and carry the token — and resolves once the server closes it. Like api(),
|
|
746
|
+
* a 401 refreshes the token once before giving up.
|
|
747
|
+
*/
|
|
748
|
+
export declare function apiStream(path: string, options: RequestInit, onEvent: StreamEventHandler, retry?: boolean): Promise<void>;
|
|
749
|
+
/**
|
|
750
|
+
* Parses a text/event-stream body, calling onEvent with each event name and its
|
|
751
|
+
* JSON-decoded data. Comments, empty blocks and undecodable data are skipped.
|
|
752
|
+
*/
|
|
753
|
+
export declare function readEventStream(body: ReadableStream<Uint8Array>, onEvent: StreamEventHandler): Promise<void>;
|
|
754
|
+
export declare function fetchSettings<T = Record<string, unknown>>(scope: string): Promise<T>;
|
|
755
|
+
export declare function updateSettings<T = Record<string, unknown>>(scope: string, values: Partial<T>): Promise<T>;
|
|
756
|
+
/** Standard JSON envelopes returned by the hub API. */
|
|
757
|
+
export interface SingleResponse<T> {
|
|
758
|
+
data: T;
|
|
759
|
+
}
|
|
760
|
+
export interface ListResponse<T> {
|
|
761
|
+
data: T[];
|
|
762
|
+
}
|
|
763
|
+
/** Paginated list envelope (data + page metadata). */
|
|
764
|
+
export interface PaginatedResponse<T> {
|
|
765
|
+
data: T[];
|
|
766
|
+
meta: {
|
|
767
|
+
page: number;
|
|
768
|
+
limit: number;
|
|
769
|
+
total: number;
|
|
770
|
+
pages: number;
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
export declare function ensureIntegrationForUrl(rawUrl: string): Promise<void>;
|
|
774
|
+
/**
|
|
775
|
+
* Sync probe — true if the domain has already been checked (positive or
|
|
776
|
+
* negative). Lets call-sites skip the async await on hot paths.
|
|
777
|
+
*/
|
|
778
|
+
export declare function isIntegrationKnownSync(rawUrl: string): boolean;
|
|
779
|
+
/** A site's player scripts, served by the back (the web socle, Resources/<host>/). */
|
|
780
|
+
export interface PlayerScripts {
|
|
781
|
+
/** run on the video page (page mode) to isolate the player */
|
|
782
|
+
page: string | null;
|
|
783
|
+
/** run on a bare embed player (embed mode) */
|
|
784
|
+
embed: string | null;
|
|
785
|
+
pause: string | null;
|
|
786
|
+
}
|
|
787
|
+
export interface PlayerConfig {
|
|
788
|
+
/** the host whose scripts apply to the requested domain */
|
|
789
|
+
host: string;
|
|
790
|
+
scripts: PlayerScripts;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* The player config for a domain, null when the back has no scripts for the
|
|
794
|
+
* site (it is then loaded as-is). A failed lookup resolves null too, and is
|
|
795
|
+
* retried next time.
|
|
796
|
+
*/
|
|
797
|
+
export declare function getPlayerConfig(domain: string): Promise<PlayerConfig | null>;
|
|
798
|
+
/**
|
|
799
|
+
* Makes sure the shell holds the back's current player blocklist before a
|
|
800
|
+
* player loads a site: once per session, a 304 when nothing changed. A failure
|
|
801
|
+
* leaves the shell's previous list in place and is retried next time.
|
|
802
|
+
*/
|
|
803
|
+
export declare function syncPlayerBlocklist(): Promise<void>;
|
|
804
|
+
export type ButtonColor = "primary" | "secondary" | "danger";
|
|
805
|
+
export interface ButtonProps {
|
|
806
|
+
label?: ReactNode;
|
|
807
|
+
children?: ReactNode;
|
|
808
|
+
icon?: IconDefinition;
|
|
809
|
+
color?: ButtonColor;
|
|
810
|
+
isDisabled?: boolean;
|
|
811
|
+
isLoading?: boolean;
|
|
812
|
+
className?: string;
|
|
813
|
+
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
814
|
+
type?: "button" | "submit" | "reset";
|
|
815
|
+
title?: string;
|
|
816
|
+
"aria-label"?: string;
|
|
817
|
+
}
|
|
818
|
+
export declare function Button({ label, children, icon, onClick, className, color, isDisabled, isLoading, type, ...rest }: ButtonProps): import("react").JSX.Element;
|
|
819
|
+
export type IconButtonColor = "primary" | "secondary" | "danger" | "dark";
|
|
820
|
+
export type IconButtonShape = "square" | "circle";
|
|
821
|
+
export type IconButtonSize = "xxs" | "xs" | "sm" | "md" | "xl";
|
|
822
|
+
export interface IconButtonProps {
|
|
823
|
+
icon: IconDefinition;
|
|
824
|
+
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
825
|
+
className?: string;
|
|
826
|
+
active?: boolean;
|
|
827
|
+
highlighted?: boolean;
|
|
828
|
+
isDisabled?: boolean;
|
|
829
|
+
isLoading?: boolean;
|
|
830
|
+
color?: IconButtonColor;
|
|
831
|
+
shape?: IconButtonShape;
|
|
832
|
+
size?: IconButtonSize;
|
|
833
|
+
type?: "button" | "submit" | "reset";
|
|
834
|
+
title?: string;
|
|
835
|
+
"aria-label"?: string;
|
|
836
|
+
"aria-expanded"?: boolean;
|
|
837
|
+
}
|
|
838
|
+
export declare function IconButton({ icon, onClick, className, active, highlighted, isDisabled, isLoading, color, shape, size, type, ...rest }: IconButtonProps): import("react").JSX.Element;
|
|
839
|
+
type FormData$1 = Record<string, unknown>;
|
|
840
|
+
export interface FormProps {
|
|
841
|
+
initialData?: FormData$1;
|
|
842
|
+
title?: string;
|
|
843
|
+
actions?: ReactNode;
|
|
844
|
+
onSubmit: (data: FormData$1) => void | Promise<void>;
|
|
845
|
+
children: ReactNode;
|
|
846
|
+
className?: string;
|
|
847
|
+
}
|
|
848
|
+
export declare function Form({ initialData, children, ...props }: FormProps): import("react").JSX.Element;
|
|
849
|
+
export interface InputFieldProps {
|
|
850
|
+
name: string;
|
|
851
|
+
label?: string;
|
|
852
|
+
type?: HTMLInputTypeAttribute;
|
|
853
|
+
required?: boolean;
|
|
854
|
+
placeholder?: string;
|
|
855
|
+
autoComplete?: string;
|
|
856
|
+
autoFocus?: boolean;
|
|
857
|
+
min?: number | string;
|
|
858
|
+
max?: number | string;
|
|
859
|
+
step?: number | string;
|
|
860
|
+
}
|
|
861
|
+
export declare function InputField({ name, label, type, required, placeholder, ...rest }: InputFieldProps): import("react").JSX.Element;
|
|
862
|
+
/**
|
|
863
|
+
* A switch between two choices (this || that, not on/off): both labels sit in the track, the
|
|
864
|
+
* thumb slides under the picked one. SwitchField's look, blue on both sides.
|
|
865
|
+
*/
|
|
866
|
+
export declare function ChoiceSwitch<V extends string>({ value, options, label, onChange }: {
|
|
867
|
+
value: V;
|
|
868
|
+
/** `count`: a number shown after the label (e.g. results waiting there) */
|
|
869
|
+
options: [
|
|
870
|
+
{
|
|
871
|
+
value: V;
|
|
872
|
+
label: string;
|
|
873
|
+
icon?: ReactNode;
|
|
874
|
+
count?: number;
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
value: V;
|
|
878
|
+
label: string;
|
|
879
|
+
icon?: ReactNode;
|
|
880
|
+
count?: number;
|
|
881
|
+
}
|
|
882
|
+
];
|
|
883
|
+
/** said to screen readers */
|
|
884
|
+
label: string;
|
|
885
|
+
onChange: (value: V) => void;
|
|
886
|
+
}): import("react").JSX.Element;
|
|
887
|
+
/** The SwitchField switch on its own, outside a form: controlled by `checked`. */
|
|
888
|
+
export declare function Switch({ checked, onChange, title }: {
|
|
889
|
+
checked: boolean;
|
|
890
|
+
onChange: (checked: boolean) => void;
|
|
891
|
+
title?: string;
|
|
892
|
+
}): import("react").JSX.Element;
|
|
893
|
+
/** The CheckboxField box on its own, outside a form: controlled by `checked`, its label beside it. */
|
|
894
|
+
export declare function Checkbox({ checked, label, onChange }: {
|
|
895
|
+
checked: boolean;
|
|
896
|
+
label: ReactNode;
|
|
897
|
+
onChange: (checked: boolean) => void;
|
|
898
|
+
}): import("react").JSX.Element;
|
|
899
|
+
export type DropdownColor = "menu" | "field";
|
|
900
|
+
export type DropdownSide = "left" | "center" | "right";
|
|
901
|
+
export type DropdownHeader = ReactNode | ((isOpen: boolean) => ReactNode);
|
|
902
|
+
export interface DropdownProps {
|
|
903
|
+
header?: DropdownHeader;
|
|
904
|
+
children: ReactNode;
|
|
905
|
+
color?: DropdownColor;
|
|
906
|
+
side?: DropdownSide;
|
|
907
|
+
isOpen?: boolean;
|
|
908
|
+
onOpenChange?: (open: boolean) => void;
|
|
909
|
+
/** extra classes on the root (e.g. to size it inside a flex row) */
|
|
910
|
+
className?: string;
|
|
911
|
+
}
|
|
912
|
+
export declare function Dropdown({ header, children, color, side, isOpen: controlledOpen, onOpenChange, className }: DropdownProps): import("react").JSX.Element;
|
|
913
|
+
export type ModalSize = "sm" | "md" | "lg" | "xl" | "full";
|
|
914
|
+
export interface ModalProps {
|
|
915
|
+
title?: ReactNode;
|
|
916
|
+
size?: ModalSize;
|
|
917
|
+
onClose: () => void;
|
|
918
|
+
children: ReactNode;
|
|
919
|
+
}
|
|
920
|
+
export declare function Modal({ title, size, onClose, children }: ModalProps): import("react").ReactPortal;
|
|
921
|
+
export interface ConfirmationModalProps {
|
|
922
|
+
title: ReactNode;
|
|
923
|
+
size?: ModalSize;
|
|
924
|
+
confirmLabel?: string;
|
|
925
|
+
confirmColor?: ButtonColor;
|
|
926
|
+
cancelLabel?: string;
|
|
927
|
+
onConfirm: () => void | Promise<void>;
|
|
928
|
+
onClose: () => void;
|
|
929
|
+
children: ReactNode;
|
|
930
|
+
}
|
|
931
|
+
export declare function ConfirmationModal({ title, size, confirmLabel, confirmColor, cancelLabel, onConfirm, onClose, children }: ConfirmationModalProps): import("react").JSX.Element;
|
|
932
|
+
export interface TableProps {
|
|
933
|
+
children: ReactNode;
|
|
934
|
+
className?: string;
|
|
935
|
+
}
|
|
936
|
+
export declare function Table({ children, className }: TableProps): import("react").JSX.Element;
|
|
937
|
+
export type TextAlign = "left" | "center" | "right";
|
|
938
|
+
export interface TableHeadProps extends Omit<ThHTMLAttributes<HTMLTableCellElement>, "children"> {
|
|
939
|
+
textAlign?: TextAlign;
|
|
940
|
+
mobile?: boolean;
|
|
941
|
+
children?: ReactNode;
|
|
942
|
+
}
|
|
943
|
+
export declare function TableHead({ textAlign, mobile, className, children, ...rest }: TableHeadProps): import("react").JSX.Element;
|
|
944
|
+
export declare function TableHeadGroup({ children }: {
|
|
945
|
+
children: ReactNode;
|
|
946
|
+
}): import("react").JSX.Element;
|
|
947
|
+
export interface TableRowProps {
|
|
948
|
+
children: ReactNode;
|
|
949
|
+
highlighted?: boolean;
|
|
950
|
+
className?: string;
|
|
951
|
+
}
|
|
952
|
+
export declare function TableRow({ children, highlighted, className }: TableRowProps): import("react").JSX.Element;
|
|
953
|
+
export declare function TableRowGroup({ children }: {
|
|
954
|
+
children: ReactNode;
|
|
955
|
+
}): import("react").JSX.Element;
|
|
956
|
+
export interface TableCellProps extends Omit<TdHTMLAttributes<HTMLTableCellElement>, "children"> {
|
|
957
|
+
to?: string;
|
|
958
|
+
textAlign?: TextAlign;
|
|
959
|
+
mobile?: boolean;
|
|
960
|
+
children?: ReactNode;
|
|
961
|
+
}
|
|
962
|
+
export declare function TableCell({ to, textAlign, mobile, className, children, ...rest }: TableCellProps): import("react").JSX.Element;
|
|
963
|
+
/** While `open`, the back button closes this (onClose). */
|
|
964
|
+
export declare function useBackHandler(open: boolean, onClose: () => void): void;
|
|
965
|
+
/** Whether `query` (a CSS media query) currently matches; follows its changes. */
|
|
966
|
+
export declare function useMediaQuery(query: string): boolean;
|
|
967
|
+
/** An entry of a tab's menu. */
|
|
968
|
+
export interface BottomBarEntry {
|
|
969
|
+
key: string;
|
|
970
|
+
label: string;
|
|
971
|
+
/** a live label rendered instead of `label` (in the menu) */
|
|
972
|
+
Label?: ComponentType;
|
|
973
|
+
icon?: IconDefinition;
|
|
974
|
+
active: boolean;
|
|
975
|
+
onClick: () => void;
|
|
976
|
+
/** a dot: something new there */
|
|
977
|
+
dot?: boolean;
|
|
978
|
+
/** a count beside it in the menu (e.g. unread) */
|
|
979
|
+
count?: number;
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* A tab of the bottom bar: an action of its own (`primary`: the page's main action, a round button
|
|
983
|
+
* in the brand colour, e.g. Mail's new message), or a menu opening upwards (its entries in groups,
|
|
984
|
+
* under an optional title). An active menu tab stands for its active entry (its icon and label,
|
|
985
|
+
* highlighted) unless `fixed`: e.g. Mail's "+" while one of its folders is open.
|
|
986
|
+
*/
|
|
987
|
+
export type BottomBarTab = {
|
|
988
|
+
key: string;
|
|
989
|
+
label: string;
|
|
990
|
+
icon: IconDefinition;
|
|
991
|
+
active: boolean;
|
|
992
|
+
onClick: () => void;
|
|
993
|
+
dot?: boolean;
|
|
994
|
+
primary?: boolean;
|
|
995
|
+
} | {
|
|
996
|
+
key: string;
|
|
997
|
+
label: string;
|
|
998
|
+
icon: IconDefinition;
|
|
999
|
+
menu: {
|
|
1000
|
+
title?: string;
|
|
1001
|
+
entries: BottomBarEntry[];
|
|
1002
|
+
}[];
|
|
1003
|
+
fixed?: boolean;
|
|
1004
|
+
};
|
|
1005
|
+
/**
|
|
1006
|
+
* The phone's bottom bar, like Android's: a module's pages (ModuleNav), or a page's own places
|
|
1007
|
+
* (Mail's folders). A menu closes on a choice, on back, on a tap beside it, on navigation.
|
|
1008
|
+
*/
|
|
1009
|
+
export declare function BottomBar({ tabs }: {
|
|
1010
|
+
tabs: BottomBarTab[];
|
|
1011
|
+
}): import("react").JSX.Element;
|
|
1012
|
+
/**
|
|
1013
|
+
* A module's declared collection, through the hubbies' generic API (/api/data/{module}/{collection},
|
|
1014
|
+
* docs/spec-module.md § 5): the current account's documents only, checked against the module's
|
|
1015
|
+
* model by the server. For the modules running in the app; an isolated hubby gets the same through
|
|
1016
|
+
* its host (@cxxi-lab/hub-sdk/frame, ui/hub-sdk/frame/sdk.ts).
|
|
1017
|
+
*
|
|
1018
|
+
* The fields its model declares encrypted are sealed here, on the device, before they leave, and
|
|
1019
|
+
* opened when they come back (core/crypto, docs/plan-storage-encryption.md § 3): the server only
|
|
1020
|
+
* ever holds them sealed. A filter on an "equality" one is sent as its blind index.
|
|
1021
|
+
*/
|
|
1022
|
+
/** What the platform adds to every document. */
|
|
1023
|
+
export interface HubbyDocument {
|
|
1024
|
+
id: string;
|
|
1025
|
+
_createdAt: string;
|
|
1026
|
+
_updatedAt: string;
|
|
1027
|
+
_version: number;
|
|
1028
|
+
}
|
|
1029
|
+
/** A filter's value: equality, or operators (`in` takes a list). Fields: indexed ones only. */
|
|
1030
|
+
export type FilterValue = string | number | boolean | null | {
|
|
1031
|
+
eq?: FilterValue;
|
|
1032
|
+
ne?: FilterValue;
|
|
1033
|
+
in?: FilterValue[];
|
|
1034
|
+
gt?: FilterValue;
|
|
1035
|
+
gte?: FilterValue;
|
|
1036
|
+
lt?: FilterValue;
|
|
1037
|
+
lte?: FilterValue;
|
|
1038
|
+
exists?: boolean;
|
|
1039
|
+
prefix?: string;
|
|
1040
|
+
};
|
|
1041
|
+
export interface CollectionQuery {
|
|
1042
|
+
filter?: Record<string, FilterValue>;
|
|
1043
|
+
/** fields along an index, `-` for descending (e.g. '-_updatedAt'); default '-_updatedAt' */
|
|
1044
|
+
sort?: string;
|
|
1045
|
+
/** 1–200, default 50 */
|
|
1046
|
+
limit?: number;
|
|
1047
|
+
cursor?: string;
|
|
1048
|
+
}
|
|
1049
|
+
export interface CollectionPage<T> {
|
|
1050
|
+
data: T[];
|
|
1051
|
+
next: string | null;
|
|
1052
|
+
}
|
|
1053
|
+
export type BatchOperation<T> = {
|
|
1054
|
+
op: "create";
|
|
1055
|
+
data: Partial<T>;
|
|
1056
|
+
} | {
|
|
1057
|
+
op: "update";
|
|
1058
|
+
id: string;
|
|
1059
|
+
data: Partial<T>;
|
|
1060
|
+
version?: number;
|
|
1061
|
+
} | {
|
|
1062
|
+
op: "delete";
|
|
1063
|
+
id: string;
|
|
1064
|
+
};
|
|
1065
|
+
/** One of a module's collections. `T`: its declared fields (the platform's are added). */
|
|
1066
|
+
export declare function collection<T extends object>(module: string, name: string): {
|
|
1067
|
+
/** One page. */
|
|
1068
|
+
find(q?: CollectionQuery): Promise<CollectionPage<T & HubbyDocument>>;
|
|
1069
|
+
/** Every document matching, page after page (200 at a time). */
|
|
1070
|
+
findAll(q?: Omit<CollectionQuery, "cursor" | "limit">): Promise<(T & HubbyDocument)[]>;
|
|
1071
|
+
get: (id: string) => Promise<T & HubbyDocument>;
|
|
1072
|
+
create(data: Partial<T>): Promise<T & HubbyDocument>;
|
|
1073
|
+
/** The given fields; `version`: refused (409) if the document changed since. */
|
|
1074
|
+
update(id: string, data: Partial<T>, version?: number): Promise<T & HubbyDocument>;
|
|
1075
|
+
/** The whole document (the fields not given are emptied). */
|
|
1076
|
+
replace(id: string, data: T): Promise<T & HubbyDocument>;
|
|
1077
|
+
remove: (id: string) => Promise<void>;
|
|
1078
|
+
/** Up to 100 operations, all or nothing. */
|
|
1079
|
+
batch(operations: BatchOperation<T>[]): Promise<Array<(T & HubbyDocument) | null>>;
|
|
1080
|
+
};
|
|
1081
|
+
export declare function useVpnStatus(): VpnInfo;
|
|
1082
|
+
export declare function formatDuration(seconds: number | null): string;
|
|
1083
|
+
export declare function formatDate(iso: string | null): string;
|
|
1084
|
+
|
|
1085
|
+
export {
|
|
1086
|
+
FormData$1 as FormData,
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
export {};
|