@soulcraft/sdk 1.1.0 → 1.3.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/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/auth/service-token.d.ts +62 -0
- package/dist/modules/auth/service-token.d.ts.map +1 -0
- package/dist/modules/auth/service-token.js +99 -0
- package/dist/modules/auth/service-token.js.map +1 -0
- package/dist/modules/billing/index.d.ts +17 -5
- package/dist/modules/billing/index.d.ts.map +1 -1
- package/dist/modules/billing/index.js +24 -9
- package/dist/modules/billing/index.js.map +1 -1
- package/dist/modules/billing/portal-provider.d.ts +70 -0
- package/dist/modules/billing/portal-provider.d.ts.map +1 -0
- package/dist/modules/billing/portal-provider.js +204 -0
- package/dist/modules/billing/portal-provider.js.map +1 -0
- package/dist/modules/billing/types.d.ts +4 -3
- package/dist/modules/billing/types.d.ts.map +1 -1
- package/dist/modules/billing/types.js +4 -3
- package/dist/modules/billing/types.js.map +1 -1
- package/dist/modules/hall/browser.d.ts +88 -0
- package/dist/modules/hall/browser.d.ts.map +1 -0
- package/dist/modules/hall/browser.js +265 -0
- package/dist/modules/hall/browser.js.map +1 -0
- package/dist/modules/hall/protocol.d.ts +39 -0
- package/dist/modules/hall/protocol.d.ts.map +1 -0
- package/dist/modules/hall/protocol.js +52 -0
- package/dist/modules/hall/protocol.js.map +1 -0
- package/dist/modules/hall/server.d.ts +172 -0
- package/dist/modules/hall/server.d.ts.map +1 -0
- package/dist/modules/hall/server.js +457 -0
- package/dist/modules/hall/server.js.map +1 -0
- package/dist/modules/hall/types.d.ts +502 -31
- package/dist/modules/hall/types.d.ts.map +1 -1
- package/dist/modules/hall/types.js +13 -8
- package/dist/modules/hall/types.js.map +1 -1
- package/dist/modules/license/index.js +2 -2
- package/dist/modules/license/index.js.map +1 -1
- package/dist/server/from-license.d.ts +213 -0
- package/dist/server/from-license.d.ts.map +1 -0
- package/dist/server/from-license.js +305 -0
- package/dist/server/from-license.js.map +1 -0
- package/dist/server/hall-handlers.d.ts +90 -151
- package/dist/server/hall-handlers.d.ts.map +1 -1
- package/dist/server/hall-handlers.js +84 -204
- package/dist/server/hall-handlers.js.map +1 -1
- package/dist/server/index.d.ts +6 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6 -2
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +22 -18
- package/dist/types.d.ts.map +1 -1
- package/docs/USAGE.md +224 -1
- package/package.json +1 -6
package/dist/client/index.d.ts
CHANGED
|
@@ -55,6 +55,9 @@ export { WsTransport } from '../transports/ws.js';
|
|
|
55
55
|
export { SseTransport } from '../transports/sse.js';
|
|
56
56
|
export type { SDKTransport } from '../transports/transport.js';
|
|
57
57
|
export { createBrainyProxy } from '../modules/brainy/proxy.js';
|
|
58
|
+
export { joinHallRoom } from '../modules/hall/browser.js';
|
|
59
|
+
export type { JoinHallRoomOptions } from '../modules/hall/browser.js';
|
|
60
|
+
export type { HallRoomHandle, HallRoomHandleEvents, TranscriptEvent, ConceptMentionEvent, RelationProposedEvent, SpeakerChangedEvent, PeerJoinedEvent, PeerLeftEvent, RoomOptions, RecordingManifest, ConceptInput, } from '../modules/hall/types.js';
|
|
58
61
|
export type { ClientSDKOptions } from '../types.js';
|
|
59
62
|
export type { SoulcraftBrainy } from '../modules/brainy/types.js';
|
|
60
63
|
export type { BrainyChangeEvent } from '../modules/brainy/events.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAG9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAG9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AACpE,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,sBAAsB,GACvB,MAAM,6BAA6B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAG9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAG9D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzD,YAAY,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AACrE,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,YAAY,GACb,MAAM,0BAA0B,CAAA;AAGjC,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AACnD,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AACpE,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,sBAAsB,GACvB,MAAM,6BAA6B,CAAA"}
|
package/dist/client/index.js
CHANGED
|
@@ -56,5 +56,7 @@ export { WsTransport } from '../transports/ws.js';
|
|
|
56
56
|
export { SseTransport } from '../transports/sse.js';
|
|
57
57
|
// ── Proxy factory ─────────────────────────────────────────────────────────────
|
|
58
58
|
export { createBrainyProxy } from '../modules/brainy/proxy.js';
|
|
59
|
+
// ── Hall room client (browser WebRTC) ─────────────────────────────────────────
|
|
60
|
+
export { joinHallRoom } from '../modules/hall/browser.js';
|
|
59
61
|
export { SDKError, SDKDisconnectedError, SDKTimeoutError, SDKAuthError, SDKForbiddenError, SDKRpcError, SDKMethodNotFoundError, } from '../modules/brainy/errors.js';
|
|
60
62
|
//# sourceMappingURL=index.js.map
|
package/dist/client/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,iFAAiF;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAGnD,iFAAiF;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,iFAAiF;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAGnD,iFAAiF;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAE9D,iFAAiF;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AAoBzD,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,sBAAsB,GACvB,MAAM,6BAA6B,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { createCapabilityToken, verifyCapabilityToken, } from './modules/brainy/
|
|
|
18
18
|
export { SDKError, SDKDisconnectedError, SDKTimeoutError, SDKAuthError, SDKForbiddenError, SDKRpcError, SDKMethodNotFoundError, } from './modules/brainy/errors.js';
|
|
19
19
|
export type { VfsModule } from './modules/vfs/types.js';
|
|
20
20
|
export type { VersionsModule } from './modules/versions/types.js';
|
|
21
|
-
export type { HallModule,
|
|
21
|
+
export type { HallModule, HallRoom, HallRoomHandle, HallRoomEvents, HallRoomHandleEvents, HallConnectionOptions, RoomOptions, ConceptInput, RecordingManifest, TranscriptEvent, ConceptMentionEvent, RelationProposedEvent, SpeakerChangedEvent, PeerJoinedEvent, PeerLeftEvent, } from './modules/hall/types.js';
|
|
22
22
|
export type { AuthModule, PlatformRole, SoulcraftAuthProvider, SoulcraftOrganization, SoulcraftUserFields, SoulcraftSessionUser, SoulcraftSession, OIDCClientConfig, AuthMode, } from './modules/auth/types.js';
|
|
23
23
|
export { SOULCRAFT_USER_FIELDS, SOULCRAFT_SESSION_CONFIG, computeEmailHash, getAuthMode, getOIDCClientConfig, } from './modules/auth/config.js';
|
|
24
24
|
export type { AiModule, AiCompleteOptions, AiCompleteResult, AiStreamOptions, AiStreamEvent, AiToolCall, AiMessage, AiContentBlock, AiTool, AiModel, } from './modules/ai/types.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAG9F,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,YAAY,EACV,MAAM,EACN,QAAQ,EACR,MAAM,EACN,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,2BAA2B,CAAA;AAClC,YAAY,EACV,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,sBAAsB,GACvB,MAAM,4BAA4B,CAAA;AAGnC,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAGvD,YAAY,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAGjE,YAAY,EACV,UAAU,EACV,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA8BH,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,sBAAsB,GACvB,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA8BH,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,QAAQ,EACR,oBAAoB,EACpB,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,sBAAsB,GACvB,MAAM,4BAA4B,CAAA;AAuCnC,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,gBAAgB,EAChB,WAAW,EACX,mBAAmB,GACpB,MAAM,0BAA0B,CAAA;AAejC,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAoFjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module modules/auth/service-token
|
|
3
|
+
* @description Timing-safe bearer token verification for service-to-service requests.
|
|
4
|
+
*
|
|
5
|
+
* Some Soulcraft services authenticate inter-service calls with a plain shared secret
|
|
6
|
+
* rather than HMAC-signed capability tokens. Examples include:
|
|
7
|
+
* - `WORKSHOP_DEPLOY_SECRET` — CI/CD pipelines triggering workshop deploys
|
|
8
|
+
* - `ACADEMY_PUBLISH_SECRET` — Academy publishing webhooks
|
|
9
|
+
*
|
|
10
|
+
* This module provides `verifyServiceToken()` — a single helper that extracts the
|
|
11
|
+
* `Authorization: Bearer <token>` header and compares it against the expected secret
|
|
12
|
+
* using a constant-time comparison to prevent timing-attack leaks.
|
|
13
|
+
*
|
|
14
|
+
* For cross-product server-to-server calls that need user identity, use capability
|
|
15
|
+
* tokens from `@soulcraft/sdk/server` (`createCapabilityToken` / `verifyCapabilityToken`)
|
|
16
|
+
* instead.
|
|
17
|
+
*
|
|
18
|
+
* @example Hono route guarded by a service token
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { verifyServiceToken } from '@soulcraft/sdk/server'
|
|
21
|
+
*
|
|
22
|
+
* app.post('/api/deploy', async (c) => {
|
|
23
|
+
* if (!verifyServiceToken(c.req.raw, process.env.WORKSHOP_DEPLOY_SECRET!)) {
|
|
24
|
+
* return c.json({ error: 'Unauthorized' }, 401)
|
|
25
|
+
* }
|
|
26
|
+
* await triggerDeploy()
|
|
27
|
+
* return c.json({ ok: true })
|
|
28
|
+
* })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Extract the bearer token from an HTTP `Authorization` header.
|
|
33
|
+
*
|
|
34
|
+
* Returns `null` if the header is absent or does not follow the
|
|
35
|
+
* `Bearer <token>` scheme.
|
|
36
|
+
*
|
|
37
|
+
* @param request - A Web API `Request` object (Hono / SvelteKit / Bun / Node18+).
|
|
38
|
+
* @returns The raw token string, or `null` if not present.
|
|
39
|
+
*/
|
|
40
|
+
export declare function extractBearerToken(request: Request): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Verify that the `Authorization: Bearer` token in `request` matches `expected`.
|
|
43
|
+
*
|
|
44
|
+
* Uses a timing-safe comparison (SHA-256 digest of both sides before comparing)
|
|
45
|
+
* to prevent timing attacks that could leak the secret.
|
|
46
|
+
*
|
|
47
|
+
* Returns `false` immediately if `expected` is empty, preventing accidental
|
|
48
|
+
* wide-open access when the env var is not configured.
|
|
49
|
+
*
|
|
50
|
+
* @param request - A Web API `Request` object.
|
|
51
|
+
* @param expected - The expected secret (e.g. `process.env.WORKSHOP_DEPLOY_SECRET`).
|
|
52
|
+
* @returns `true` if the token is present and matches `expected`; `false` otherwise.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* if (!verifyServiceToken(c.req.raw, process.env.WORKSHOP_DEPLOY_SECRET!)) {
|
|
57
|
+
* return c.json({ error: 'Unauthorized' }, 401)
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function verifyServiceToken(request: Request, expected: string): boolean;
|
|
62
|
+
//# sourceMappingURL=service-token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-token.d.ts","sourceRoot":"","sources":["../../../src/modules/auth/service-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAsBH;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAKlE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAc9E"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module modules/auth/service-token
|
|
3
|
+
* @description Timing-safe bearer token verification for service-to-service requests.
|
|
4
|
+
*
|
|
5
|
+
* Some Soulcraft services authenticate inter-service calls with a plain shared secret
|
|
6
|
+
* rather than HMAC-signed capability tokens. Examples include:
|
|
7
|
+
* - `WORKSHOP_DEPLOY_SECRET` — CI/CD pipelines triggering workshop deploys
|
|
8
|
+
* - `ACADEMY_PUBLISH_SECRET` — Academy publishing webhooks
|
|
9
|
+
*
|
|
10
|
+
* This module provides `verifyServiceToken()` — a single helper that extracts the
|
|
11
|
+
* `Authorization: Bearer <token>` header and compares it against the expected secret
|
|
12
|
+
* using a constant-time comparison to prevent timing-attack leaks.
|
|
13
|
+
*
|
|
14
|
+
* For cross-product server-to-server calls that need user identity, use capability
|
|
15
|
+
* tokens from `@soulcraft/sdk/server` (`createCapabilityToken` / `verifyCapabilityToken`)
|
|
16
|
+
* instead.
|
|
17
|
+
*
|
|
18
|
+
* @example Hono route guarded by a service token
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { verifyServiceToken } from '@soulcraft/sdk/server'
|
|
21
|
+
*
|
|
22
|
+
* app.post('/api/deploy', async (c) => {
|
|
23
|
+
* if (!verifyServiceToken(c.req.raw, process.env.WORKSHOP_DEPLOY_SECRET!)) {
|
|
24
|
+
* return c.json({ error: 'Unauthorized' }, 401)
|
|
25
|
+
* }
|
|
26
|
+
* await triggerDeploy()
|
|
27
|
+
* return c.json({ ok: true })
|
|
28
|
+
* })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
import { timingSafeEqual, createHash } from 'node:crypto';
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
// Utilities
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Hash a string to a fixed 32-byte SHA-256 digest for `timingSafeEqual`.
|
|
37
|
+
*
|
|
38
|
+
* Both sides must be the same byte length for `timingSafeEqual` to work.
|
|
39
|
+
* Hashing also ensures comparison time is independent of token length.
|
|
40
|
+
*/
|
|
41
|
+
function _toComparableBuffer(value) {
|
|
42
|
+
return createHash('sha256').update(value, 'utf8').digest();
|
|
43
|
+
}
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
// Public API
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Extract the bearer token from an HTTP `Authorization` header.
|
|
49
|
+
*
|
|
50
|
+
* Returns `null` if the header is absent or does not follow the
|
|
51
|
+
* `Bearer <token>` scheme.
|
|
52
|
+
*
|
|
53
|
+
* @param request - A Web API `Request` object (Hono / SvelteKit / Bun / Node18+).
|
|
54
|
+
* @returns The raw token string, or `null` if not present.
|
|
55
|
+
*/
|
|
56
|
+
export function extractBearerToken(request) {
|
|
57
|
+
const header = request.headers.get('authorization');
|
|
58
|
+
if (!header?.startsWith('Bearer '))
|
|
59
|
+
return null;
|
|
60
|
+
const token = header.slice(7).trim();
|
|
61
|
+
return token.length > 0 ? token : null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Verify that the `Authorization: Bearer` token in `request` matches `expected`.
|
|
65
|
+
*
|
|
66
|
+
* Uses a timing-safe comparison (SHA-256 digest of both sides before comparing)
|
|
67
|
+
* to prevent timing attacks that could leak the secret.
|
|
68
|
+
*
|
|
69
|
+
* Returns `false` immediately if `expected` is empty, preventing accidental
|
|
70
|
+
* wide-open access when the env var is not configured.
|
|
71
|
+
*
|
|
72
|
+
* @param request - A Web API `Request` object.
|
|
73
|
+
* @param expected - The expected secret (e.g. `process.env.WORKSHOP_DEPLOY_SECRET`).
|
|
74
|
+
* @returns `true` if the token is present and matches `expected`; `false` otherwise.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* if (!verifyServiceToken(c.req.raw, process.env.WORKSHOP_DEPLOY_SECRET!)) {
|
|
79
|
+
* return c.json({ error: 'Unauthorized' }, 401)
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function verifyServiceToken(request, expected) {
|
|
84
|
+
// Reject immediately if the expected secret is not configured.
|
|
85
|
+
if (!expected)
|
|
86
|
+
return false;
|
|
87
|
+
const actual = extractBearerToken(request);
|
|
88
|
+
if (!actual)
|
|
89
|
+
return false;
|
|
90
|
+
try {
|
|
91
|
+
const a = _toComparableBuffer(actual);
|
|
92
|
+
const b = _toComparableBuffer(expected);
|
|
93
|
+
return timingSafeEqual(a, b);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=service-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-token.js","sourceRoot":"","sources":["../../../src/modules/auth/service-token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEzD,gFAAgF;AAChF,YAAY;AACZ,gFAAgF;AAEhF;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAA;AAC5D,CAAC;AAED,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;IACnD,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IACpC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AACxC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,QAAgB;IACnE,+DAA+D;IAC/D,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAA;IAE3B,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC1C,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEzB,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAA;QACrC,MAAM,CAAC,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAA;QACvC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* @description Factory for sdk.billing.* — usage metering, subscription management,
|
|
4
4
|
* top-up credits, and Stripe integration.
|
|
5
5
|
*
|
|
6
|
-
* **Provider selection
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* **Provider selection (priority order):**
|
|
7
|
+
* 1. `options.provider` — explicit override (primarily for testing)
|
|
8
|
+
* 2. `PORTAL_CREDIT_SECRET` set → `PortalBillingProvider` (cross-product unified credits)
|
|
9
|
+
* 3. `FIREBASE_SERVICE_ACCOUNT_KEY` set → `FirestoreBillingProvider` (per-product production)
|
|
10
|
+
* 4. Neither → `LocalBillingProvider` (dev, stores state in `.soulcraft/billing/`)
|
|
9
11
|
*
|
|
10
12
|
* **Usage metering:** Fire-and-forget via a 5-second write-behind buffer. Recording
|
|
11
13
|
* usage never blocks the AI response path.
|
|
@@ -30,10 +32,20 @@ import type { BillingModule, BillingProvider } from './types.js';
|
|
|
30
32
|
export interface CreateBillingModuleOptions {
|
|
31
33
|
/**
|
|
32
34
|
* Override the billing provider directly.
|
|
33
|
-
* When not provided, the provider is selected from the environment
|
|
34
|
-
*
|
|
35
|
+
* When not provided, the provider is selected from the environment
|
|
36
|
+
* (Portal → Firestore → Local).
|
|
35
37
|
*/
|
|
36
38
|
provider?: BillingProvider;
|
|
39
|
+
/**
|
|
40
|
+
* The product name sent to the Portal credit API with every consume/batch call.
|
|
41
|
+
* Used for per-product usage attribution in Portal analytics.
|
|
42
|
+
*
|
|
43
|
+
* Only relevant when `PORTAL_CREDIT_SECRET` is set and no explicit `provider` is given.
|
|
44
|
+
* Defaults to `'unknown'` if not specified.
|
|
45
|
+
*
|
|
46
|
+
* @example `'workshop'` | `'venue'` | `'academy'`
|
|
47
|
+
*/
|
|
48
|
+
product?: string;
|
|
37
49
|
}
|
|
38
50
|
/**
|
|
39
51
|
* Create the `sdk.billing.*` module.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/billing/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/billing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EAOhB,MAAM,YAAY,CAAA;AAMnB;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;IAE1B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAgBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,0BAA+B,GAAG,aAAa,CAwI3F;AAGD,YAAY,EACV,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,YAAY,CAAA"}
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* @description Factory for sdk.billing.* — usage metering, subscription management,
|
|
4
4
|
* top-up credits, and Stripe integration.
|
|
5
5
|
*
|
|
6
|
-
* **Provider selection
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* **Provider selection (priority order):**
|
|
7
|
+
* 1. `options.provider` — explicit override (primarily for testing)
|
|
8
|
+
* 2. `PORTAL_CREDIT_SECRET` set → `PortalBillingProvider` (cross-product unified credits)
|
|
9
|
+
* 3. `FIREBASE_SERVICE_ACCOUNT_KEY` set → `FirestoreBillingProvider` (per-product production)
|
|
10
|
+
* 4. Neither → `LocalBillingProvider` (dev, stores state in `.soulcraft/billing/`)
|
|
9
11
|
*
|
|
10
12
|
* **Usage metering:** Fire-and-forget via a 5-second write-behind buffer. Recording
|
|
11
13
|
* usage never blocks the AI response path.
|
|
@@ -26,6 +28,7 @@
|
|
|
26
28
|
import Stripe from 'stripe';
|
|
27
29
|
import { LocalBillingProvider } from './local-provider.js';
|
|
28
30
|
import { FirestoreBillingProvider } from './firestore-provider.js';
|
|
31
|
+
import { PortalBillingProvider } from './portal-provider.js';
|
|
29
32
|
import { UsageBuffer } from './usage-buffer.js';
|
|
30
33
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
34
|
// Stripe helper
|
|
@@ -46,10 +49,17 @@ function _getStripe() {
|
|
|
46
49
|
* @returns A fully configured `BillingModule`.
|
|
47
50
|
*/
|
|
48
51
|
export function createBillingModule(options = {}) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
const _selectProvider = () => {
|
|
53
|
+
const portalSecret = process.env['PORTAL_CREDIT_SECRET'];
|
|
54
|
+
if (portalSecret) {
|
|
55
|
+
return new PortalBillingProvider('https://portal.soulcraft.com', portalSecret, options.product ?? 'unknown');
|
|
56
|
+
}
|
|
57
|
+
if (process.env['FIREBASE_SERVICE_ACCOUNT_KEY']) {
|
|
58
|
+
return new FirestoreBillingProvider();
|
|
59
|
+
}
|
|
60
|
+
return new LocalBillingProvider();
|
|
61
|
+
};
|
|
62
|
+
const provider = options.provider ?? _selectProvider();
|
|
53
63
|
const buffer = new UsageBuffer(provider);
|
|
54
64
|
return {
|
|
55
65
|
// ── Usage metering ──────────────────────────────────────────────────────
|
|
@@ -112,15 +122,20 @@ export function createBillingModule(options = {}) {
|
|
|
112
122
|
return provider.getTopUpBalance(userId);
|
|
113
123
|
},
|
|
114
124
|
async addTopUp(userId, amount) {
|
|
125
|
+
// Portal manages top-up credits directly — this method is a no-op
|
|
126
|
+
// when the Portal provider is active. Products should redirect users
|
|
127
|
+
// to portal.soulcraft.com to purchase top-up credits.
|
|
128
|
+
if (provider instanceof PortalBillingProvider) {
|
|
129
|
+
return provider.getTopUpBalance(userId);
|
|
130
|
+
}
|
|
115
131
|
// For the local provider, directly update the balance.
|
|
116
|
-
// For Firestore, we use a targeted increment to avoid race conditions.
|
|
117
132
|
if (provider instanceof LocalBillingProvider) {
|
|
118
133
|
const current = await provider.getTopUpBalance(userId);
|
|
119
134
|
const newBalance = current + amount;
|
|
120
135
|
await provider.setTopUpBalance(userId, newBalance);
|
|
121
136
|
return newBalance;
|
|
122
137
|
}
|
|
123
|
-
// Firestore: increment
|
|
138
|
+
// Firestore: increment via a targeted negative-topUpUsed write to avoid race conditions.
|
|
124
139
|
if (provider instanceof FirestoreBillingProvider) {
|
|
125
140
|
await provider.batchIncrementUsage(userId, {
|
|
126
141
|
messageCount: 0, inputTokens: 0, outputTokens: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/billing/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/billing/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module billing/portal-provider
|
|
3
|
+
* @description Portal-backed billing provider for unified cross-product credit tracking.
|
|
4
|
+
*
|
|
5
|
+
* Delegates all credit checks and usage recording to the Portal credit API at
|
|
6
|
+
* `https://portal.soulcraft.com/api/credits/*`. This ensures that a user's
|
|
7
|
+
* 1,000 monthly credits are shared across Workshop, Venue, and Academy — not siloed
|
|
8
|
+
* per product.
|
|
9
|
+
*
|
|
10
|
+
* Activated when `PORTAL_CREDIT_SECRET` is set in the environment. Takes precedence
|
|
11
|
+
* over the Firestore provider.
|
|
12
|
+
*
|
|
13
|
+
* **Endpoint summary:**
|
|
14
|
+
* ```
|
|
15
|
+
* POST /api/credits/check — pre-flight before an AI call
|
|
16
|
+
* POST /api/credits/consume — deduct usage after a single AI response
|
|
17
|
+
* POST /api/credits/batch — flush buffered usage at end of session
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* All requests carry `X-Portal-Credit-Secret: <PORTAL_CREDIT_SECRET>`.
|
|
21
|
+
*
|
|
22
|
+
* Subscription data, top-up balances, and usage history are not surfaced via
|
|
23
|
+
* this API — those are managed by Portal directly. Methods that require them
|
|
24
|
+
* (`getUsageStatus`, `getSubscription`, `addTopUp`) return safe defaults.
|
|
25
|
+
* Products that need rich billing UI should call Portal's management API directly.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // Automatically selected when PORTAL_CREDIT_SECRET is set:
|
|
30
|
+
* const billing = createBillingModule()
|
|
31
|
+
*
|
|
32
|
+
* // Explicit override (e.g. for testing):
|
|
33
|
+
* const billing = createBillingModule({
|
|
34
|
+
* provider: new PortalBillingProvider('https://portal.soulcraft.com', secret, 'workshop'),
|
|
35
|
+
* })
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
import type { BillingProvider, UsageData, SubscriptionData, LimitCheckResult, UsageStatus, BatchIncrementData } from './types.js';
|
|
39
|
+
/**
|
|
40
|
+
* Portal-backed billing provider.
|
|
41
|
+
*
|
|
42
|
+
* Calls the Portal credit API for all gate checks and usage recording.
|
|
43
|
+
* All requests are authenticated with the `X-Portal-Credit-Secret` header.
|
|
44
|
+
*
|
|
45
|
+
* The `userId` parameter on all methods must be the user's **email address** —
|
|
46
|
+
* the same identifier Portal uses to look up the license document.
|
|
47
|
+
*/
|
|
48
|
+
export declare class PortalBillingProvider implements BillingProvider {
|
|
49
|
+
private readonly baseUrl;
|
|
50
|
+
private readonly secret;
|
|
51
|
+
private readonly product;
|
|
52
|
+
/**
|
|
53
|
+
* @param baseUrl - Portal base URL (e.g. `"https://portal.soulcraft.com"`).
|
|
54
|
+
* @param secret - The value of `PORTAL_CREDIT_SECRET`.
|
|
55
|
+
* @param product - The product name sent with consume/batch calls (`'workshop'`, `'venue'`, `'academy'`).
|
|
56
|
+
*/
|
|
57
|
+
constructor(baseUrl: string, secret: string, product: string);
|
|
58
|
+
private _headers;
|
|
59
|
+
private _post;
|
|
60
|
+
checkLimit(userId: string, hasPersonalKey: boolean): Promise<LimitCheckResult>;
|
|
61
|
+
getUsage(userId: string): Promise<UsageData>;
|
|
62
|
+
getUsageStatus(userId: string, hasPersonalKey: boolean): Promise<UsageStatus>;
|
|
63
|
+
incrementUsage(userId: string, inputTokens: number, outputTokens: number, model: string, _useTopUp: boolean): Promise<void>;
|
|
64
|
+
batchIncrementUsage(userId: string, batch: BatchIncrementData): Promise<void>;
|
|
65
|
+
getSubscription(_userId: string): Promise<SubscriptionData | undefined>;
|
|
66
|
+
hasActiveSubscription(userId: string): Promise<boolean>;
|
|
67
|
+
getTopUpBalance(userId: string): Promise<number>;
|
|
68
|
+
canAccessPaidFeature(userId: string): Promise<boolean>;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=portal-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal-provider.d.ts","sourceRoot":"","sources":["../../../src/modules/billing/portal-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAmBnB;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,YAAW,eAAe;IAOzD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAR1B;;;;OAIG;gBAEgB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;IAKlC,OAAO,CAAC,QAAQ;YAOF,KAAK;IAeb,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA2B9E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAgB5C,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAgC7E,cAAc,CAClB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,IAAI,CAAC;IAeV,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAe7E,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAKvE,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKvD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKhD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAG7D"}
|