@schlessera/brain-ui-server 0.12.1 → 0.13.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/dist/app.d.ts.map +1 -1
- package/dist/app.js +5 -0
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware/origin.d.ts +17 -0
- package/dist/middleware/origin.d.ts.map +1 -0
- package/dist/middleware/origin.js +40 -0
- package/dist/middleware/origin.js.map +1 -0
- package/dist/routes/share.d.ts +89 -0
- package/dist/routes/share.d.ts.map +1 -0
- package/dist/routes/share.js +158 -0
- package/dist/routes/share.js.map +1 -0
- package/dist/share/staging.d.ts +62 -0
- package/dist/share/staging.d.ts.map +1 -0
- package/dist/share/staging.js +376 -0
- package/dist/share/staging.js.map +1 -0
- package/dist/ws/host.d.ts +23 -2
- package/dist/ws/host.d.ts.map +1 -1
- package/dist/ws/host.js +23 -2
- package/dist/ws/host.js.map +1 -1
- package/dist/ws/run-session.d.ts.map +1 -1
- package/dist/ws/run-session.js +38 -9
- package/dist/ws/run-session.js.map +1 -1
- package/dist/ws/turns.d.ts +14 -0
- package/dist/ws/turns.d.ts.map +1 -1
- package/dist/ws/turns.js +25 -0
- package/dist/ws/turns.js.map +1 -1
- package/package.json +4 -4
- package/src/app.ts +5 -0
- package/src/index.ts +4 -0
- package/src/middleware/origin.ts +39 -0
- package/src/routes/share.ts +178 -0
- package/src/share/staging.ts +425 -0
- package/src/ws/host.ts +24 -2
- package/src/ws/run-session.ts +49 -13
- package/src/ws/turns.ts +26 -0
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAYA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAoB1E,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB;;;EA6FvD"}
|
package/dist/app.js
CHANGED
|
@@ -8,6 +8,7 @@ import { brainRoutes } from "./routes/brain.js";
|
|
|
8
8
|
import { sessionRoutes } from "./routes/sessions.js";
|
|
9
9
|
import { voiceRoutes } from "./routes/voice.js";
|
|
10
10
|
import { filesRoutes } from "./routes/files.js";
|
|
11
|
+
import { shareRoutes, shareTargetFallbackRoutes } from "./routes/share.js";
|
|
11
12
|
import { createRenderRoutes } from "./routes/render.js";
|
|
12
13
|
import { providerRoutes } from "./routes/providers.js";
|
|
13
14
|
import { modelRoutes } from "./routes/models.js";
|
|
@@ -74,6 +75,9 @@ export function createApp(options = {}) {
|
|
|
74
75
|
// /api/status is intentionally NOT here — it leaks the git SHA, cron errors,
|
|
75
76
|
// and a session oracle, so it lives behind the guard below.
|
|
76
77
|
app.route("/api", healthRoutes);
|
|
78
|
+
// Not under /api, and not behind the guard: this is where a system share
|
|
79
|
+
// lands when no service worker was around to intercept it. See the route.
|
|
80
|
+
app.route("/", shareTargetFallbackRoutes);
|
|
77
81
|
app.route("/api", authRoutes(authMode, { passwordDisabled: passwordLoginDisabled }));
|
|
78
82
|
app.route("/api", passkeyPublicRoutes(authMode));
|
|
79
83
|
// Auth guard for every other /api/* route. The probe below is intentionally
|
|
@@ -89,6 +93,7 @@ export function createApp(options = {}) {
|
|
|
89
93
|
app.route("/api", sessionRoutes);
|
|
90
94
|
app.route("/api", voiceRoutes);
|
|
91
95
|
app.route("/api", filesRoutes);
|
|
96
|
+
app.route("/api", shareRoutes);
|
|
92
97
|
app.route("/api", createRenderRoutes(options.renderer));
|
|
93
98
|
app.route("/api", providerRoutes);
|
|
94
99
|
app.route("/api", modelRoutes);
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAoB,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAyBxE,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,gFAAgF;AAChF,iFAAiF;AACjF,2EAA2E;AAC3E,6CAA6C;AAC7C,SAAS,iBAAiB,CAAC,CAAU,EAAE,cAAwB;IAC7D,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,UAA4B,EAAE;IACtD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;IACnC,4EAA4E;IAC5E,2EAA2E;IAC3E,6CAA6C;IAC7C,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC3B,mBAAmB,EAAE,CAAC;IAEtB,IAAI,OAAO,CAAC,MAAM;QAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,eAAe,CAAC;QACd,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3E,CAAC,CAAC;IAEH,aAAa;IACb,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;IAEvB,6EAA6E;IAC7E,sEAAsE;IACtE,sEAAsE;IACtE,kCAAkC;IAClC,MAAM,cAAc,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;SACvD,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;SAC9B,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,GAAG,CAAC,GAAG,CACL,QAAQ,EACR,IAAI,CAAC;YACH,MAAM,EAAE,cAAc;YACtB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;YAC9C,YAAY,EAAE,CAAC,cAAc,CAAC;YAC9B,WAAW,EAAE,IAAI;SAClB,CAAC,CACH,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,qEAAqE;IACrE,6EAA6E;IAC7E,4DAA4D;IAC5D,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC;IACrF,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEjD,4EAA4E;IAC5E,wEAAwE;IACxE,oEAAoE;IACpE,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IACvC,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxD,6EAA6E;IAC7E,wEAAwE;IACxE,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,uBAAuB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAChC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACjC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxD,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAClC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC/B,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAE/B,6EAA6E;IAC7E,2EAA2E;IAC3E,sEAAsE;IACtE,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QAC/B,IAAI,CAAC,iBAAiB,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,EAAE,GAAG,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,CAAC,MAAM,cAAc,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;YACzC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yBAAyB,EAAE,EAAE,GAAG,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,yEAAyE;IACzE,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;QACtC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;QACjD,eAAe;QACf,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,5 +14,6 @@ export { getBackends, getBackendsInfo } from "./agent/backend.js";
|
|
|
14
14
|
export { resolveAuthMode, type AuthMode } from "./middleware/auth.js";
|
|
15
15
|
export { WsHost, type WsHostOptions, defaultWsHost, configureWsHost, handleClientMessage, resetForTests, resolveTurnTarget, createSessionCatalog, type SessionCatalog, } from "./ws/handler.js";
|
|
16
16
|
export * as brainClient from "./brain/client.js";
|
|
17
|
+
export { pruneShareStaging, shareStagingRoot } from "./share/staging.js";
|
|
17
18
|
export { buildKeyterms, writeCache } from "./voice/keyterm-builder.js";
|
|
18
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EACL,MAAM,EACN,KAAK,aAAa,EAClB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EACL,MAAM,EACN,KAAK,aAAa,EAClB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAIjD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGzE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,9 @@ export { WsHost, defaultWsHost, configureWsHost, handleClientMessage, resetForTe
|
|
|
20
20
|
// Brain repo access (spawned CLI wrapper) — useful for embedders that add
|
|
21
21
|
// their own routes on top.
|
|
22
22
|
export * as brainClient from "./brain/client.js";
|
|
23
|
+
// Share staging: a deployment can sweep expired staging dirs at boot; the
|
|
24
|
+
// intake route also sweeps opportunistically on every share.
|
|
25
|
+
export { pruneShareStaging, shareStagingRoot } from "./share/staging.js";
|
|
23
26
|
// Voice keyterm cache rebuild (used by deployments after `brain sync`).
|
|
24
27
|
export { buildKeyterms, writeCache } from "./voice/keyterm-builder.js";
|
|
25
28
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAA2C,MAAM,UAAU,CAAC;AAE9E,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7D,4EAA4E;AAC5E,+BAA+B;AAC/B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AAEtE,+CAA+C;AAC/C,OAAO,EACL,MAAM,EAEN,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,iBAAiB,CAAC;AAEzB,0EAA0E;AAC1E,2BAA2B;AAC3B,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,wEAAwE;AACxE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAA2C,MAAM,UAAU,CAAC;AAE9E,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7D,4EAA4E;AAC5E,+BAA+B;AAC/B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AAEtE,+CAA+C;AAC/C,OAAO,EACL,MAAM,EAEN,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,iBAAiB,CAAC;AAEzB,0EAA0E;AAC1E,2BAA2B;AAC3B,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,0EAA0E;AAC1E,6DAA6D;AAC7D,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEzE,wEAAwE;AACxE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
/**
|
|
3
|
+
* Same-origin enforcement for requests a browser sends without a preflight.
|
|
4
|
+
*
|
|
5
|
+
* Every other state-changing route here reads `application/json`, which is not
|
|
6
|
+
* a CORS-simple content type and so forces a preflight the attacker's origin
|
|
7
|
+
* fails — CSRF-safe by accident. A multipart upload has no such protection: it
|
|
8
|
+
* is a simple request, and under `AUTH_MODE=tailscale` the credential is the
|
|
9
|
+
* source IP rather than a cookie, so no SameSite flag stands in the way either.
|
|
10
|
+
* Any page opened on the tailnet could otherwise POST into the brain.
|
|
11
|
+
*
|
|
12
|
+
* The check is free because the routes that use it are only ever called by the
|
|
13
|
+
* app itself, same-origin. Same shape as the WebSocket upgrade's origin guard.
|
|
14
|
+
*/
|
|
15
|
+
export declare function allowedOriginList(): string[];
|
|
16
|
+
export declare function isSameOriginRequest(c: Context, allowed?: string[]): boolean;
|
|
17
|
+
//# sourceMappingURL=origin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../../src/middleware/origin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAK5C;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,WAAsB,GAAG,OAAO,CAgBtF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Same-origin enforcement for requests a browser sends without a preflight.
|
|
3
|
+
*
|
|
4
|
+
* Every other state-changing route here reads `application/json`, which is not
|
|
5
|
+
* a CORS-simple content type and so forces a preflight the attacker's origin
|
|
6
|
+
* fails — CSRF-safe by accident. A multipart upload has no such protection: it
|
|
7
|
+
* is a simple request, and under `AUTH_MODE=tailscale` the credential is the
|
|
8
|
+
* source IP rather than a cookie, so no SameSite flag stands in the way either.
|
|
9
|
+
* Any page opened on the tailnet could otherwise POST into the brain.
|
|
10
|
+
*
|
|
11
|
+
* The check is free because the routes that use it are only ever called by the
|
|
12
|
+
* app itself, same-origin. Same shape as the WebSocket upgrade's origin guard.
|
|
13
|
+
*/
|
|
14
|
+
export function allowedOriginList() {
|
|
15
|
+
return (process.env.ALLOWED_ORIGINS ?? "")
|
|
16
|
+
.split(",")
|
|
17
|
+
.map((origin) => origin.trim())
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
export function isSameOriginRequest(c, allowed = allowedOriginList()) {
|
|
21
|
+
// Chromium and Firefox send this, and it is not settable by script.
|
|
22
|
+
const site = c.req.header("sec-fetch-site");
|
|
23
|
+
if (site)
|
|
24
|
+
return site === "same-origin" || site === "none";
|
|
25
|
+
// Older browsers: fall back to Origin, and to the configured allowlist when
|
|
26
|
+
// the deployment is split across origins.
|
|
27
|
+
const origin = c.req.header("origin");
|
|
28
|
+
if (!origin)
|
|
29
|
+
return true; // A non-browser client; still gated by auth.
|
|
30
|
+
if (allowed.length > 0)
|
|
31
|
+
return allowed.includes(origin);
|
|
32
|
+
try {
|
|
33
|
+
const host = c.req.header("host");
|
|
34
|
+
return !!host && new URL(origin).host === host;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/middleware/origin.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;SACvC,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;SAC9B,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,CAAU,EAAE,OAAO,GAAG,iBAAiB,EAAE;IAC3E,oEAAoE;IACpE,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC5C,IAAI,IAAI;QAAE,OAAO,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,MAAM,CAAC;IAE3D,4EAA4E;IAC5E,0CAA0C;IAC1C,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,6CAA6C;IACvE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Answer a share that reached the SERVER — which means no service worker was
|
|
3
|
+
* there to intercept it.
|
|
4
|
+
*
|
|
5
|
+
* That happens: the worker is evicted, storage was cleared, or the app was
|
|
6
|
+
* installed before the worker activated. Android bakes the share target's
|
|
7
|
+
* intent filters into the WebAPK at install time, so the share sheet keeps
|
|
8
|
+
* offering the app regardless, and the POST lands here. Without this route the
|
|
9
|
+
* user gets a bare 404 inside the app window (the SPA fallback is GET-only).
|
|
10
|
+
*
|
|
11
|
+
* The body is deliberately not read: the payload cannot be recovered from here
|
|
12
|
+
* anyway. Landing in the app is still the best outcome, because loading the app
|
|
13
|
+
* re-registers the worker and the next share works.
|
|
14
|
+
*
|
|
15
|
+
* Registered BEFORE the auth guard, and it must stay there: a share navigation
|
|
16
|
+
* is cross-site, so the SameSite=Strict session cookie is absent by
|
|
17
|
+
* construction and an authenticated version of this route could never fire.
|
|
18
|
+
*/
|
|
19
|
+
export declare const shareTargetFallbackRoutes: import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
|
|
20
|
+
"/share-target": {
|
|
21
|
+
$post: {
|
|
22
|
+
input: {};
|
|
23
|
+
output: undefined;
|
|
24
|
+
outputFormat: "redirect";
|
|
25
|
+
status: 303;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}, "/", "/share-target">;
|
|
29
|
+
export declare const shareRoutes: import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
|
|
30
|
+
"/share": {
|
|
31
|
+
$post: {
|
|
32
|
+
input: {};
|
|
33
|
+
output: {
|
|
34
|
+
error: string;
|
|
35
|
+
};
|
|
36
|
+
outputFormat: "json";
|
|
37
|
+
status: 403;
|
|
38
|
+
} | {
|
|
39
|
+
input: {};
|
|
40
|
+
output: {
|
|
41
|
+
error: string;
|
|
42
|
+
};
|
|
43
|
+
outputFormat: "json";
|
|
44
|
+
status: 503;
|
|
45
|
+
} | {
|
|
46
|
+
input: {};
|
|
47
|
+
output: {
|
|
48
|
+
error: string;
|
|
49
|
+
limit: number;
|
|
50
|
+
};
|
|
51
|
+
outputFormat: "json";
|
|
52
|
+
status: 413;
|
|
53
|
+
} | {
|
|
54
|
+
input: {};
|
|
55
|
+
output: {
|
|
56
|
+
error: string;
|
|
57
|
+
};
|
|
58
|
+
outputFormat: "json";
|
|
59
|
+
status: 400;
|
|
60
|
+
} | {
|
|
61
|
+
input: {};
|
|
62
|
+
output: {
|
|
63
|
+
id: string;
|
|
64
|
+
dir: string;
|
|
65
|
+
receivedAt: number;
|
|
66
|
+
title?: string | undefined;
|
|
67
|
+
text?: string | undefined;
|
|
68
|
+
url?: string | undefined;
|
|
69
|
+
files: {
|
|
70
|
+
name: string;
|
|
71
|
+
path: string;
|
|
72
|
+
mediaType: string;
|
|
73
|
+
bytes: number;
|
|
74
|
+
}[];
|
|
75
|
+
skipped?: string[] | undefined;
|
|
76
|
+
};
|
|
77
|
+
outputFormat: "json";
|
|
78
|
+
status: 201;
|
|
79
|
+
} | {
|
|
80
|
+
input: {};
|
|
81
|
+
output: {
|
|
82
|
+
error: string;
|
|
83
|
+
};
|
|
84
|
+
outputFormat: "json";
|
|
85
|
+
status: 500;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}, "/", "/share">;
|
|
89
|
+
//# sourceMappingURL=share.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../src/routes/share.ts"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;wBAErC,CAAC;AA8BF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiEtB,CAAC"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { SHARE_MAX_CONCURRENT_INTAKE, SHARE_MAX_TOTAL_BYTES, } from "@schlessera/brain-ui-sdk/protocol";
|
|
3
|
+
import { isSameOriginRequest } from "../middleware/origin.js";
|
|
4
|
+
import { EmptyShareError, ShareTooLargeError, pruneShareStaging, stageShare, } from "../share/staging.js";
|
|
5
|
+
/**
|
|
6
|
+
* Slack over the total cap for multipart framing (boundaries, part headers, and
|
|
7
|
+
* the title/text/url fields).
|
|
8
|
+
*/
|
|
9
|
+
const BODY_SLACK_BYTES = 1_000_000;
|
|
10
|
+
const HARD_BODY_LIMIT = SHARE_MAX_TOTAL_BYTES + BODY_SLACK_BYTES;
|
|
11
|
+
/**
|
|
12
|
+
* Read the body, refusing to buffer more than `limit` bytes.
|
|
13
|
+
*
|
|
14
|
+
* `content-length` is only a hint — HTTP/2 and chunked transfer encoding omit
|
|
15
|
+
* it entirely, and a client is free to lie — so the cap has to be counted off
|
|
16
|
+
* the stream itself. Calling `formData()` first would hand an unbounded body to
|
|
17
|
+
* the multipart parser and let an authenticated caller exhaust memory well
|
|
18
|
+
* inside the advertised limits.
|
|
19
|
+
*
|
|
20
|
+
* Returns null when the body exceeds the limit.
|
|
21
|
+
*/
|
|
22
|
+
async function readCappedBody(request, limit) {
|
|
23
|
+
const stream = request.body;
|
|
24
|
+
if (!stream)
|
|
25
|
+
return new Blob([]);
|
|
26
|
+
const reader = stream.getReader();
|
|
27
|
+
const chunks = [];
|
|
28
|
+
let total = 0;
|
|
29
|
+
try {
|
|
30
|
+
for (;;) {
|
|
31
|
+
const { done, value } = await reader.read();
|
|
32
|
+
if (done)
|
|
33
|
+
break;
|
|
34
|
+
if (!value)
|
|
35
|
+
continue;
|
|
36
|
+
total += value.byteLength;
|
|
37
|
+
if (total > limit)
|
|
38
|
+
return null;
|
|
39
|
+
// Copy out of the reader's view: the underlying buffer may be reused.
|
|
40
|
+
chunks.push(value.slice().buffer);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
reader.releaseLock();
|
|
45
|
+
// Nothing more is wanted from an over-limit body; let the peer find out.
|
|
46
|
+
if (total > limit)
|
|
47
|
+
await stream.cancel().catch(() => { });
|
|
48
|
+
}
|
|
49
|
+
// Hand the chunks to the parser as they are. Concatenating them into one
|
|
50
|
+
// buffer first would copy the whole payload a second time, for nothing.
|
|
51
|
+
return new Blob(chunks);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Answer a share that reached the SERVER — which means no service worker was
|
|
55
|
+
* there to intercept it.
|
|
56
|
+
*
|
|
57
|
+
* That happens: the worker is evicted, storage was cleared, or the app was
|
|
58
|
+
* installed before the worker activated. Android bakes the share target's
|
|
59
|
+
* intent filters into the WebAPK at install time, so the share sheet keeps
|
|
60
|
+
* offering the app regardless, and the POST lands here. Without this route the
|
|
61
|
+
* user gets a bare 404 inside the app window (the SPA fallback is GET-only).
|
|
62
|
+
*
|
|
63
|
+
* The body is deliberately not read: the payload cannot be recovered from here
|
|
64
|
+
* anyway. Landing in the app is still the best outcome, because loading the app
|
|
65
|
+
* re-registers the worker and the next share works.
|
|
66
|
+
*
|
|
67
|
+
* Registered BEFORE the auth guard, and it must stay there: a share navigation
|
|
68
|
+
* is cross-site, so the SameSite=Strict session cookie is absent by
|
|
69
|
+
* construction and an authenticated version of this route could never fire.
|
|
70
|
+
*/
|
|
71
|
+
export const shareTargetFallbackRoutes = new Hono().post("/share-target", (c) => c.redirect("/?share_error=no_worker", 303));
|
|
72
|
+
/**
|
|
73
|
+
* In-flight intakes. Each one holds its whole payload in memory while the
|
|
74
|
+
* multipart parser runs, so without a bound the per-share cap multiplies by
|
|
75
|
+
* however many clients ask at once — on a box that also runs headless Chrome
|
|
76
|
+
* for the renderer.
|
|
77
|
+
*/
|
|
78
|
+
let inFlight = 0;
|
|
79
|
+
/** At most one sweep per interval: the intake path should not stat the inbox on every upload. */
|
|
80
|
+
let lastPrune = 0;
|
|
81
|
+
const PRUNE_INTERVAL_MS = 10 * 60 * 1000;
|
|
82
|
+
function maybePrune() {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
if (now - lastPrune < PRUNE_INTERVAL_MS)
|
|
85
|
+
return;
|
|
86
|
+
lastPrune = now;
|
|
87
|
+
// Deliberately not awaited: pruning is housekeeping, and the client is
|
|
88
|
+
// waiting on the staging result, not on it.
|
|
89
|
+
void pruneShareStaging().catch((err) => {
|
|
90
|
+
console.error("[share] prune failed:", err);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function firstString(form, name) {
|
|
94
|
+
const value = form.get(name);
|
|
95
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
96
|
+
}
|
|
97
|
+
export const shareRoutes = new Hono().post("/share", async (c) => {
|
|
98
|
+
// See middleware/origin.ts: a multipart POST is a CORS-simple request, so it
|
|
99
|
+
// reaches this route with no preflight, and in tailscale mode the credential
|
|
100
|
+
// is the source IP. This route is only ever called by the app itself.
|
|
101
|
+
if (!isSameOriginRequest(c)) {
|
|
102
|
+
return c.json({ error: "cross_origin_rejected" }, 403);
|
|
103
|
+
}
|
|
104
|
+
if (inFlight >= SHARE_MAX_CONCURRENT_INTAKE) {
|
|
105
|
+
return c.json({ error: "busy" }, 503);
|
|
106
|
+
}
|
|
107
|
+
const tooLarge = () => c.json({ error: "share_too_large", limit: SHARE_MAX_TOTAL_BYTES }, 413);
|
|
108
|
+
// Cheap early-out for a client that declares its size honestly; the streamed
|
|
109
|
+
// count below is what actually enforces the cap.
|
|
110
|
+
const declaredLength = Number(c.req.header("content-length") ?? "0");
|
|
111
|
+
if (Number.isFinite(declaredLength) && declaredLength > HARD_BODY_LIMIT) {
|
|
112
|
+
return tooLarge();
|
|
113
|
+
}
|
|
114
|
+
const raw = await readCappedBody(c.req.raw, HARD_BODY_LIMIT);
|
|
115
|
+
if (raw === null)
|
|
116
|
+
return tooLarge();
|
|
117
|
+
let form;
|
|
118
|
+
try {
|
|
119
|
+
const contentType = c.req.header("content-type");
|
|
120
|
+
form = await new Response(raw, {
|
|
121
|
+
headers: contentType ? { "content-type": contentType } : {},
|
|
122
|
+
}).formData();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// A truncated upload or a malformed multipart body. The share is gone
|
|
126
|
+
// either way; the client re-offers it from its own copy.
|
|
127
|
+
return c.json({ error: "invalid_form" }, 400);
|
|
128
|
+
}
|
|
129
|
+
// Empty parts are what an app sends when it has nothing to attach.
|
|
130
|
+
const files = form
|
|
131
|
+
.getAll("files")
|
|
132
|
+
.filter((value) => value instanceof File && value.size > 0);
|
|
133
|
+
inFlight += 1;
|
|
134
|
+
try {
|
|
135
|
+
const result = await stageShare({
|
|
136
|
+
title: firstString(form, "title"),
|
|
137
|
+
text: firstString(form, "text"),
|
|
138
|
+
url: firstString(form, "url"),
|
|
139
|
+
files,
|
|
140
|
+
});
|
|
141
|
+
maybePrune();
|
|
142
|
+
return c.json(result, 201);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
if (err instanceof EmptyShareError) {
|
|
146
|
+
return c.json({ error: "empty_share" }, 400);
|
|
147
|
+
}
|
|
148
|
+
if (err instanceof ShareTooLargeError) {
|
|
149
|
+
return c.json({ error: err.reason, limit: err.limit }, 413);
|
|
150
|
+
}
|
|
151
|
+
console.error("[share]", err);
|
|
152
|
+
return c.json({ error: "share_failed" }, 500);
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
inFlight -= 1;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
//# sourceMappingURL=share.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.js","sourceRoot":"","sources":["../../src/routes/share.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ShareIntakeResult } from "@schlessera/brain-ui-sdk/protocol";
|
|
2
|
+
/**
|
|
3
|
+
* Staging for an incoming system share.
|
|
4
|
+
*
|
|
5
|
+
* Everything a share carries is attacker-influenced: file names come from
|
|
6
|
+
* whichever app invoked the share sheet, and the text is whatever page the user
|
|
7
|
+
* was looking at. So the payload lands in a directory whose name the SERVER
|
|
8
|
+
* mints, under a dot-directory that is gitignored and hidden from the file
|
|
9
|
+
* browser, and nothing in it is ever trusted to be a path.
|
|
10
|
+
*
|
|
11
|
+
* The consumer is an LLM agent with file and shell tools, which sets the bar
|
|
12
|
+
* for "sanitized": a name is not safe merely because the filesystem accepts it.
|
|
13
|
+
* It also has to be safe unquoted in a shell, and free of characters that are
|
|
14
|
+
* invisible to the human reviewing what the agent is about to do.
|
|
15
|
+
*/
|
|
16
|
+
export declare class EmptyShareError extends Error {
|
|
17
|
+
constructor();
|
|
18
|
+
}
|
|
19
|
+
export type ShareLimitReason = "too_many_files" | "file_too_large" | "share_too_large" | "text_too_large" | "inbox_full";
|
|
20
|
+
export declare class ShareTooLargeError extends Error {
|
|
21
|
+
/** Machine-readable reason, returned to the client verbatim. */
|
|
22
|
+
reason: ShareLimitReason;
|
|
23
|
+
limit: number;
|
|
24
|
+
constructor(
|
|
25
|
+
/** Machine-readable reason, returned to the client verbatim. */
|
|
26
|
+
reason: ShareLimitReason, limit: number);
|
|
27
|
+
}
|
|
28
|
+
export interface ShareInput {
|
|
29
|
+
title?: string | undefined;
|
|
30
|
+
text?: string | undefined;
|
|
31
|
+
url?: string | undefined;
|
|
32
|
+
files: File[];
|
|
33
|
+
}
|
|
34
|
+
/** Absolute staging root. Read per call, so a test can move BRAIN_PATH. */
|
|
35
|
+
export declare function shareStagingRoot(): string;
|
|
36
|
+
/**
|
|
37
|
+
* Reduce whatever the sharing app called a file to a plain name that cannot
|
|
38
|
+
* escape its directory, hide itself, or do anything in a shell.
|
|
39
|
+
*
|
|
40
|
+
* Path separators are dropped rather than replaced: a name is a name, and the
|
|
41
|
+
* only directory a staged file may land in is the one this module just made.
|
|
42
|
+
*/
|
|
43
|
+
export declare function sanitizeFileName(raw: string, mediaType: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Write one share to its own staging directory and return the manifest.
|
|
46
|
+
*
|
|
47
|
+
* Staged into `.<id>.partial` and renamed into place only once `meta.json` is
|
|
48
|
+
* written. The rename is atomic within a filesystem, so the agent can never
|
|
49
|
+
* observe a share that is missing files or a manifest — not even if the process
|
|
50
|
+
* is killed mid-write, which `try/catch` cleanup cannot cover.
|
|
51
|
+
*/
|
|
52
|
+
export declare function stageShare(input: ShareInput): Promise<ShareIntakeResult>;
|
|
53
|
+
/**
|
|
54
|
+
* Remove staged shares older than the TTL, and partial directories orphaned by
|
|
55
|
+
* a crash mid-write.
|
|
56
|
+
*
|
|
57
|
+
* Called opportunistically on each intake (debounced) and exported so a
|
|
58
|
+
* deployment can sweep at boot too — scheduling belongs to the container
|
|
59
|
+
* crontab, and this sweep is cheap and bounded.
|
|
60
|
+
*/
|
|
61
|
+
export declare function pruneShareStaging(now?: number): Promise<number>;
|
|
62
|
+
//# sourceMappingURL=staging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../src/share/staging.ts"],"names":[],"mappings":"AAEA,OAAO,EASL,KAAK,iBAAiB,EAEvB,MAAM,mCAAmC,CAAC;AAG3C;;;;;;;;;;;;;GAaG;AAEH,qBAAa,eAAgB,SAAQ,KAAK;;CAKzC;AAED,MAAM,MAAM,gBAAgB,GACxB,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,YAAY,CAAC;AAEjB,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,gEAAgE;IACzD,MAAM,EAAE,gBAAgB;IACxB,KAAK,EAAE,MAAM;;IAFpB,gEAAgE;IACzD,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,MAAM;CAKvB;AAgED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AA+BD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAyBvE;AAyED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgH9E;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,SAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BzE"}
|