ptbk 0.119.0-70 → 0.119.0-72
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 +4 -1
- package/dist/app/connection.d.ts +77 -0
- package/dist/app/connection.js +170 -0
- package/dist/app/connection.js.map +1 -0
- package/dist/app/encoding.d.ts +48 -0
- package/dist/app/encoding.js +133 -0
- package/dist/app/encoding.js.map +1 -0
- package/dist/app/files.d.ts +45 -0
- package/dist/app/files.js +139 -0
- package/dist/app/files.js.map +1 -0
- package/dist/app/heartbeat.d.ts +95 -0
- package/dist/app/heartbeat.js +157 -0
- package/dist/app/heartbeat.js.map +1 -0
- package/dist/app/identity.d.ts +66 -0
- package/dist/app/identity.js +146 -0
- package/dist/app/identity.js.map +1 -0
- package/dist/app/net.d.ts +72 -0
- package/dist/app/net.js +195 -0
- package/dist/app/net.js.map +1 -0
- package/dist/app/registration.d.ts +134 -0
- package/dist/app/registration.js +255 -0
- package/dist/app/registration.js.map +1 -0
- package/dist/app/settings.d.ts +43 -0
- package/dist/app/settings.js +74 -0
- package/dist/app/settings.js.map +1 -0
- package/dist/app/signing.d.ts +49 -0
- package/dist/app/signing.js +63 -0
- package/dist/app/signing.js.map +1 -0
- package/dist/app/tunnel/frames.d.ts +140 -0
- package/dist/app/tunnel/frames.js +207 -0
- package/dist/app/tunnel/frames.js.map +1 -0
- package/dist/app/tunnel/handshake.d.ts +150 -0
- package/dist/app/tunnel/handshake.js +181 -0
- package/dist/app/tunnel/handshake.js.map +1 -0
- package/dist/app/tunnel/relay-link.d.ts +116 -0
- package/dist/app/tunnel/relay-link.js +450 -0
- package/dist/app/tunnel/relay-link.js.map +1 -0
- package/dist/app/tunnel/websocket-client.d.ts +82 -0
- package/dist/app/tunnel/websocket-client.js +285 -0
- package/dist/app/tunnel/websocket-client.js.map +1 -0
- package/dist/browser/browser.d.ts +98 -5
- package/dist/browser/browser.js +255 -19
- package/dist/browser/browser.js.map +1 -1
- package/dist/browser/tab-control.d.ts +386 -0
- package/dist/browser/tab-control.js +1013 -0
- package/dist/browser/tab-control.js.map +1 -0
- package/dist/browser/tab-reservations.d.ts +59 -0
- package/dist/browser/tab-reservations.js +101 -0
- package/dist/browser/tab-reservations.js.map +1 -0
- package/dist/chats/voice-call.d.ts +43 -11
- package/dist/chats/voice-call.js +96 -40
- package/dist/chats/voice-call.js.map +1 -1
- package/dist/cli/commands/connect.d.ts +15 -0
- package/dist/cli/commands/connect.js +161 -0
- package/dist/cli/commands/connect.js.map +1 -0
- package/dist/cli/commands/init.d.ts +4 -0
- package/dist/cli/commands/init.js +21 -7
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/registry.js +29 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli/service-view.js +4 -1
- package/dist/cli/service-view.js.map +1 -1
- package/dist/cli/sources.js +2 -0
- package/dist/cli/sources.js.map +1 -1
- package/dist/common/websocket.d.ts +60 -0
- package/dist/common/websocket.js +179 -0
- package/dist/common/websocket.js.map +1 -0
- package/dist/engine/agenda.d.ts +26 -9
- package/dist/engine/agenda.js +32 -13
- package/dist/engine/agenda.js.map +1 -1
- package/dist/engine/attention.js +16 -1
- package/dist/engine/attention.js.map +1 -1
- package/dist/engine/engine-update.d.ts +1 -1
- package/dist/engine/engine-update.js +2 -0
- package/dist/engine/engine-update.js.map +1 -1
- package/dist/engine/snapshot.d.ts +77 -10
- package/dist/engine/voice-calls.d.ts +41 -4
- package/dist/engine/voice-calls.js +40 -8
- package/dist/engine/voice-calls.js.map +1 -1
- package/dist/expert/_main.md +12 -7
- package/dist/expert/app/connection.md +74 -0
- package/dist/expert/app/integration.md +4 -4
- package/dist/expert/cli/acceptance.md +3 -1
- package/dist/expert/cli/api.md +1 -1
- package/dist/expert/cli/browser-tab-control.md +38 -0
- package/dist/expert/cli/browser.md +7 -5
- package/dist/expert/cli/chat-ui.md +1 -1
- package/dist/expert/cli/cli.md +2 -0
- package/dist/expert/cli/dashboard-details.md +5 -3
- package/dist/expert/cli/dashboard-layout.md +1 -1
- package/dist/expert/cli/dashboard.md +4 -2
- package/dist/expert/cli/desktop.md +21 -4
- package/dist/expert/cli/harness-voice.md +1 -1
- package/dist/expert/cli/installation.md +2 -2
- package/dist/expert/cli/interactive-feature-bridges.md +35 -0
- package/dist/expert/cli/notifications.md +2 -2
- package/dist/expert/cli/releasing.md +8 -2
- package/dist/expert/cli/start.md +1 -1
- package/dist/expert/cli/status-view.md +2 -2
- package/dist/expert/cli/task-control.md +1 -1
- package/dist/expert/cli/terminal.md +3 -1
- package/dist/expert/cli/traces.md +1 -1
- package/dist/expert/cli/user-interactions.md +1 -1
- package/dist/expert/cli/voice-chat-ui.md +11 -9
- package/dist/expert/cli/voice-chats.md +10 -10
- package/dist/expert/cli/wallet-testing.md +2 -0
- package/dist/expert/cli/wallet.md +16 -4
- package/dist/expert/cli/windows-installer.md +15 -41
- package/dist/expert/concepts/engine-and-project.md +2 -0
- package/dist/expert/concepts/minimal-interruption/_index.md +1 -0
- package/dist/expert/concepts/minimal-interruption/authentication-examples.md +1 -0
- package/dist/expert/concepts/product-presentation.md +1 -1
- package/dist/expert/concepts/user-control.md +15 -0
- package/dist/expert/dictionary.md +6 -2
- package/dist/project/workspace.d.ts +2 -0
- package/dist/project/workspace.js +1 -0
- package/dist/project/workspace.js.map +1 -1
- package/dist/service/auth.d.ts +5 -0
- package/dist/service/auth.js +2 -1
- package/dist/service/auth.js.map +1 -1
- package/dist/service/discovery.d.ts +3 -0
- package/dist/service/discovery.js +1 -0
- package/dist/service/discovery.js.map +1 -1
- package/dist/service/host.d.ts +21 -3
- package/dist/service/host.js +184 -12
- package/dist/service/host.js.map +1 -1
- package/dist/service/http.d.ts +2 -0
- package/dist/service/http.js +27 -4
- package/dist/service/http.js.map +1 -1
- package/dist/service/spa/app.css +143 -2
- package/dist/service/spa/app.js +109 -21
- package/dist/service/spa/fonts/fonts.css +35 -0
- package/dist/service/spa/fonts/inter-OFL.txt +93 -0
- package/dist/service/spa/fonts/inter-latin-ext.woff2 +0 -0
- package/dist/service/spa/fonts/inter-latin.woff2 +0 -0
- package/dist/service/spa/fonts/outfit-OFL.txt +93 -0
- package/dist/service/spa/fonts/outfit-latin-ext.woff2 +0 -0
- package/dist/service/spa/fonts/outfit-latin.woff2 +0 -0
- package/dist/service/spa/index.html +1 -0
- package/dist/ui/app-connection.d.ts +53 -0
- package/dist/ui/app-connection.js +216 -0
- package/dist/ui/app-connection.js.map +1 -0
- package/dist/ui/chat-client.d.ts +7 -2
- package/dist/ui/chat-client.js +41 -6
- package/dist/ui/chat-client.js.map +1 -1
- package/dist/ui/dashboard-home.js +5 -11
- package/dist/ui/dashboard-home.js.map +1 -1
- package/dist/ui/dashboard-pages.d.ts +8 -1
- package/dist/ui/dashboard-pages.js +67 -19
- package/dist/ui/dashboard-pages.js.map +1 -1
- package/dist/ui/dashboard.d.ts +5 -0
- package/dist/ui/dashboard.js +167 -4
- package/dist/ui/dashboard.js.map +1 -1
- package/dist/ui/feature-server.d.ts +169 -0
- package/dist/ui/feature-server.js +443 -0
- package/dist/ui/feature-server.js.map +1 -0
- package/dist/ui/global-dashboard.d.ts +2 -0
- package/dist/ui/global-dashboard.js +1 -1
- package/dist/ui/global-dashboard.js.map +1 -1
- package/dist/ui/port-surface.d.ts +92 -0
- package/dist/ui/port-surface.js +172 -0
- package/dist/ui/port-surface.js.map +1 -0
- package/dist/ui/status-view.js +10 -25
- package/dist/ui/status-view.js.map +1 -1
- package/dist/ui/tab-page.d.ts +13 -0
- package/dist/ui/tab-page.js +194 -0
- package/dist/ui/tab-page.js.map +1 -0
- package/dist/ui/tab-surface.d.ts +67 -0
- package/dist/ui/tab-surface.js +218 -0
- package/dist/ui/tab-surface.js.map +1 -0
- package/dist/ui/voice-page.d.ts +15 -0
- package/dist/{chats → ui}/voice-page.js +38 -39
- package/dist/ui/voice-page.js.map +1 -0
- package/dist/ui/voice-surface.d.ts +88 -0
- package/dist/ui/voice-surface.js +162 -0
- package/dist/ui/voice-surface.js.map +1 -0
- package/dist/wallet/wallet.d.ts +62 -0
- package/dist/wallet/wallet.js +120 -1
- package/dist/wallet/wallet.js.map +1 -1
- package/package.json +1 -1
- package/dist/chats/voice-bridge.d.ts +0 -49
- package/dist/chats/voice-bridge.js +0 -282
- package/dist/chats/voice-bridge.js.map +0 -1
- package/dist/chats/voice-page.d.ts +0 -14
- package/dist/chats/voice-page.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
<a href="#quickstart"><strong>Quickstart</strong></a> ·
|
|
12
12
|
<a href="#deploy-to-your-own-server"><strong>Deploy</strong></a> ·
|
|
13
13
|
<a href="https://www.ptbk.io/en"><strong>Website</strong></a> ·
|
|
14
|
+
<a href="design/README.md"><strong>Brand</strong></a> ·
|
|
14
15
|
<a href="specs/_main.md"><strong>Specifications</strong></a> ·
|
|
15
16
|
<a href="https://www.ptbk.io/en/partnership"><strong>For partners</strong></a>
|
|
16
17
|
</p>
|
|
@@ -63,10 +64,12 @@ Agents explicitly call you with `+@User` when they need a response. Viewing that
|
|
|
63
64
|
|
|
64
65
|
A background engine [updates itself](specs/cli/updates.md) when it has no other work: stable minor and patch releases of its major version, or a running prerelease to its stable release. It never installs a version your `ptbk.json` `updates` pin or range excludes, or one that needs a newer Node.js than your host runs, and you can switch it off on the dashboard's System page. Release signing and rollout delays are still being agreed with the platform, so your host access and backups still matter.
|
|
65
66
|
|
|
66
|
-
**Windows:** download the EXE from [GitHub Releases](https://github.com/webgptorg/promptbook/releases), or follow the [installation guide](https://www.ptbk.io/en/open-source). The installer offers the local desktop client (selected by default)
|
|
67
|
+
**Windows:** download the EXE from [GitHub Releases](https://github.com/webgptorg/promptbook/releases), or follow the [installation guide](https://www.ptbk.io/en/open-source). The installer offers the local desktop client (selected by default). Open the app to set up your project and choose AI tools. The desktop uses the same engine and initializer as the CLI, with an embedded CLI terminal; closing a window leaves your engines running. CLI-only installation remains available.
|
|
67
68
|
|
|
68
69
|
**macOS:** download the matching DMG from [GitHub Releases](https://github.com/webgptorg/promptbook/releases). Choose `arm64` for Apple Silicon or `x64` for Intel, then drag Promptbook into Applications. Node.js, npm and the matching engine are included. Project setup uses Git from your Mac, such as Git from the Xcode Command Line Tools. See the [desktop installation guide](apps/desktop/README.md#macos-installation) for signing and login details.
|
|
69
70
|
|
|
71
|
+
**Android preview:** download the ARM64 APK from [GitHub Releases](https://github.com/webgptorg/promptbook/releases). It includes the local engine, Node.js, Git and Gemini CLI. The app runs the engine in an Android foreground service and opens the project dashboard. See the [Android installation guide](apps/android/README.md) for setup and signing details.
|
|
72
|
+
|
|
70
73
|
## Deploy to your own server
|
|
71
74
|
|
|
72
75
|
**A clean Ubuntu 24.04 LTS x86-64 server** with systemd, outbound HTTPS and root or sudo access over SSH needs no preinstalled Node.js, npm, Git, Chrome or AI harness: use the [one-command installation](#clean-ubuntu-vps). It prepares the server through the [shared installer](deploy/ubuntu/install.sh) and opens `ptbk init` as a dedicated non-sudo `promptbook` user. Sign-in to Codex or Claude Code is finished on your phone or computer; see the [server guide](deploy/README.md#finish-setup-over-ssh).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AppConnectionView } from '../engine/snapshot.js';
|
|
2
|
+
import { type HeartbeatUpdateState } from './heartbeat.js';
|
|
3
|
+
import type { TestTlsOptions } from './net.js';
|
|
4
|
+
import { type TunnelStreamHandler } from './tunnel/relay-link.js';
|
|
5
|
+
/**
|
|
6
|
+
* The App view of a project whose engine is not running, from the stored identity and registration only (no network,
|
|
7
|
+
* nothing created).
|
|
8
|
+
*
|
|
9
|
+
* @private App connection
|
|
10
|
+
*/
|
|
11
|
+
export declare function storedAppView(directory: string, env: NodeJS.ProcessEnv): Promise<AppConnectionView>;
|
|
12
|
+
/**
|
|
13
|
+
* Live figures the heartbeat reports from the engine.
|
|
14
|
+
*
|
|
15
|
+
* @private App connection
|
|
16
|
+
*/
|
|
17
|
+
export type AppEngineFigures = {
|
|
18
|
+
/** Project agents (not deleted). */
|
|
19
|
+
readonly agents: number;
|
|
20
|
+
/** People who can sign in to this engine through the App (0 until App sign-in exists). */
|
|
21
|
+
readonly members: number;
|
|
22
|
+
readonly update: HeartbeatUpdateState;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Options of a project's App connection.
|
|
26
|
+
*
|
|
27
|
+
* @private App connection
|
|
28
|
+
*/
|
|
29
|
+
export type AppConnectionOptions = {
|
|
30
|
+
/** The App data directory (`.ptbk/app/`). */
|
|
31
|
+
readonly directory: string;
|
|
32
|
+
readonly env: NodeJS.ProcessEnv;
|
|
33
|
+
readonly onChange: () => void;
|
|
34
|
+
readonly figures: () => AppEngineFigures;
|
|
35
|
+
readonly log?: (text: string, level: 'info' | 'warning' | 'error') => void;
|
|
36
|
+
/** Browser streams; refused until the engine ends browser TLS itself. */
|
|
37
|
+
readonly onStream?: TunnelStreamHandler;
|
|
38
|
+
/** Heartbeat cadence (tests only shorten it). */
|
|
39
|
+
readonly heartbeatIntervalMs?: number;
|
|
40
|
+
readonly tls?: TestTlsOptions;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Keeps one project's App connection: the signed heartbeat and one tunnel per relay while the project is linked. It
|
|
44
|
+
* never blocks the engine: every failure becomes part of its view.
|
|
45
|
+
*
|
|
46
|
+
* @private App connection
|
|
47
|
+
*/
|
|
48
|
+
export declare class AppConnection {
|
|
49
|
+
private readonly options;
|
|
50
|
+
private identity?;
|
|
51
|
+
private registration?;
|
|
52
|
+
private problem?;
|
|
53
|
+
private links;
|
|
54
|
+
private heartbeat?;
|
|
55
|
+
private heartbeatTimer?;
|
|
56
|
+
private dashboardUrl;
|
|
57
|
+
private running;
|
|
58
|
+
/** Serializes start, reload and stop. */
|
|
59
|
+
private queue;
|
|
60
|
+
constructor(options: AppConnectionOptions);
|
|
61
|
+
/** Loads the link and connects when the project is linked. */
|
|
62
|
+
start(): Promise<void>;
|
|
63
|
+
/** Re-reads the link after `ptbk connect` or `ptbk disconnect`. */
|
|
64
|
+
reload(): Promise<AppConnectionView>;
|
|
65
|
+
/** Closes every tunnel and stops the heartbeat. */
|
|
66
|
+
stop(): Promise<void>;
|
|
67
|
+
/** Runs one lifecycle step after the previous one. */
|
|
68
|
+
private serialize;
|
|
69
|
+
/** Reads settings, identity and registration, then connects when linked. */
|
|
70
|
+
private load;
|
|
71
|
+
/** Starts the relay links and the heartbeat loop. */
|
|
72
|
+
private connect;
|
|
73
|
+
/** Stops every link and the heartbeat. */
|
|
74
|
+
private disconnect;
|
|
75
|
+
/** The App page's view of this connection (no secrets: no key, signature or link code). */
|
|
76
|
+
view(): AppConnectionView;
|
|
77
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { errorMessage } from '../common/errors.js';
|
|
2
|
+
import { HEARTBEAT_INTERVAL_MS, buildHeartbeat, hostFigures, nextHeartbeatSeq, sendHeartbeat } from './heartbeat.js';
|
|
3
|
+
import { loadEngineIdentity } from './identity.js';
|
|
4
|
+
import { engineVersion, readRegistration } from './registration.js';
|
|
5
|
+
import { DEFAULT_DASHBOARD_URL, appSettings } from './settings.js';
|
|
6
|
+
import { RelayLink } from './tunnel/relay-link.js';
|
|
7
|
+
/**
|
|
8
|
+
* The App view of a project whose engine is not running, from the stored identity and registration only (no network,
|
|
9
|
+
* nothing created).
|
|
10
|
+
*
|
|
11
|
+
* @private App connection
|
|
12
|
+
*/
|
|
13
|
+
export async function storedAppView(directory, env) {
|
|
14
|
+
let dashboardUrl = DEFAULT_DASHBOARD_URL;
|
|
15
|
+
try {
|
|
16
|
+
const settings = appSettings(env);
|
|
17
|
+
dashboardUrl = settings.dashboardUrl;
|
|
18
|
+
const identity = await loadEngineIdentity(directory, { create: false });
|
|
19
|
+
const registration = await readRegistration(directory, identity, settings);
|
|
20
|
+
return {
|
|
21
|
+
state: registration ? 'offline' : 'unlinked',
|
|
22
|
+
dashboardUrl: registration?.dashboardUrl ?? dashboardUrl,
|
|
23
|
+
...(identity ? { engineId: identity.engineId, fingerprint: identity.fingerprint.formatted } : {}),
|
|
24
|
+
...(registration ? { hostname: registration.hostname, url: `https://${registration.hostname}/`, linkedAt: registration.registeredAt } : {}),
|
|
25
|
+
relays: [],
|
|
26
|
+
browserAccess: 'unavailable',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return { state: 'problem', dashboardUrl, relays: [], browserAccess: 'unavailable', problem: errorMessage(error) };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Keeps one project's App connection: the signed heartbeat and one tunnel per relay while the project is linked. It
|
|
35
|
+
* never blocks the engine: every failure becomes part of its view.
|
|
36
|
+
*
|
|
37
|
+
* @private App connection
|
|
38
|
+
*/
|
|
39
|
+
export class AppConnection {
|
|
40
|
+
options;
|
|
41
|
+
identity;
|
|
42
|
+
registration;
|
|
43
|
+
problem;
|
|
44
|
+
links = [];
|
|
45
|
+
heartbeat;
|
|
46
|
+
heartbeatTimer;
|
|
47
|
+
dashboardUrl = DEFAULT_DASHBOARD_URL;
|
|
48
|
+
running = false;
|
|
49
|
+
/** Serializes start, reload and stop. */
|
|
50
|
+
queue = Promise.resolve();
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.options = options;
|
|
53
|
+
}
|
|
54
|
+
/** Loads the link and connects when the project is linked. */
|
|
55
|
+
async start() {
|
|
56
|
+
await this.serialize(async () => {
|
|
57
|
+
this.running = true;
|
|
58
|
+
await this.load();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** Re-reads the link after `ptbk connect` or `ptbk disconnect`. */
|
|
62
|
+
async reload() {
|
|
63
|
+
await this.serialize(async () => {
|
|
64
|
+
if (!this.running)
|
|
65
|
+
return;
|
|
66
|
+
this.disconnect();
|
|
67
|
+
await this.load();
|
|
68
|
+
});
|
|
69
|
+
return this.view();
|
|
70
|
+
}
|
|
71
|
+
/** Closes every tunnel and stops the heartbeat. */
|
|
72
|
+
async stop() {
|
|
73
|
+
await this.serialize(async () => {
|
|
74
|
+
this.running = false;
|
|
75
|
+
this.disconnect();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Runs one lifecycle step after the previous one. */
|
|
79
|
+
async serialize(step) {
|
|
80
|
+
const next = this.queue.then(step, step);
|
|
81
|
+
this.queue = next.catch(() => undefined);
|
|
82
|
+
await next;
|
|
83
|
+
}
|
|
84
|
+
/** Reads settings, identity and registration, then connects when linked. */
|
|
85
|
+
async load() {
|
|
86
|
+
this.problem = undefined;
|
|
87
|
+
this.identity = undefined;
|
|
88
|
+
this.registration = undefined;
|
|
89
|
+
this.heartbeat = undefined;
|
|
90
|
+
try {
|
|
91
|
+
const settings = appSettings(this.options.env);
|
|
92
|
+
this.dashboardUrl = settings.dashboardUrl;
|
|
93
|
+
this.identity = await loadEngineIdentity(this.options.directory, { create: false });
|
|
94
|
+
this.registration = await readRegistration(this.options.directory, this.identity, settings);
|
|
95
|
+
if (this.registration && this.identity)
|
|
96
|
+
this.connect(this.identity, this.registration, settings.relays);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
this.problem = errorMessage(error);
|
|
100
|
+
this.options.log?.(`The App connection is not available: ${this.problem}`, 'error');
|
|
101
|
+
}
|
|
102
|
+
this.options.onChange();
|
|
103
|
+
}
|
|
104
|
+
/** Starts the relay links and the heartbeat loop. */
|
|
105
|
+
connect(identity, registration, override) {
|
|
106
|
+
const version = engineVersion();
|
|
107
|
+
const relays = override ?? registration.relays.map((id) => ({ id, port: 443 }));
|
|
108
|
+
this.links = relays.map((relay) => new RelayLink({
|
|
109
|
+
relay,
|
|
110
|
+
identity,
|
|
111
|
+
version,
|
|
112
|
+
env: this.options.env,
|
|
113
|
+
onChange: this.options.onChange,
|
|
114
|
+
onStream: this.options.onStream,
|
|
115
|
+
log: this.options.log,
|
|
116
|
+
tls: this.options.tls,
|
|
117
|
+
}));
|
|
118
|
+
for (const link of this.links)
|
|
119
|
+
link.start();
|
|
120
|
+
const beat = async () => {
|
|
121
|
+
try {
|
|
122
|
+
const figures = this.options.figures();
|
|
123
|
+
const host = await hostFigures(this.options.directory);
|
|
124
|
+
const seq = await nextHeartbeatSeq(this.options.directory);
|
|
125
|
+
const body = buildHeartbeat({ engineId: identity.engineId, seq, ts: new Date().toISOString(), version, uptimeS: process.uptime(), ...host, ...figures });
|
|
126
|
+
const result = await sendHeartbeat({ identity, dashboardUrl: registration.dashboardUrl, body, env: this.options.env, tls: this.options.tls });
|
|
127
|
+
if (!this.running || this.registration !== registration)
|
|
128
|
+
return;
|
|
129
|
+
this.heartbeat = { ...result, nextAt: new Date(Date.now() + (this.options.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS)).toISOString() };
|
|
130
|
+
if (result.outcome !== 'accepted')
|
|
131
|
+
this.options.log?.(`The App heartbeat was ${result.outcome === 'refused' ? `refused (HTTP ${String(result.status)}${result.code ? `, ${result.code}` : ''})` : 'not delivered'}.`, 'warning');
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
this.heartbeat = { at: new Date().toISOString(), outcome: 'failed', body: '', message: errorMessage(error) };
|
|
135
|
+
}
|
|
136
|
+
this.options.onChange();
|
|
137
|
+
};
|
|
138
|
+
void beat();
|
|
139
|
+
this.heartbeatTimer = setInterval(() => void beat(), this.options.heartbeatIntervalMs ?? HEARTBEAT_INTERVAL_MS);
|
|
140
|
+
this.heartbeatTimer.unref();
|
|
141
|
+
}
|
|
142
|
+
/** Stops every link and the heartbeat. */
|
|
143
|
+
disconnect() {
|
|
144
|
+
clearInterval(this.heartbeatTimer);
|
|
145
|
+
this.heartbeatTimer = undefined;
|
|
146
|
+
for (const link of this.links)
|
|
147
|
+
link.stop();
|
|
148
|
+
this.links = [];
|
|
149
|
+
}
|
|
150
|
+
/** The App page's view of this connection (no secrets: no key, signature or link code). */
|
|
151
|
+
view() {
|
|
152
|
+
const identity = this.identity;
|
|
153
|
+
const registration = this.registration;
|
|
154
|
+
const relays = this.links.map((link) => link.view());
|
|
155
|
+
const connected = relays.filter((relay) => relay.state === 'connected').length;
|
|
156
|
+
const refusal = relays.find((relay) => relay.refusal)?.refusal;
|
|
157
|
+
const state = this.problem ? 'problem' : !registration ? 'unlinked' : connected ? 'linked' : refusal || this.heartbeat?.code === 'engine-suspended' ? 'refused' : 'connecting';
|
|
158
|
+
return {
|
|
159
|
+
state,
|
|
160
|
+
dashboardUrl: registration?.dashboardUrl ?? this.dashboardUrl,
|
|
161
|
+
...(identity ? { engineId: identity.engineId, fingerprint: identity.fingerprint.formatted } : {}),
|
|
162
|
+
...(registration ? { hostname: registration.hostname, url: `https://${registration.hostname}/`, linkedAt: registration.registeredAt } : {}),
|
|
163
|
+
relays,
|
|
164
|
+
...(this.heartbeat ? { heartbeat: this.heartbeat } : {}),
|
|
165
|
+
browserAccess: 'unavailable',
|
|
166
|
+
...(this.problem ? { problem: this.problem } : {}),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/app/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte encodings of the App platform protocol (`@ptbk/protocol` in `webgptorg/app`, ported natively because `ptbk`
|
|
3
|
+
* ships without runtime dependencies). Each byte string has exactly one accepted encoding: decoders refuse anything
|
|
4
|
+
* an encoder would not produce, so a signed value can never be read two ways.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Encodes bytes as RFC 4648 §5 base64url without padding.
|
|
8
|
+
*
|
|
9
|
+
* @private App platform protocol
|
|
10
|
+
*/
|
|
11
|
+
export declare function encodeBase64Url(bytes: Uint8Array): string;
|
|
12
|
+
/**
|
|
13
|
+
* Decodes canonical base64url: no padding, `+`, `/` or whitespace, no length ≡ 1 (mod 4) and zero unused trailing
|
|
14
|
+
* bits. Returns `undefined` for anything else.
|
|
15
|
+
*
|
|
16
|
+
* @private App platform protocol
|
|
17
|
+
*/
|
|
18
|
+
export declare function decodeBase64Url(text: string): Uint8Array | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Whether a text is the canonical base64url of exactly `length` bytes.
|
|
21
|
+
*
|
|
22
|
+
* @private App platform protocol
|
|
23
|
+
*/
|
|
24
|
+
export declare function isBase64UrlOfLength(text: unknown, length: number): text is string;
|
|
25
|
+
/**
|
|
26
|
+
* Encodes bytes as lowercase RFC 4648 base32 without padding.
|
|
27
|
+
*
|
|
28
|
+
* @private App platform protocol
|
|
29
|
+
*/
|
|
30
|
+
export declare function encodeBase32Lower(bytes: Uint8Array): string;
|
|
31
|
+
/**
|
|
32
|
+
* Encodes a string of `0`/`1` bits (a multiple of five long) in Crockford base32.
|
|
33
|
+
*
|
|
34
|
+
* @private App platform protocol
|
|
35
|
+
*/
|
|
36
|
+
export declare function encodeCrockfordBits(bits: string): string;
|
|
37
|
+
/**
|
|
38
|
+
* UTF-8 bytes of a text.
|
|
39
|
+
*
|
|
40
|
+
* @private App platform protocol
|
|
41
|
+
*/
|
|
42
|
+
export declare function utf8(text: string): Uint8Array;
|
|
43
|
+
/**
|
|
44
|
+
* Concatenates byte strings.
|
|
45
|
+
*
|
|
46
|
+
* @private App platform protocol
|
|
47
|
+
*/
|
|
48
|
+
export declare function concatBytes(...parts: readonly Uint8Array[]): Uint8Array;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte encodings of the App platform protocol (`@ptbk/protocol` in `webgptorg/app`, ported natively because `ptbk`
|
|
3
|
+
* ships without runtime dependencies). Each byte string has exactly one accepted encoding: decoders refuse anything
|
|
4
|
+
* an encoder would not produce, so a signed value can never be read two ways.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Alphabet of RFC 4648 §5 base64url.
|
|
8
|
+
*/
|
|
9
|
+
const BASE64URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
10
|
+
/**
|
|
11
|
+
* Character code → 6-bit value of base64url, or -1.
|
|
12
|
+
*/
|
|
13
|
+
const BASE64URL_VALUES = new Int8Array(128).fill(-1);
|
|
14
|
+
for (let value = 0; value < BASE64URL_ALPHABET.length; value++)
|
|
15
|
+
BASE64URL_VALUES[BASE64URL_ALPHABET.charCodeAt(value)] = value;
|
|
16
|
+
/**
|
|
17
|
+
* Lowercase RFC 4648 §6 base32 alphabet (engine IDs and link codes).
|
|
18
|
+
*/
|
|
19
|
+
const BASE32_LOWER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567';
|
|
20
|
+
/**
|
|
21
|
+
* Crockford base32 alphabet of the engine fingerprint code.
|
|
22
|
+
*/
|
|
23
|
+
const CROCKFORD_BASE32_ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
24
|
+
/**
|
|
25
|
+
* Encodes bytes as RFC 4648 §5 base64url without padding.
|
|
26
|
+
*
|
|
27
|
+
* @private App platform protocol
|
|
28
|
+
*/
|
|
29
|
+
export function encodeBase64Url(bytes) {
|
|
30
|
+
let text = '';
|
|
31
|
+
for (let index = 0; index < bytes.length; index += 3) {
|
|
32
|
+
const chunk = ((bytes[index] ?? 0) << 16) | ((bytes[index + 1] ?? 0) << 8) | (bytes[index + 2] ?? 0);
|
|
33
|
+
const chars = Math.min(bytes.length - index, 3) + 1;
|
|
34
|
+
for (let char = 0; char < chars; char++)
|
|
35
|
+
text += BASE64URL_ALPHABET.charAt((chunk >>> (18 - 6 * char)) & 0x3f);
|
|
36
|
+
}
|
|
37
|
+
return text;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decodes canonical base64url: no padding, `+`, `/` or whitespace, no length ≡ 1 (mod 4) and zero unused trailing
|
|
41
|
+
* bits. Returns `undefined` for anything else.
|
|
42
|
+
*
|
|
43
|
+
* @private App platform protocol
|
|
44
|
+
*/
|
|
45
|
+
export function decodeBase64Url(text) {
|
|
46
|
+
if (text.length % 4 === 1)
|
|
47
|
+
return undefined;
|
|
48
|
+
const bytes = new Uint8Array(Math.floor((text.length * 3) / 4));
|
|
49
|
+
let buffer = 0;
|
|
50
|
+
let bits = 0;
|
|
51
|
+
let offset = 0;
|
|
52
|
+
for (let index = 0; index < text.length; index++) {
|
|
53
|
+
const code = text.charCodeAt(index);
|
|
54
|
+
const value = code < 128 ? (BASE64URL_VALUES[code] ?? -1) : -1;
|
|
55
|
+
if (value < 0)
|
|
56
|
+
return undefined;
|
|
57
|
+
buffer = ((buffer << 6) | value) & 0xfff;
|
|
58
|
+
bits += 6;
|
|
59
|
+
if (bits >= 8) {
|
|
60
|
+
bits -= 8;
|
|
61
|
+
bytes[offset++] = (buffer >>> bits) & 0xff;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if ((buffer & ((1 << bits) - 1)) !== 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
return bytes;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether a text is the canonical base64url of exactly `length` bytes.
|
|
70
|
+
*
|
|
71
|
+
* @private App platform protocol
|
|
72
|
+
*/
|
|
73
|
+
export function isBase64UrlOfLength(text, length) {
|
|
74
|
+
if (typeof text !== 'string' || text.length !== Math.ceil((length * 4) / 3))
|
|
75
|
+
return false;
|
|
76
|
+
return decodeBase64Url(text)?.length === length;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Encodes bytes as lowercase RFC 4648 base32 without padding.
|
|
80
|
+
*
|
|
81
|
+
* @private App platform protocol
|
|
82
|
+
*/
|
|
83
|
+
export function encodeBase32Lower(bytes) {
|
|
84
|
+
let text = '';
|
|
85
|
+
let buffer = 0;
|
|
86
|
+
let bits = 0;
|
|
87
|
+
for (const byte of bytes) {
|
|
88
|
+
buffer = ((buffer << 8) | byte) & 0xfff;
|
|
89
|
+
bits += 8;
|
|
90
|
+
while (bits >= 5) {
|
|
91
|
+
bits -= 5;
|
|
92
|
+
text += BASE32_LOWER_ALPHABET.charAt((buffer >>> bits) & 0x1f);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (bits > 0)
|
|
96
|
+
text += BASE32_LOWER_ALPHABET.charAt((buffer << (5 - bits)) & 0x1f);
|
|
97
|
+
return text;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Encodes a string of `0`/`1` bits (a multiple of five long) in Crockford base32.
|
|
101
|
+
*
|
|
102
|
+
* @private App platform protocol
|
|
103
|
+
*/
|
|
104
|
+
export function encodeCrockfordBits(bits) {
|
|
105
|
+
let text = '';
|
|
106
|
+
for (let offset = 0; offset + 5 <= bits.length; offset += 5)
|
|
107
|
+
text += CROCKFORD_BASE32_ALPHABET.charAt(Number.parseInt(bits.slice(offset, offset + 5), 2));
|
|
108
|
+
return text;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* UTF-8 bytes of a text.
|
|
112
|
+
*
|
|
113
|
+
* @private App platform protocol
|
|
114
|
+
*/
|
|
115
|
+
export function utf8(text) {
|
|
116
|
+
return new Uint8Array(Buffer.from(text, 'utf8'));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Concatenates byte strings.
|
|
120
|
+
*
|
|
121
|
+
* @private App platform protocol
|
|
122
|
+
*/
|
|
123
|
+
export function concatBytes(...parts) {
|
|
124
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
125
|
+
const result = new Uint8Array(total);
|
|
126
|
+
let offset = 0;
|
|
127
|
+
for (const part of parts) {
|
|
128
|
+
result.set(part, offset);
|
|
129
|
+
offset += part.length;
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=encoding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encoding.js","sourceRoot":"","sources":["../../src/app/encoding.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuses an App data directory or file that is a symbolic link, of the wrong type, owned by another user or
|
|
3
|
+
* accessible to group or others. Names the path and mode, never the content.
|
|
4
|
+
*
|
|
5
|
+
* @private App connection
|
|
6
|
+
*/
|
|
7
|
+
export declare function assertPrivate(path: string, kind: 'directory' | 'file'): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Creates the App data directory (mode 0700) when missing and checks that it is private.
|
|
10
|
+
*
|
|
11
|
+
* @private App connection
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensurePrivateDirectory(path: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Flushes a directory entry (skipped where the platform cannot open directories).
|
|
16
|
+
*
|
|
17
|
+
* @private App connection
|
|
18
|
+
*/
|
|
19
|
+
export declare function syncDirectory(directory: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Writes a new private file (mode 0600) exclusively, flushing it and then its directory. Fails with `EEXIST` when the
|
|
22
|
+
* file exists.
|
|
23
|
+
*
|
|
24
|
+
* @private App connection
|
|
25
|
+
*/
|
|
26
|
+
export declare function createPrivateFile(directory: string, name: string, content: string | Uint8Array): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Replaces a private file durably: a fresh 0600 temporary file is written and flushed, renamed over the target and
|
|
29
|
+
* the directory is flushed, so a crash leaves either the old or the new content.
|
|
30
|
+
*
|
|
31
|
+
* @private App connection
|
|
32
|
+
*/
|
|
33
|
+
export declare function writePrivateFile(directory: string, name: string, content: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Reads a private file after checking it, or `undefined` when it does not exist.
|
|
36
|
+
*
|
|
37
|
+
* @private App connection
|
|
38
|
+
*/
|
|
39
|
+
export declare function readPrivateFile(directory: string, name: string): Promise<string | undefined>;
|
|
40
|
+
/**
|
|
41
|
+
* Removes a private file when it exists, flushing the directory.
|
|
42
|
+
*
|
|
43
|
+
* @private App connection
|
|
44
|
+
*/
|
|
45
|
+
export declare function removePrivateFile(directory: string, name: string): Promise<void>;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import { chmod, lstat, mkdir, open, readFile, rename, rm } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { PtbkError } from '../common/errors.js';
|
|
6
|
+
/**
|
|
7
|
+
* Whether POSIX ownership and modes protect files on this platform.
|
|
8
|
+
*/
|
|
9
|
+
const POSIX_MODES = process.platform !== 'win32';
|
|
10
|
+
/**
|
|
11
|
+
* Refuses an App data directory or file that is a symbolic link, of the wrong type, owned by another user or
|
|
12
|
+
* accessible to group or others. Names the path and mode, never the content.
|
|
13
|
+
*
|
|
14
|
+
* @private App connection
|
|
15
|
+
*/
|
|
16
|
+
export async function assertPrivate(path, kind) {
|
|
17
|
+
const stats = await lstat(path);
|
|
18
|
+
if (stats.isSymbolicLink())
|
|
19
|
+
throw new PtbkError(`The App connection refuses ${path}: it is a symbolic link.`, { kind: 'configuration' });
|
|
20
|
+
if (kind === 'directory' ? !stats.isDirectory() : !stats.isFile())
|
|
21
|
+
throw new PtbkError(`The App connection refuses ${path}: it is not a ${kind === 'directory' ? 'directory' : 'regular file'}.`, { kind: 'configuration' });
|
|
22
|
+
if (!POSIX_MODES)
|
|
23
|
+
return;
|
|
24
|
+
const uid = process.getuid?.();
|
|
25
|
+
if (uid !== undefined && stats.uid !== uid)
|
|
26
|
+
throw new PtbkError(`The App connection refuses ${path}: it is owned by user ${String(stats.uid)}, not by this engine's user ${String(uid)}.`, { kind: 'configuration' });
|
|
27
|
+
const mode = stats.mode & 0o777;
|
|
28
|
+
if (mode & 0o077)
|
|
29
|
+
throw new PtbkError(`The App connection refuses ${path}: its mode is ${mode.toString(8).padStart(4, '0')}; it must not be accessible to group or others (${kind === 'directory' ? '0700' : '0600'}).`, { kind: 'configuration' });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates the App data directory (mode 0700) when missing and checks that it is private.
|
|
33
|
+
*
|
|
34
|
+
* @private App connection
|
|
35
|
+
*/
|
|
36
|
+
export async function ensurePrivateDirectory(path) {
|
|
37
|
+
try {
|
|
38
|
+
await lstat(path);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code !== 'ENOENT')
|
|
42
|
+
throw error;
|
|
43
|
+
await mkdir(path, { recursive: true, mode: 0o700 });
|
|
44
|
+
if (POSIX_MODES)
|
|
45
|
+
await chmod(path, 0o700);
|
|
46
|
+
}
|
|
47
|
+
await assertPrivate(path, 'directory');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Flushes a directory entry (skipped where the platform cannot open directories).
|
|
51
|
+
*
|
|
52
|
+
* @private App connection
|
|
53
|
+
*/
|
|
54
|
+
export async function syncDirectory(directory) {
|
|
55
|
+
if (!POSIX_MODES)
|
|
56
|
+
return;
|
|
57
|
+
const handle = await open(directory, constants.O_RDONLY);
|
|
58
|
+
try {
|
|
59
|
+
await handle.sync();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await handle.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Writes a new private file (mode 0600) exclusively, flushing it and then its directory. Fails with `EEXIST` when the
|
|
67
|
+
* file exists.
|
|
68
|
+
*
|
|
69
|
+
* @private App connection
|
|
70
|
+
*/
|
|
71
|
+
export async function createPrivateFile(directory, name, content) {
|
|
72
|
+
const handle = await open(join(directory, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
|
|
73
|
+
try {
|
|
74
|
+
await handle.writeFile(content);
|
|
75
|
+
await handle.sync();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
await handle.close();
|
|
79
|
+
}
|
|
80
|
+
await syncDirectory(directory);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Replaces a private file durably: a fresh 0600 temporary file is written and flushed, renamed over the target and
|
|
84
|
+
* the directory is flushed, so a crash leaves either the old or the new content.
|
|
85
|
+
*
|
|
86
|
+
* @private App connection
|
|
87
|
+
*/
|
|
88
|
+
export async function writePrivateFile(directory, name, content) {
|
|
89
|
+
const temporary = `.${name}.${randomBytes(6).toString('hex')}.tmp`;
|
|
90
|
+
await createPrivateFile(directory, temporary, content);
|
|
91
|
+
try {
|
|
92
|
+
for (let attempt = 1;; attempt++) {
|
|
93
|
+
try {
|
|
94
|
+
await rename(join(directory, temporary), join(directory, name));
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
const code = error.code ?? '';
|
|
99
|
+
if (!['EPERM', 'EACCES', 'EBUSY'].includes(code) || attempt >= 8)
|
|
100
|
+
throw error;
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 25 * attempt));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
await rm(join(directory, temporary), { force: true }).catch(() => undefined);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
await syncDirectory(directory);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Reads a private file after checking it, or `undefined` when it does not exist.
|
|
113
|
+
*
|
|
114
|
+
* @private App connection
|
|
115
|
+
*/
|
|
116
|
+
export async function readPrivateFile(directory, name) {
|
|
117
|
+
const path = join(directory, name);
|
|
118
|
+
try {
|
|
119
|
+
await lstat(path);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error.code === 'ENOENT')
|
|
123
|
+
return undefined;
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
await assertPrivate(directory, 'directory');
|
|
127
|
+
await assertPrivate(path, 'file');
|
|
128
|
+
return await readFile(path, 'utf8');
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Removes a private file when it exists, flushing the directory.
|
|
132
|
+
*
|
|
133
|
+
* @private App connection
|
|
134
|
+
*/
|
|
135
|
+
export async function removePrivateFile(directory, name) {
|
|
136
|
+
await rm(join(directory, name), { force: true });
|
|
137
|
+
await syncDirectory(directory).catch(() => undefined);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=files.js.map
|