@soulcraft/sdk 1.0.0 → 1.2.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 +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- 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/firestore-provider.d.ts +60 -0
- package/dist/modules/billing/firestore-provider.d.ts.map +1 -0
- package/dist/modules/billing/firestore-provider.js +314 -0
- package/dist/modules/billing/firestore-provider.js.map +1 -0
- package/dist/modules/billing/index.d.ts +58 -0
- package/dist/modules/billing/index.d.ts.map +1 -0
- package/dist/modules/billing/index.js +164 -0
- package/dist/modules/billing/index.js.map +1 -0
- package/dist/modules/billing/local-provider.d.ts +38 -0
- package/dist/modules/billing/local-provider.d.ts.map +1 -0
- package/dist/modules/billing/local-provider.js +242 -0
- package/dist/modules/billing/local-provider.js.map +1 -0
- 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 +323 -3
- package/dist/modules/billing/types.d.ts.map +1 -1
- package/dist/modules/billing/types.js +22 -2
- package/dist/modules/billing/types.js.map +1 -1
- package/dist/modules/billing/usage-buffer.d.ts +72 -0
- package/dist/modules/billing/usage-buffer.d.ts.map +1 -0
- package/dist/modules/billing/usage-buffer.js +141 -0
- package/dist/modules/billing/usage-buffer.js.map +1 -0
- package/dist/modules/formats/types.d.ts +65 -3
- package/dist/modules/formats/types.d.ts.map +1 -1
- package/dist/modules/formats/types.js +40 -3
- package/dist/modules/formats/types.js.map +1 -1
- package/dist/modules/formats/wdoc.d.ts +263 -0
- package/dist/modules/formats/wdoc.d.ts.map +1 -0
- package/dist/modules/formats/wdoc.js +21 -0
- package/dist/modules/formats/wdoc.js.map +1 -0
- package/dist/modules/formats/wquiz.d.ts +122 -0
- package/dist/modules/formats/wquiz.d.ts.map +1 -0
- package/dist/modules/formats/wquiz.js +23 -0
- package/dist/modules/formats/wquiz.js.map +1 -0
- package/dist/modules/formats/wslide.d.ts +130 -0
- package/dist/modules/formats/wslide.d.ts.map +1 -0
- package/dist/modules/formats/wslide.js +23 -0
- package/dist/modules/formats/wslide.js.map +1 -0
- package/dist/modules/formats/wviz.d.ts +114 -0
- package/dist/modules/formats/wviz.d.ts.map +1 -0
- package/dist/modules/formats/wviz.js +21 -0
- package/dist/modules/formats/wviz.js.map +1 -0
- 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/kits/index.d.ts +41 -0
- package/dist/modules/kits/index.d.ts.map +1 -0
- package/dist/modules/kits/index.js +85 -0
- package/dist/modules/kits/index.js.map +1 -0
- package/dist/modules/kits/types.d.ts +107 -3
- package/dist/modules/kits/types.d.ts.map +1 -1
- package/dist/modules/kits/types.js +15 -2
- package/dist/modules/kits/types.js.map +1 -1
- package/dist/modules/license/index.d.ts +53 -0
- package/dist/modules/license/index.d.ts.map +1 -0
- package/dist/modules/license/index.js +233 -0
- package/dist/modules/license/index.js.map +1 -0
- package/dist/modules/license/types.d.ts +222 -3
- package/dist/modules/license/types.d.ts.map +1 -1
- package/dist/modules/license/types.js +21 -2
- package/dist/modules/license/types.js.map +1 -1
- package/dist/modules/notifications/index.d.ts +40 -0
- package/dist/modules/notifications/index.d.ts.map +1 -0
- package/dist/modules/notifications/index.js +280 -0
- package/dist/modules/notifications/index.js.map +1 -0
- package/dist/modules/notifications/types.d.ts +152 -3
- package/dist/modules/notifications/types.d.ts.map +1 -1
- package/dist/modules/notifications/types.js +21 -2
- package/dist/modules/notifications/types.js.map +1 -1
- package/dist/server/create-sdk.d.ts +4 -0
- package/dist/server/create-sdk.d.ts.map +1 -1
- package/dist/server/create-sdk.js +19 -26
- package/dist/server/create-sdk.js.map +1 -1
- 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 +10 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +9 -2
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +35 -25
- package/dist/types.d.ts.map +1 -1
- package/docs/USAGE.md +224 -1
- package/package.json +13 -5
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,16 +18,17 @@ 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';
|
|
25
25
|
export { AI_MODELS } from './modules/ai/types.js';
|
|
26
26
|
export type { EventsModule, SoulcraftEventMap, SoulcraftEventName, VfsWriteEvent, VfsDeleteEvent, VfsRenameEvent, } from './modules/events/types.js';
|
|
27
27
|
export type { SkillsModule, Skill, SkillListOptions, SkillInstallOptions, } from './modules/skills/types.js';
|
|
28
|
-
export type
|
|
29
|
-
export type
|
|
30
|
-
export type
|
|
31
|
-
export
|
|
32
|
-
export type
|
|
28
|
+
export type { LicenseResult, LicenseModule, LicensePlanTier, LicenseFeatures, AICreditCheckResult, AIUsageRecord, AIProviderConfig, } from './modules/license/types.js';
|
|
29
|
+
export type { KitsModule, SoulcraftKitConfig, } from './modules/kits/types.js';
|
|
30
|
+
export type { WvizDocument, WvizEntity, WvizEdge, WvizSnapshot, WvizViewType, WdocDocument, WdocMeta, WdocBlock, WdocInlineContent, WdocTextNode, WdocMark, WdocParagraphBlock, WdocHeadingBlock, WdocBlockquoteBlock, WdocCodeBlock, WdocBulletListBlock, WdocOrderedListBlock, WdocListItemBlock, WdocTaskListBlock, WdocTaskItemBlock, WdocImageBlock, WdocVideoBlock, WdocIconBlock, WdocSvgEmbedBlock, WdocEmbedBlock, WdocTableBlock, WdocTableRowBlock, WdocTableCellBlock, WdocTableHeaderBlock, WdocHorizontalRuleBlock, WdocCalloutBlock, WslideDocument, WslideSlide, WslideBackground, WslideLayout, WslideTransition, WslideConfig, WquizDocument, WquizQuestion, WquizQuestionType, WquizAnswer, WquizScoringRules, SoulcraftFormat, SoulcraftFormatDocument, } from './modules/formats/types.js';
|
|
31
|
+
export { SOULCRAFT_FORMATS } from './modules/formats/types.js';
|
|
32
|
+
export type { BillingModule, BillingPlanType, SubscriptionStatus, ModelUsage, UsageData, UsageStatus, SubscriptionData, LimitCheckResult, BatchIncrementData, CreateCheckoutSessionOptions, CreatePortalSessionOptions, } from './modules/billing/types.js';
|
|
33
|
+
export type { NotificationsModule, Notification, NotificationResult, NotificationSender, EmailNotification, SmsNotification, } from './modules/notifications/types.js';
|
|
33
34
|
//# 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;;;;;;;;;;;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
CHANGED
|
@@ -14,4 +14,5 @@ export { createCapabilityToken, verifyCapabilityToken, } from './modules/brainy/
|
|
|
14
14
|
export { SDKError, SDKDisconnectedError, SDKTimeoutError, SDKAuthError, SDKForbiddenError, SDKRpcError, SDKMethodNotFoundError, } from './modules/brainy/errors.js';
|
|
15
15
|
export { SOULCRAFT_USER_FIELDS, SOULCRAFT_SESSION_CONFIG, computeEmailHash, getAuthMode, getOIDCClientConfig, } from './modules/auth/config.js';
|
|
16
16
|
export { AI_MODELS } from './modules/ai/types.js';
|
|
17
|
+
export { SOULCRAFT_FORMATS } from './modules/formats/types.js';
|
|
17
18
|
//# 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;;;;;;;;;;;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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module billing/firestore-provider
|
|
3
|
+
* @description Firestore-backed billing provider for production deployments.
|
|
4
|
+
*
|
|
5
|
+
* Reads subscription and license data from the `licenses` Firestore collection
|
|
6
|
+
* managed by Portal (`soulcraft.com`). Workshop, Venue, and Academy all write
|
|
7
|
+
* usage counters to the same document so Portal's Admin can see cross-product usage.
|
|
8
|
+
*
|
|
9
|
+
* This provider is activated when `FIREBASE_SERVICE_ACCOUNT_KEY` is set in the
|
|
10
|
+
* environment. `firebase-admin` is loaded lazily via dynamic import so that the
|
|
11
|
+
* SDK does not require the package when using the local provider.
|
|
12
|
+
*
|
|
13
|
+
* **Firestore document structure:**
|
|
14
|
+
* ```
|
|
15
|
+
* licenses/{docId}
|
|
16
|
+
* email: string (user email — used to look up the doc)
|
|
17
|
+
* product: 'workshop' | 'venue' | 'academy'
|
|
18
|
+
* status: 'active' | 'past_due' | 'canceled' | 'trialing'
|
|
19
|
+
* plan: 'standard' | 'byok'
|
|
20
|
+
* credits: {
|
|
21
|
+
* monthlyIncluded: number
|
|
22
|
+
* monthlyUsed: number (SDK increments this)
|
|
23
|
+
* topUpBalance: number (SDK decrements this)
|
|
24
|
+
* inputTokens: number
|
|
25
|
+
* outputTokens: number
|
|
26
|
+
* byModel: Record<model, { input, output }>
|
|
27
|
+
* periodStart: Timestamp
|
|
28
|
+
* periodEnd: Timestamp
|
|
29
|
+
* lastUpdated: Timestamp
|
|
30
|
+
* }
|
|
31
|
+
* stripeCustomerId: string
|
|
32
|
+
* stripeSubscriptionId: string
|
|
33
|
+
* cancelAtPeriodEnd: boolean
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Note: The `userId` passed to provider methods is the user's email address when
|
|
37
|
+
* using Firestore, since the `licenses` collection is keyed by email. Products
|
|
38
|
+
* must pass `user.email` (not `user.id`) when constructing the SDK in Firestore mode.
|
|
39
|
+
*/
|
|
40
|
+
import type { BillingProvider, UsageData, SubscriptionData, LimitCheckResult, UsageStatus, BatchIncrementData } from './types.js';
|
|
41
|
+
/**
|
|
42
|
+
* Firestore-backed billing provider for production deployments.
|
|
43
|
+
*
|
|
44
|
+
* The `userId` parameter on all methods must be the user's **email address**,
|
|
45
|
+
* since the Firestore `licenses` collection is indexed by email.
|
|
46
|
+
*/
|
|
47
|
+
export declare class FirestoreBillingProvider implements BillingProvider {
|
|
48
|
+
checkLimit(userId: string, hasPersonalKey: boolean): Promise<LimitCheckResult>;
|
|
49
|
+
getUsage(userId: string): Promise<UsageData>;
|
|
50
|
+
getUsageStatus(userId: string, hasPersonalKey: boolean): Promise<UsageStatus>;
|
|
51
|
+
incrementUsage(userId: string, inputTokens: number, outputTokens: number, model: string, useTopUp: boolean): Promise<void>;
|
|
52
|
+
batchIncrementUsage(userId: string, batch: BatchIncrementData): Promise<void>;
|
|
53
|
+
getSubscription(userId: string): Promise<SubscriptionData | undefined>;
|
|
54
|
+
hasActiveSubscription(userId: string): Promise<boolean>;
|
|
55
|
+
getTopUpBalance(userId: string): Promise<number>;
|
|
56
|
+
canAccessPaidFeature(userId: string): Promise<boolean>;
|
|
57
|
+
private _toSubscription;
|
|
58
|
+
private _resetPeriod;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=firestore-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firestore-provider.d.ts","sourceRoot":"","sources":["../../../src/modules/billing/firestore-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,SAAS,EACT,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAkJnB;;;;;GAKG;AACH,qBAAa,wBAAyB,YAAW,eAAe;IACxD,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8B9E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAiB5C,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC;IAgC7E,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB1H,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB7E,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAMtE,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOvD,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOhD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAM5D,OAAO,CAAC,eAAe;YA2BT,YAAY;CAe3B"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module billing/firestore-provider
|
|
3
|
+
* @description Firestore-backed billing provider for production deployments.
|
|
4
|
+
*
|
|
5
|
+
* Reads subscription and license data from the `licenses` Firestore collection
|
|
6
|
+
* managed by Portal (`soulcraft.com`). Workshop, Venue, and Academy all write
|
|
7
|
+
* usage counters to the same document so Portal's Admin can see cross-product usage.
|
|
8
|
+
*
|
|
9
|
+
* This provider is activated when `FIREBASE_SERVICE_ACCOUNT_KEY` is set in the
|
|
10
|
+
* environment. `firebase-admin` is loaded lazily via dynamic import so that the
|
|
11
|
+
* SDK does not require the package when using the local provider.
|
|
12
|
+
*
|
|
13
|
+
* **Firestore document structure:**
|
|
14
|
+
* ```
|
|
15
|
+
* licenses/{docId}
|
|
16
|
+
* email: string (user email — used to look up the doc)
|
|
17
|
+
* product: 'workshop' | 'venue' | 'academy'
|
|
18
|
+
* status: 'active' | 'past_due' | 'canceled' | 'trialing'
|
|
19
|
+
* plan: 'standard' | 'byok'
|
|
20
|
+
* credits: {
|
|
21
|
+
* monthlyIncluded: number
|
|
22
|
+
* monthlyUsed: number (SDK increments this)
|
|
23
|
+
* topUpBalance: number (SDK decrements this)
|
|
24
|
+
* inputTokens: number
|
|
25
|
+
* outputTokens: number
|
|
26
|
+
* byModel: Record<model, { input, output }>
|
|
27
|
+
* periodStart: Timestamp
|
|
28
|
+
* periodEnd: Timestamp
|
|
29
|
+
* lastUpdated: Timestamp
|
|
30
|
+
* }
|
|
31
|
+
* stripeCustomerId: string
|
|
32
|
+
* stripeSubscriptionId: string
|
|
33
|
+
* cancelAtPeriodEnd: boolean
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* Note: The `userId` passed to provider methods is the user's email address when
|
|
37
|
+
* using Firestore, since the `licenses` collection is keyed by email. Products
|
|
38
|
+
* must pass `user.email` (not `user.id`) when constructing the SDK in Firestore mode.
|
|
39
|
+
*/
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
// Lazy loader
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
let _db;
|
|
44
|
+
let _FieldValue;
|
|
45
|
+
async function _getFirestore() {
|
|
46
|
+
if (_db && _FieldValue)
|
|
47
|
+
return { db: _db, FieldValue: _FieldValue };
|
|
48
|
+
const key = process.env['FIREBASE_SERVICE_ACCOUNT_KEY'];
|
|
49
|
+
if (!key)
|
|
50
|
+
throw new Error('FIREBASE_SERVICE_ACCOUNT_KEY is not set — cannot use Firestore billing provider');
|
|
51
|
+
// firebase-admin is an optional peer dependency — loaded at runtime only when configured.
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
const admin = await Function('return import("firebase-admin")')();
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
const { getFirestore, FieldValue } = await Function('return import("firebase-admin/firestore")')();
|
|
56
|
+
if (!admin.apps?.length) {
|
|
57
|
+
admin.initializeApp({
|
|
58
|
+
credential: admin.credential.cert(JSON.parse(Buffer.from(key, 'base64').toString('utf-8'))),
|
|
59
|
+
preferRest: true, // Use REST transport — required for Bun compatibility (avoids gRPC).
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
_db = getFirestore();
|
|
63
|
+
_FieldValue = FieldValue;
|
|
64
|
+
return { db: _db, FieldValue: _FieldValue };
|
|
65
|
+
}
|
|
66
|
+
const _docCache = new Map();
|
|
67
|
+
const DOC_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
68
|
+
async function _getLicenseDoc(email) {
|
|
69
|
+
const cached = _docCache.get(email);
|
|
70
|
+
if (cached && Date.now() < cached.expiresAt)
|
|
71
|
+
return cached;
|
|
72
|
+
const { db } = await _getFirestore();
|
|
73
|
+
const snap = await db.collection('licenses')
|
|
74
|
+
.where('email', '==', email.toLowerCase())
|
|
75
|
+
.where('status', 'in', ['active', 'trialing', 'past_due'])
|
|
76
|
+
.get();
|
|
77
|
+
if (!snap.docs.length)
|
|
78
|
+
return null;
|
|
79
|
+
const doc = snap.docs[0];
|
|
80
|
+
const data = doc.data() ?? {};
|
|
81
|
+
const result = { data, docId: doc.id, ref: doc.ref, expiresAt: Date.now() + DOC_CACHE_TTL_MS };
|
|
82
|
+
_docCache.set(email, result);
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
function _invalidateCache(email) {
|
|
86
|
+
_docCache.delete(email);
|
|
87
|
+
}
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
// Period helpers
|
|
90
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
function _toISO(ts) {
|
|
92
|
+
if (!ts)
|
|
93
|
+
return undefined;
|
|
94
|
+
if (typeof ts === 'string')
|
|
95
|
+
return ts;
|
|
96
|
+
// Firestore Timestamp object
|
|
97
|
+
if (typeof ts === 'object' && ts !== null && 'toDate' in ts) {
|
|
98
|
+
return ts.toDate().toISOString();
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
function _currentPeriod() {
|
|
103
|
+
const d = new Date();
|
|
104
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
|
|
105
|
+
}
|
|
106
|
+
function _shouldReset(periodEnd) {
|
|
107
|
+
const iso = _toISO(periodEnd);
|
|
108
|
+
if (!iso)
|
|
109
|
+
return true;
|
|
110
|
+
return new Date() >= new Date(iso);
|
|
111
|
+
}
|
|
112
|
+
const FREE_TIER_LIMIT = 100;
|
|
113
|
+
const MODEL_PRICING = {
|
|
114
|
+
'claude-haiku-4-5-20251001': { input: 0.80, output: 4.00 },
|
|
115
|
+
'claude-sonnet-4-6': { input: 3.00, output: 15.00 },
|
|
116
|
+
'claude-opus-4-6': { input: 15.00, output: 75.00 },
|
|
117
|
+
};
|
|
118
|
+
function _estimateCostUSD(byModel) {
|
|
119
|
+
let total = 0;
|
|
120
|
+
for (const [model, counts] of Object.entries(byModel)) {
|
|
121
|
+
const pricing = MODEL_PRICING[model];
|
|
122
|
+
if (!pricing)
|
|
123
|
+
continue;
|
|
124
|
+
total += (counts.input / 1_000_000) * pricing.input;
|
|
125
|
+
total += (counts.output / 1_000_000) * pricing.output;
|
|
126
|
+
}
|
|
127
|
+
return Math.round(total * 10_000) / 10_000;
|
|
128
|
+
}
|
|
129
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
130
|
+
// Provider implementation
|
|
131
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
132
|
+
/**
|
|
133
|
+
* Firestore-backed billing provider for production deployments.
|
|
134
|
+
*
|
|
135
|
+
* The `userId` parameter on all methods must be the user's **email address**,
|
|
136
|
+
* since the Firestore `licenses` collection is indexed by email.
|
|
137
|
+
*/
|
|
138
|
+
export class FirestoreBillingProvider {
|
|
139
|
+
async checkLimit(userId, hasPersonalKey) {
|
|
140
|
+
if (hasPersonalKey) {
|
|
141
|
+
return { ok: true, remaining: Infinity, topUpBalance: 0, totalAvailable: Infinity, useTopUp: false };
|
|
142
|
+
}
|
|
143
|
+
const doc = await _getLicenseDoc(userId);
|
|
144
|
+
if (!doc) {
|
|
145
|
+
// No license — free tier.
|
|
146
|
+
return { ok: false, remaining: 0, topUpBalance: 0, totalAvailable: 0, useTopUp: false, reason: 'no_subscription' };
|
|
147
|
+
}
|
|
148
|
+
const credits = doc.data['credits'] ?? {};
|
|
149
|
+
const monthlyIncluded = credits['monthlyIncluded'] ?? FREE_TIER_LIMIT;
|
|
150
|
+
const monthlyUsed = credits['monthlyUsed'] ?? 0;
|
|
151
|
+
const topUpBalance = credits['topUpBalance'] ?? 0;
|
|
152
|
+
const periodEnd = credits['periodEnd'];
|
|
153
|
+
if (_shouldReset(periodEnd)) {
|
|
154
|
+
await this._resetPeriod(doc);
|
|
155
|
+
return { ok: true, remaining: monthlyIncluded, topUpBalance, totalAvailable: monthlyIncluded + topUpBalance, useTopUp: false };
|
|
156
|
+
}
|
|
157
|
+
const remaining = Math.max(0, monthlyIncluded - monthlyUsed);
|
|
158
|
+
const totalAvailable = remaining + topUpBalance;
|
|
159
|
+
if (remaining > 0)
|
|
160
|
+
return { ok: true, remaining, topUpBalance, totalAvailable, useTopUp: false };
|
|
161
|
+
if (topUpBalance > 0)
|
|
162
|
+
return { ok: true, remaining: 0, topUpBalance, totalAvailable, useTopUp: true };
|
|
163
|
+
return { ok: false, remaining: 0, topUpBalance: 0, totalAvailable: 0, useTopUp: false, reason: 'limit_reached' };
|
|
164
|
+
}
|
|
165
|
+
async getUsage(userId) {
|
|
166
|
+
const doc = await _getLicenseDoc(userId);
|
|
167
|
+
if (!doc) {
|
|
168
|
+
return { currentPeriod: _currentPeriod(), periodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), messagesUsed: 0, inputTokens: 0, outputTokens: 0, byModel: {}, lastUpdated: new Date().toISOString() };
|
|
169
|
+
}
|
|
170
|
+
const credits = doc.data['credits'] ?? {};
|
|
171
|
+
return {
|
|
172
|
+
currentPeriod: _currentPeriod(),
|
|
173
|
+
periodEnd: _toISO(credits['periodEnd']) ?? new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
174
|
+
messagesUsed: credits['monthlyUsed'] ?? 0,
|
|
175
|
+
inputTokens: credits['inputTokens'] ?? 0,
|
|
176
|
+
outputTokens: credits['outputTokens'] ?? 0,
|
|
177
|
+
byModel: credits['byModel'] ?? {},
|
|
178
|
+
lastUpdated: _toISO(credits['lastUpdated']) ?? new Date().toISOString(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async getUsageStatus(userId, hasPersonalKey) {
|
|
182
|
+
const usage = await this.getUsage(userId);
|
|
183
|
+
const doc = await _getLicenseDoc(userId);
|
|
184
|
+
const credits = doc?.data['credits'] ?? {};
|
|
185
|
+
const topUpBalance = credits['topUpBalance'] ?? 0;
|
|
186
|
+
const monthlyIncluded = credits['monthlyIncluded'] ?? FREE_TIER_LIMIT;
|
|
187
|
+
const subscription = doc ? this._toSubscription(doc.data) : undefined;
|
|
188
|
+
if (hasPersonalKey) {
|
|
189
|
+
return { mode: 'byok', usage, limit: null, remaining: null, topUpBalance, totalAvailable: null, percentUsed: null, resetsAt: usage.periodEnd, estimatedCostUSD: _estimateCostUSD(usage.byModel), ...(subscription ? { subscription } : {}) };
|
|
190
|
+
}
|
|
191
|
+
const isSubscriber = subscription?.status === 'active' || subscription?.status === 'trialing';
|
|
192
|
+
const remaining = Math.max(0, monthlyIncluded - usage.messagesUsed);
|
|
193
|
+
const totalAvailable = remaining + topUpBalance;
|
|
194
|
+
const percentUsed = Math.round((usage.messagesUsed / monthlyIncluded) * 100);
|
|
195
|
+
return {
|
|
196
|
+
mode: isSubscriber ? 'included' : 'free',
|
|
197
|
+
usage,
|
|
198
|
+
limit: monthlyIncluded,
|
|
199
|
+
remaining,
|
|
200
|
+
topUpBalance,
|
|
201
|
+
totalAvailable,
|
|
202
|
+
percentUsed,
|
|
203
|
+
resetsAt: usage.periodEnd,
|
|
204
|
+
estimatedCostUSD: _estimateCostUSD(usage.byModel),
|
|
205
|
+
...(subscription ? { subscription } : {}),
|
|
206
|
+
isFreeUser: !isSubscriber,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
async incrementUsage(userId, inputTokens, outputTokens, model, useTopUp) {
|
|
210
|
+
const doc = await _getLicenseDoc(userId);
|
|
211
|
+
if (!doc)
|
|
212
|
+
return; // No license doc — free tier, usage not tracked in Firestore.
|
|
213
|
+
const { FieldValue } = await _getFirestore();
|
|
214
|
+
const updates = {
|
|
215
|
+
'credits.monthlyUsed': FieldValue.increment(1),
|
|
216
|
+
'credits.inputTokens': FieldValue.increment(inputTokens),
|
|
217
|
+
'credits.outputTokens': FieldValue.increment(outputTokens),
|
|
218
|
+
[`credits.byModel.${model}.input`]: FieldValue.increment(inputTokens),
|
|
219
|
+
[`credits.byModel.${model}.output`]: FieldValue.increment(outputTokens),
|
|
220
|
+
'credits.lastUpdated': FieldValue.serverTimestamp(),
|
|
221
|
+
'updatedAt': FieldValue.serverTimestamp(),
|
|
222
|
+
};
|
|
223
|
+
if (useTopUp)
|
|
224
|
+
updates['credits.topUpBalance'] = FieldValue.increment(-1);
|
|
225
|
+
await doc.ref.update(updates);
|
|
226
|
+
_invalidateCache(userId);
|
|
227
|
+
}
|
|
228
|
+
async batchIncrementUsage(userId, batch) {
|
|
229
|
+
const doc = await _getLicenseDoc(userId);
|
|
230
|
+
if (!doc)
|
|
231
|
+
return;
|
|
232
|
+
const { FieldValue } = await _getFirestore();
|
|
233
|
+
const updates = {
|
|
234
|
+
'credits.monthlyUsed': FieldValue.increment(batch.messageCount),
|
|
235
|
+
'credits.inputTokens': FieldValue.increment(batch.inputTokens),
|
|
236
|
+
'credits.outputTokens': FieldValue.increment(batch.outputTokens),
|
|
237
|
+
'credits.lastUpdated': FieldValue.serverTimestamp(),
|
|
238
|
+
'updatedAt': FieldValue.serverTimestamp(),
|
|
239
|
+
};
|
|
240
|
+
if (batch.topUpUsed > 0)
|
|
241
|
+
updates['credits.topUpBalance'] = FieldValue.increment(-batch.topUpUsed);
|
|
242
|
+
for (const [model, counts] of Object.entries(batch.byModel)) {
|
|
243
|
+
updates[`credits.byModel.${model}.input`] = FieldValue.increment(counts.input);
|
|
244
|
+
updates[`credits.byModel.${model}.output`] = FieldValue.increment(counts.output);
|
|
245
|
+
}
|
|
246
|
+
await doc.ref.update(updates);
|
|
247
|
+
_invalidateCache(userId);
|
|
248
|
+
}
|
|
249
|
+
async getSubscription(userId) {
|
|
250
|
+
const doc = await _getLicenseDoc(userId);
|
|
251
|
+
if (!doc)
|
|
252
|
+
return undefined;
|
|
253
|
+
return this._toSubscription(doc.data);
|
|
254
|
+
}
|
|
255
|
+
async hasActiveSubscription(userId) {
|
|
256
|
+
const doc = await _getLicenseDoc(userId);
|
|
257
|
+
if (!doc)
|
|
258
|
+
return false;
|
|
259
|
+
const status = doc.data['status'];
|
|
260
|
+
return status === 'active' || status === 'trialing';
|
|
261
|
+
}
|
|
262
|
+
async getTopUpBalance(userId) {
|
|
263
|
+
const doc = await _getLicenseDoc(userId);
|
|
264
|
+
if (!doc)
|
|
265
|
+
return 0;
|
|
266
|
+
const credits = doc.data['credits'] ?? {};
|
|
267
|
+
return credits['topUpBalance'] ?? 0;
|
|
268
|
+
}
|
|
269
|
+
async canAccessPaidFeature(userId) {
|
|
270
|
+
return this.hasActiveSubscription(userId);
|
|
271
|
+
}
|
|
272
|
+
// ── Internal helpers ────────────────────────────────────────────────────
|
|
273
|
+
_toSubscription(data) {
|
|
274
|
+
const status = data['status'];
|
|
275
|
+
if (!status)
|
|
276
|
+
return undefined;
|
|
277
|
+
const stripeCustomerId = data['stripeCustomerId'];
|
|
278
|
+
const stripeSubscriptionId = data['stripeSubscriptionId'];
|
|
279
|
+
const cancelAtPeriodEnd = data['cancelAtPeriodEnd'];
|
|
280
|
+
const giftedBy = data['giftedBy'];
|
|
281
|
+
const currentPeriodEnd = _toISO(data['credits']?.['periodEnd']);
|
|
282
|
+
const giftedAt = _toISO(data['giftedAt']);
|
|
283
|
+
const expiresAt = _toISO(data['expiresAt']);
|
|
284
|
+
const note = data['note'];
|
|
285
|
+
return {
|
|
286
|
+
plan: data['plan'] ?? 'standard',
|
|
287
|
+
status: status,
|
|
288
|
+
...(stripeCustomerId ? { stripeCustomerId } : {}),
|
|
289
|
+
...(stripeSubscriptionId ? { stripeSubscriptionId } : {}),
|
|
290
|
+
...(currentPeriodEnd ? { currentPeriodEnd } : {}),
|
|
291
|
+
...(cancelAtPeriodEnd !== undefined ? { cancelAtPeriodEnd } : {}),
|
|
292
|
+
...(giftedBy ? { giftedBy } : {}),
|
|
293
|
+
...(giftedAt ? { giftedAt } : {}),
|
|
294
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
295
|
+
...(note ? { note } : {}),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async _resetPeriod(doc) {
|
|
299
|
+
const { FieldValue } = await _getFirestore();
|
|
300
|
+
const newPeriodEnd = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
301
|
+
await doc.ref.update({
|
|
302
|
+
'credits.monthlyUsed': 0,
|
|
303
|
+
'credits.inputTokens': 0,
|
|
304
|
+
'credits.outputTokens': 0,
|
|
305
|
+
'credits.byModel': {},
|
|
306
|
+
'credits.periodStart': FieldValue.serverTimestamp(),
|
|
307
|
+
'credits.periodEnd': newPeriodEnd,
|
|
308
|
+
'credits.lastUpdated': FieldValue.serverTimestamp(),
|
|
309
|
+
'updatedAt': FieldValue.serverTimestamp(),
|
|
310
|
+
});
|
|
311
|
+
_invalidateCache(doc.docId);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=firestore-provider.js.map
|