@comfyorg/account-core 1.0.0-alpha.1 → 1.0.0-alpha.2
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/core/billing/operationState.d.ts +123 -1
- package/dist/core/billing/operationState.js +17 -2
- package/dist/core/billing/subscriptionCommands.d.ts +26 -0
- package/dist/core/billing/subscriptionCommands.js +4 -0
- package/dist/core/requestTimeout.d.ts +7 -0
- package/dist/core/requestTimeout.js +13 -0
- package/dist/core/sessionTokenMint.d.ts +3 -1
- package/dist/core/sessionTokenMint.js +28 -11
- package/dist/core/webSession.d.ts +2 -0
- package/dist/core/webSession.js +25 -18
- package/dist/core/webSessionFlag.d.ts +1 -2
- package/dist/core/webSessionFlag.js +1 -2
- package/dist/core/webSessionIdentity.d.ts +6 -0
- package/dist/core/webSessionIdentity.js +17 -4
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { z } from 'zod';
|
|
2
2
|
import type { BillingScope } from './billingScope.js';
|
|
3
3
|
export declare const BillingOpStatusSchema: z.ZodObject<{
|
|
4
4
|
action_url: z.ZodOptional<z.ZodString>;
|
|
@@ -26,6 +26,94 @@ export declare const BillingOpStatusSchema: z.ZodObject<{
|
|
|
26
26
|
} & {
|
|
27
27
|
amount_charged_cents: z.ZodOptional<z.ZodNumber>;
|
|
28
28
|
credits_added: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
charge_breakdown: z.ZodOptional<z.ZodObject<{
|
|
30
|
+
currency: z.ZodString;
|
|
31
|
+
prorated: z.ZodBoolean;
|
|
32
|
+
} & {
|
|
33
|
+
amount_charged_cents: z.ZodNumber;
|
|
34
|
+
reasons: z.ZodArray<z.ZodObject<{
|
|
35
|
+
kind: z.ZodEnum<["promo_code", "subscription_discount", "account_balance"]>;
|
|
36
|
+
} & {
|
|
37
|
+
amount_cents: z.ZodNumber;
|
|
38
|
+
discount: z.ZodOptional<z.ZodObject<{
|
|
39
|
+
code: z.ZodString;
|
|
40
|
+
duration: z.ZodOptional<z.ZodEnum<["once", "repeating", "forever"]>>;
|
|
41
|
+
kind: z.ZodEnum<["plan", "promotion"]>;
|
|
42
|
+
name: z.ZodOptional<z.ZodString>;
|
|
43
|
+
} & {
|
|
44
|
+
amount_off_cents: z.ZodOptional<z.ZodNumber>;
|
|
45
|
+
duration_in_months: z.ZodOptional<z.ZodNumber>;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
code: string;
|
|
48
|
+
kind: "plan" | "promotion";
|
|
49
|
+
name?: string | undefined;
|
|
50
|
+
amount_off_cents?: number | undefined;
|
|
51
|
+
duration?: "once" | "repeating" | "forever" | undefined;
|
|
52
|
+
duration_in_months?: number | undefined;
|
|
53
|
+
}, {
|
|
54
|
+
code: string;
|
|
55
|
+
kind: "plan" | "promotion";
|
|
56
|
+
name?: string | undefined;
|
|
57
|
+
amount_off_cents?: number | undefined;
|
|
58
|
+
duration?: "once" | "repeating" | "forever" | undefined;
|
|
59
|
+
duration_in_months?: number | undefined;
|
|
60
|
+
}>>;
|
|
61
|
+
}, "strip", z.ZodTypeAny, {
|
|
62
|
+
amount_cents: number;
|
|
63
|
+
kind: "promo_code" | "subscription_discount" | "account_balance";
|
|
64
|
+
discount?: {
|
|
65
|
+
code: string;
|
|
66
|
+
kind: "plan" | "promotion";
|
|
67
|
+
name?: string | undefined;
|
|
68
|
+
amount_off_cents?: number | undefined;
|
|
69
|
+
duration?: "once" | "repeating" | "forever" | undefined;
|
|
70
|
+
duration_in_months?: number | undefined;
|
|
71
|
+
} | undefined;
|
|
72
|
+
}, {
|
|
73
|
+
amount_cents: number;
|
|
74
|
+
kind: "promo_code" | "subscription_discount" | "account_balance";
|
|
75
|
+
discount?: {
|
|
76
|
+
code: string;
|
|
77
|
+
kind: "plan" | "promotion";
|
|
78
|
+
name?: string | undefined;
|
|
79
|
+
amount_off_cents?: number | undefined;
|
|
80
|
+
duration?: "once" | "repeating" | "forever" | undefined;
|
|
81
|
+
duration_in_months?: number | undefined;
|
|
82
|
+
} | undefined;
|
|
83
|
+
}>, "many">;
|
|
84
|
+
}, "strip", z.ZodTypeAny, {
|
|
85
|
+
currency: string;
|
|
86
|
+
amount_charged_cents: number;
|
|
87
|
+
prorated: boolean;
|
|
88
|
+
reasons: {
|
|
89
|
+
amount_cents: number;
|
|
90
|
+
kind: "promo_code" | "subscription_discount" | "account_balance";
|
|
91
|
+
discount?: {
|
|
92
|
+
code: string;
|
|
93
|
+
kind: "plan" | "promotion";
|
|
94
|
+
name?: string | undefined;
|
|
95
|
+
amount_off_cents?: number | undefined;
|
|
96
|
+
duration?: "once" | "repeating" | "forever" | undefined;
|
|
97
|
+
duration_in_months?: number | undefined;
|
|
98
|
+
} | undefined;
|
|
99
|
+
}[];
|
|
100
|
+
}, {
|
|
101
|
+
currency: string;
|
|
102
|
+
amount_charged_cents: number;
|
|
103
|
+
prorated: boolean;
|
|
104
|
+
reasons: {
|
|
105
|
+
amount_cents: number;
|
|
106
|
+
kind: "promo_code" | "subscription_discount" | "account_balance";
|
|
107
|
+
discount?: {
|
|
108
|
+
code: string;
|
|
109
|
+
kind: "plan" | "promotion";
|
|
110
|
+
name?: string | undefined;
|
|
111
|
+
amount_off_cents?: number | undefined;
|
|
112
|
+
duration?: "once" | "repeating" | "forever" | undefined;
|
|
113
|
+
duration_in_months?: number | undefined;
|
|
114
|
+
} | undefined;
|
|
115
|
+
}[];
|
|
116
|
+
}>>;
|
|
29
117
|
}, "strip", z.ZodTypeAny, {
|
|
30
118
|
status: "failed" | "pending" | "succeeded" | "reconciliation_needed";
|
|
31
119
|
id: string;
|
|
@@ -40,6 +128,23 @@ export declare const BillingOpStatusSchema: z.ZodObject<{
|
|
|
40
128
|
payment_intent_client_secret?: string | undefined;
|
|
41
129
|
amount_charged_cents?: number | undefined;
|
|
42
130
|
authentication_state?: "requires_action" | "processing" | "failed_retryable" | "succeeded" | "reconciliation_needed" | undefined;
|
|
131
|
+
charge_breakdown?: {
|
|
132
|
+
currency: string;
|
|
133
|
+
amount_charged_cents: number;
|
|
134
|
+
prorated: boolean;
|
|
135
|
+
reasons: {
|
|
136
|
+
amount_cents: number;
|
|
137
|
+
kind: "promo_code" | "subscription_discount" | "account_balance";
|
|
138
|
+
discount?: {
|
|
139
|
+
code: string;
|
|
140
|
+
kind: "plan" | "promotion";
|
|
141
|
+
name?: string | undefined;
|
|
142
|
+
amount_off_cents?: number | undefined;
|
|
143
|
+
duration?: "once" | "repeating" | "forever" | undefined;
|
|
144
|
+
duration_in_months?: number | undefined;
|
|
145
|
+
} | undefined;
|
|
146
|
+
}[];
|
|
147
|
+
} | undefined;
|
|
43
148
|
credits_added?: number | undefined;
|
|
44
149
|
decline_reason?: "card_declined" | "insufficient_funds" | "expired_card" | "incorrect_cvc" | "authentication_required" | "authentication_failed" | "processing_error" | "payment_not_completed" | "generic" | undefined;
|
|
45
150
|
phase?: "in_progress" | "awaiting_payment_method" | "awaiting_invoice_payment" | undefined;
|
|
@@ -59,6 +164,23 @@ export declare const BillingOpStatusSchema: z.ZodObject<{
|
|
|
59
164
|
payment_intent_client_secret?: string | undefined;
|
|
60
165
|
amount_charged_cents?: number | undefined;
|
|
61
166
|
authentication_state?: "requires_action" | "processing" | "failed_retryable" | "succeeded" | "reconciliation_needed" | undefined;
|
|
167
|
+
charge_breakdown?: {
|
|
168
|
+
currency: string;
|
|
169
|
+
amount_charged_cents: number;
|
|
170
|
+
prorated: boolean;
|
|
171
|
+
reasons: {
|
|
172
|
+
amount_cents: number;
|
|
173
|
+
kind: "promo_code" | "subscription_discount" | "account_balance";
|
|
174
|
+
discount?: {
|
|
175
|
+
code: string;
|
|
176
|
+
kind: "plan" | "promotion";
|
|
177
|
+
name?: string | undefined;
|
|
178
|
+
amount_off_cents?: number | undefined;
|
|
179
|
+
duration?: "once" | "repeating" | "forever" | undefined;
|
|
180
|
+
duration_in_months?: number | undefined;
|
|
181
|
+
} | undefined;
|
|
182
|
+
}[];
|
|
183
|
+
} | undefined;
|
|
62
184
|
credits_added?: number | undefined;
|
|
63
185
|
decline_reason?: "card_declined" | "insufficient_funds" | "expired_card" | "incorrect_cvc" | "authentication_required" | "authentication_failed" | "processing_error" | "payment_not_completed" | "generic" | undefined;
|
|
64
186
|
phase?: "in_progress" | "awaiting_payment_method" | "awaiting_invoice_payment" | undefined;
|
|
@@ -11,11 +11,26 @@
|
|
|
11
11
|
* so no server or payment-provider text can reach a consumer through this
|
|
12
12
|
* state.
|
|
13
13
|
*/
|
|
14
|
-
import { zBillingOpStatusResponse } from '@comfyorg/ingest-types/zod';
|
|
14
|
+
import { zBillingOpChargeBreakdown, zBillingOpChargeReason, zBillingOpStatusResponse, zSubscriptionDiscount } from '@comfyorg/ingest-types/zod';
|
|
15
|
+
import { z } from 'zod';
|
|
15
16
|
import { wireCents } from './wireCents.js';
|
|
16
17
|
export const BillingOpStatusSchema = zBillingOpStatusResponse.extend({
|
|
17
18
|
amount_charged_cents: wireCents.optional(),
|
|
18
|
-
credits_added: wireCents.optional()
|
|
19
|
+
credits_added: wireCents.optional(),
|
|
20
|
+
charge_breakdown: zBillingOpChargeBreakdown
|
|
21
|
+
.extend({
|
|
22
|
+
amount_charged_cents: wireCents,
|
|
23
|
+
reasons: z.array(zBillingOpChargeReason.extend({
|
|
24
|
+
amount_cents: wireCents,
|
|
25
|
+
discount: zSubscriptionDiscount
|
|
26
|
+
.extend({
|
|
27
|
+
amount_off_cents: wireCents.optional(),
|
|
28
|
+
duration_in_months: wireCents.optional()
|
|
29
|
+
})
|
|
30
|
+
.optional()
|
|
31
|
+
}))
|
|
32
|
+
})
|
|
33
|
+
.optional()
|
|
19
34
|
});
|
|
20
35
|
/**
|
|
21
36
|
* The phases the contract defines as blocked on the customer. Neither advances
|
|
@@ -93,6 +93,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
93
93
|
renewal_amount_cents: z.ZodOptional<z.ZodNumber>;
|
|
94
94
|
subtotal_cents: z.ZodOptional<z.ZodNumber>;
|
|
95
95
|
balance_applied_cents: z.ZodOptional<z.ZodNumber>;
|
|
96
|
+
proration_remaining_cents: z.ZodOptional<z.ZodNumber>;
|
|
97
|
+
proration_unused_cents: z.ZodOptional<z.ZodNumber>;
|
|
96
98
|
current_plan: z.ZodOptional<z.ZodObject<{
|
|
97
99
|
duration: z.ZodEnum<["MONTHLY", "ANNUAL"]>;
|
|
98
100
|
period_end: z.ZodOptional<z.ZodString>;
|
|
@@ -103,6 +105,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
103
105
|
credits_cents: z.ZodNumber;
|
|
104
106
|
price_cents: z.ZodNumber;
|
|
105
107
|
list_price_cents: z.ZodOptional<z.ZodNumber>;
|
|
108
|
+
monthly_list_price_cents: z.ZodOptional<z.ZodNumber>;
|
|
109
|
+
monthly_price_cents: z.ZodOptional<z.ZodNumber>;
|
|
106
110
|
seat_summary: z.ZodObject<{
|
|
107
111
|
seat_count: z.ZodNumber;
|
|
108
112
|
} & {
|
|
@@ -131,6 +135,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
131
135
|
period_end?: string | undefined;
|
|
132
136
|
period_start?: string | undefined;
|
|
133
137
|
list_price_cents?: number | undefined;
|
|
138
|
+
monthly_list_price_cents?: number | undefined;
|
|
139
|
+
monthly_price_cents?: number | undefined;
|
|
134
140
|
}, {
|
|
135
141
|
price_cents: number;
|
|
136
142
|
duration: "MONTHLY" | "ANNUAL";
|
|
@@ -145,6 +151,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
145
151
|
period_end?: string | undefined;
|
|
146
152
|
period_start?: string | undefined;
|
|
147
153
|
list_price_cents?: number | undefined;
|
|
154
|
+
monthly_list_price_cents?: number | undefined;
|
|
155
|
+
monthly_price_cents?: number | undefined;
|
|
148
156
|
}>>;
|
|
149
157
|
new_plan: z.ZodObject<{
|
|
150
158
|
duration: z.ZodEnum<["MONTHLY", "ANNUAL"]>;
|
|
@@ -156,6 +164,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
156
164
|
credits_cents: z.ZodNumber;
|
|
157
165
|
price_cents: z.ZodNumber;
|
|
158
166
|
list_price_cents: z.ZodOptional<z.ZodNumber>;
|
|
167
|
+
monthly_list_price_cents: z.ZodOptional<z.ZodNumber>;
|
|
168
|
+
monthly_price_cents: z.ZodOptional<z.ZodNumber>;
|
|
159
169
|
seat_summary: z.ZodObject<{
|
|
160
170
|
seat_count: z.ZodNumber;
|
|
161
171
|
} & {
|
|
@@ -184,6 +194,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
184
194
|
period_end?: string | undefined;
|
|
185
195
|
period_start?: string | undefined;
|
|
186
196
|
list_price_cents?: number | undefined;
|
|
197
|
+
monthly_list_price_cents?: number | undefined;
|
|
198
|
+
monthly_price_cents?: number | undefined;
|
|
187
199
|
}, {
|
|
188
200
|
price_cents: number;
|
|
189
201
|
duration: "MONTHLY" | "ANNUAL";
|
|
@@ -198,6 +210,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
198
210
|
period_end?: string | undefined;
|
|
199
211
|
period_start?: string | undefined;
|
|
200
212
|
list_price_cents?: number | undefined;
|
|
213
|
+
monthly_list_price_cents?: number | undefined;
|
|
214
|
+
monthly_price_cents?: number | undefined;
|
|
201
215
|
}>;
|
|
202
216
|
discounts: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
203
217
|
code: z.ZodString;
|
|
@@ -244,6 +258,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
244
258
|
period_end?: string | undefined;
|
|
245
259
|
period_start?: string | undefined;
|
|
246
260
|
list_price_cents?: number | undefined;
|
|
261
|
+
monthly_list_price_cents?: number | undefined;
|
|
262
|
+
monthly_price_cents?: number | undefined;
|
|
247
263
|
};
|
|
248
264
|
transition_type: "new_subscription" | "upgrade" | "downgrade" | "duration_change";
|
|
249
265
|
currency?: string | undefined;
|
|
@@ -267,6 +283,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
267
283
|
period_end?: string | undefined;
|
|
268
284
|
period_start?: string | undefined;
|
|
269
285
|
list_price_cents?: number | undefined;
|
|
286
|
+
monthly_list_price_cents?: number | undefined;
|
|
287
|
+
monthly_price_cents?: number | undefined;
|
|
270
288
|
} | undefined;
|
|
271
289
|
discounts?: {
|
|
272
290
|
code: string;
|
|
@@ -277,6 +295,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
277
295
|
duration_in_months?: number | undefined;
|
|
278
296
|
}[] | undefined;
|
|
279
297
|
payment_method_configuration_id?: string | undefined;
|
|
298
|
+
proration_remaining_cents?: number | undefined;
|
|
299
|
+
proration_unused_cents?: number | undefined;
|
|
280
300
|
reason?: string | undefined;
|
|
281
301
|
renewal_amount_cents?: number | undefined;
|
|
282
302
|
renewal_at?: string | undefined;
|
|
@@ -304,6 +324,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
304
324
|
period_end?: string | undefined;
|
|
305
325
|
period_start?: string | undefined;
|
|
306
326
|
list_price_cents?: number | undefined;
|
|
327
|
+
monthly_list_price_cents?: number | undefined;
|
|
328
|
+
monthly_price_cents?: number | undefined;
|
|
307
329
|
};
|
|
308
330
|
transition_type: "new_subscription" | "upgrade" | "downgrade" | "duration_change";
|
|
309
331
|
currency?: string | undefined;
|
|
@@ -327,6 +349,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
327
349
|
period_end?: string | undefined;
|
|
328
350
|
period_start?: string | undefined;
|
|
329
351
|
list_price_cents?: number | undefined;
|
|
352
|
+
monthly_list_price_cents?: number | undefined;
|
|
353
|
+
monthly_price_cents?: number | undefined;
|
|
330
354
|
} | undefined;
|
|
331
355
|
discounts?: {
|
|
332
356
|
code: string;
|
|
@@ -337,6 +361,8 @@ declare const PreviewSchema: z.ZodObject<{
|
|
|
337
361
|
duration_in_months?: number | undefined;
|
|
338
362
|
}[] | undefined;
|
|
339
363
|
payment_method_configuration_id?: string | undefined;
|
|
364
|
+
proration_remaining_cents?: number | undefined;
|
|
365
|
+
proration_unused_cents?: number | undefined;
|
|
340
366
|
reason?: string | undefined;
|
|
341
367
|
renewal_amount_cents?: number | undefined;
|
|
342
368
|
renewal_at?: string | undefined;
|
|
@@ -33,6 +33,8 @@ const PlanInfoSchema = zPreviewPlanInfo.extend({
|
|
|
33
33
|
credits_cents: wireCents,
|
|
34
34
|
price_cents: wireCents,
|
|
35
35
|
list_price_cents: wireCents.optional(),
|
|
36
|
+
monthly_list_price_cents: wireCents.optional(),
|
|
37
|
+
monthly_price_cents: wireCents.optional(),
|
|
36
38
|
seat_summary: zPreviewPlanInfo.shape.seat_summary.extend({
|
|
37
39
|
total_cost_cents: wireCents,
|
|
38
40
|
total_credits_cents: wireCents
|
|
@@ -47,6 +49,8 @@ const PreviewSchema = zPreviewSubscribeResponse.extend({
|
|
|
47
49
|
renewal_amount_cents: wireCents.optional(),
|
|
48
50
|
subtotal_cents: wireCents.optional(),
|
|
49
51
|
balance_applied_cents: wireCents.optional(),
|
|
52
|
+
proration_remaining_cents: wireCents.optional(),
|
|
53
|
+
proration_unused_cents: wireCents.optional(),
|
|
50
54
|
current_plan: PlanInfoSchema.optional(),
|
|
51
55
|
new_plan: PlanInfoSchema,
|
|
52
56
|
discounts: z
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface TimedSignal {
|
|
2
|
+
readonly signal: AbortSignal | undefined;
|
|
3
|
+
/** Disarms the timer; call once the response body has been read. */
|
|
4
|
+
readonly release: () => void;
|
|
5
|
+
}
|
|
6
|
+
/** Joins the caller's signal with an abort after `timeoutMs`, if one is set. */
|
|
7
|
+
export declare function timedSignal(signal: AbortSignal | undefined, timeoutMs: number | undefined): TimedSignal;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Joins the caller's signal with an abort after `timeoutMs`, if one is set. */
|
|
2
|
+
export function timedSignal(signal, timeoutMs) {
|
|
3
|
+
if (timeoutMs === undefined)
|
|
4
|
+
return { signal, release: () => undefined };
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
7
|
+
return {
|
|
8
|
+
signal: signal
|
|
9
|
+
? AbortSignal.any([signal, controller.signal])
|
|
10
|
+
: controller.signal,
|
|
11
|
+
release: () => clearTimeout(timer)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -8,6 +8,8 @@ export interface SessionTokenMintOptions {
|
|
|
8
8
|
readonly now?: () => number;
|
|
9
9
|
/** Re-mint once a cached token has this long left. Default 60s. */
|
|
10
10
|
readonly refreshBufferMs?: number;
|
|
11
|
+
/** Caps each mint, body included; a timeout is `SESSION_UNAVAILABLE`. Default: none. */
|
|
12
|
+
readonly timeoutMs?: number;
|
|
11
13
|
}
|
|
12
14
|
export interface SessionTokenFailure extends WebSessionFailure {
|
|
13
15
|
/** From a 429's `Retry-After`; no request is sent until it passes. */
|
|
@@ -30,4 +32,4 @@ export interface SessionTokenMint {
|
|
|
30
32
|
readonly getWorkspaceToken: (workspaceId?: string) => Promise<string>;
|
|
31
33
|
}
|
|
32
34
|
export declare const DEFAULT_REFRESH_BUFFER_MS = 60000;
|
|
33
|
-
export declare function createSessionTokenMint({ apiBaseUrl, fetchImpl, getSession, now, refreshBufferMs }: SessionTokenMintOptions): SessionTokenMint;
|
|
35
|
+
export declare function createSessionTokenMint({ apiBaseUrl, fetchImpl, getSession, now, refreshBufferMs, timeoutMs }: SessionTokenMintOptions): SessionTokenMint;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { zErrorResponse, zExchangeTokenResponse } from '@comfyorg/ingest-types/zod';
|
|
8
8
|
import { isCredentialFresh } from './credentialCache.js';
|
|
9
9
|
import { COMFY_CLIENT } from './requestAuth.js';
|
|
10
|
+
import { timedSignal } from './requestTimeout.js';
|
|
10
11
|
export class SessionTokenError extends Error {
|
|
11
12
|
failure;
|
|
12
13
|
constructor(failure) {
|
|
@@ -21,7 +22,8 @@ const STATUS_RULES = {
|
|
|
21
22
|
401: {
|
|
22
23
|
byServerCode: {
|
|
23
24
|
session_expired: 'SESSION_EXPIRED',
|
|
24
|
-
session_revoked: 'SESSION_REVOKED'
|
|
25
|
+
session_revoked: 'SESSION_REVOKED',
|
|
26
|
+
TOKEN_REVOKED: 'SESSION_REVOKED'
|
|
25
27
|
},
|
|
26
28
|
fallback: 'NO_SESSION'
|
|
27
29
|
},
|
|
@@ -31,6 +33,11 @@ const STATUS_RULES = {
|
|
|
31
33
|
workspace_access_denied: 'WORKSPACE_ACCESS_DENIED'
|
|
32
34
|
},
|
|
33
35
|
fallback: 'SESSION_REQUEST_REFUSED'
|
|
36
|
+
},
|
|
37
|
+
/** The workspace is unknown, deleted, or not the user's: one remedy. */
|
|
38
|
+
404: {
|
|
39
|
+
byServerCode: { NOT_FOUND: 'WORKSPACE_ACCESS_DENIED' },
|
|
40
|
+
fallback: 'SESSION_REQUEST_REFUSED'
|
|
34
41
|
}
|
|
35
42
|
};
|
|
36
43
|
const zServerCode = zErrorResponse.pick({ code: true });
|
|
@@ -72,17 +79,17 @@ function codeFor(status, serverCode) {
|
|
|
72
79
|
return 'SESSION_REQUEST_REFUSED';
|
|
73
80
|
return rule.byServerCode[serverCode ?? ''] ?? rule.fallback;
|
|
74
81
|
}
|
|
75
|
-
|
|
82
|
+
function classifyFailure(response, body, nowMs) {
|
|
76
83
|
const { status } = response;
|
|
77
84
|
if (status === 429)
|
|
78
85
|
return rateLimited(response, nowMs);
|
|
79
86
|
if (status >= 500)
|
|
80
87
|
return failure('SESSION_UNAVAILABLE', status);
|
|
81
|
-
const parsed = zServerCode.safeParse(
|
|
88
|
+
const parsed = zServerCode.safeParse(body);
|
|
82
89
|
const serverCode = parsed.success ? parsed.data.code : undefined;
|
|
83
90
|
return failure(codeFor(status, serverCode), status, serverCode);
|
|
84
91
|
}
|
|
85
|
-
export function createSessionTokenMint({ apiBaseUrl, fetchImpl, getSession, now = Date.now, refreshBufferMs = DEFAULT_REFRESH_BUFFER_MS }) {
|
|
92
|
+
export function createSessionTokenMint({ apiBaseUrl, fetchImpl, getSession, now = Date.now, refreshBufferMs = DEFAULT_REFRESH_BUFFER_MS, timeoutMs }) {
|
|
86
93
|
const tokenUrl = `${apiBaseUrl.replace(/\/+$/, '')}/auth/token`;
|
|
87
94
|
/** Keyed by workspace; every entry belongs to `cacheOwner`. */
|
|
88
95
|
const cache = new Map();
|
|
@@ -101,9 +108,9 @@ export function createSessionTokenMint({ apiBaseUrl, fetchImpl, getSession, now
|
|
|
101
108
|
rateLimit = undefined;
|
|
102
109
|
}
|
|
103
110
|
async function request(session, workspaceId) {
|
|
104
|
-
|
|
111
|
+
const { signal, release } = timedSignal(undefined, timeoutMs);
|
|
105
112
|
try {
|
|
106
|
-
response = await fetchImpl(tokenUrl, {
|
|
113
|
+
const response = await fetchImpl(tokenUrl, {
|
|
107
114
|
method: 'POST',
|
|
108
115
|
credentials: 'include',
|
|
109
116
|
headers: {
|
|
@@ -114,20 +121,30 @@ export function createSessionTokenMint({ apiBaseUrl, fetchImpl, getSession, now
|
|
|
114
121
|
? {}
|
|
115
122
|
: { 'X-Comfy-Workspace-ID': workspaceId })
|
|
116
123
|
},
|
|
117
|
-
body: JSON.stringify(workspaceId === undefined ? {} : { workspace_id: workspaceId })
|
|
124
|
+
body: JSON.stringify(workspaceId === undefined ? {} : { workspace_id: workspaceId }),
|
|
125
|
+
signal
|
|
118
126
|
});
|
|
127
|
+
const body = await readJson(response);
|
|
128
|
+
if (signal?.aborted)
|
|
129
|
+
return failure('SESSION_UNAVAILABLE');
|
|
130
|
+
return response.ok
|
|
131
|
+
? credentialFrom(session, response.status, body)
|
|
132
|
+
: classifyFailure(response, body, now());
|
|
119
133
|
}
|
|
120
134
|
catch {
|
|
121
135
|
return failure('SESSION_UNAVAILABLE');
|
|
122
136
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
finally {
|
|
138
|
+
release();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function credentialFrom(session, status, body) {
|
|
142
|
+
const parsed = zExchangeTokenResponse.safeParse(body);
|
|
126
143
|
const expiresAt = parsed.success
|
|
127
144
|
? Date.parse(parsed.data.expires_at)
|
|
128
145
|
: Number.NaN;
|
|
129
146
|
if (!parsed.success || parsed.data.token === '' || !(expiresAt > now())) {
|
|
130
|
-
return failure('SESSION_UNAVAILABLE',
|
|
147
|
+
return failure('SESSION_UNAVAILABLE', status);
|
|
131
148
|
}
|
|
132
149
|
return {
|
|
133
150
|
status: 'ok',
|
|
@@ -5,6 +5,8 @@ export interface WebSessionOptions {
|
|
|
5
5
|
readonly apiBaseUrl: string;
|
|
6
6
|
readonly fetchImpl: typeof fetch;
|
|
7
7
|
readonly signal?: AbortSignal;
|
|
8
|
+
/** Caps each request, body included; a timeout is `SESSION_UNAVAILABLE`. Default: none. */
|
|
9
|
+
readonly timeoutMs?: number;
|
|
8
10
|
}
|
|
9
11
|
export declare function readWebSession(options: WebSessionOptions, { expectedUserId }?: {
|
|
10
12
|
readonly expectedUserId?: string;
|
package/dist/core/webSession.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Identity proof is an opaque string from the caller, so no provider leaks in.
|
|
5
5
|
*/
|
|
6
6
|
import { zCreateSessionResponse, zDeleteSessionResponse, zErrorResponse, zGetSessionResponse, zRevokeAllSessionsResponse } from '@comfyorg/ingest-types/zod';
|
|
7
|
+
import { timedSignal } from './requestTimeout.js';
|
|
7
8
|
const UNAUTHORIZED_CODES = {
|
|
8
9
|
no_session: 'NO_SESSION',
|
|
9
10
|
session_expired: 'SESSION_EXPIRED',
|
|
@@ -31,12 +32,11 @@ async function readJson(response) {
|
|
|
31
32
|
return undefined;
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
const { status } = response;
|
|
35
|
+
function classifyFailure(status, body) {
|
|
36
36
|
if (status === 429 || status >= 500) {
|
|
37
37
|
return failure('SESSION_UNAVAILABLE', status);
|
|
38
38
|
}
|
|
39
|
-
const parsed = zErrorResponse.safeParse(
|
|
39
|
+
const parsed = zErrorResponse.safeParse(body);
|
|
40
40
|
if (!parsed.success)
|
|
41
41
|
return failure('SESSION_REQUEST_REFUSED', status);
|
|
42
42
|
const serverCode = parsed.data.code;
|
|
@@ -48,24 +48,31 @@ async function classifyFailure(response) {
|
|
|
48
48
|
return failure(byServerCode, status, serverCode);
|
|
49
49
|
}
|
|
50
50
|
async function send(options, path, init) {
|
|
51
|
-
|
|
51
|
+
const { signal, release } = timedSignal(options.signal, options.timeoutMs);
|
|
52
52
|
try {
|
|
53
|
-
response = await options.fetchImpl(`${options.apiBaseUrl.replace(/\/+$/, '')}${path}`, { ...init, credentials: 'include', signal
|
|
53
|
+
const response = await options.fetchImpl(`${options.apiBaseUrl.replace(/\/+$/, '')}${path}`, { ...init, credentials: 'include', signal });
|
|
54
|
+
const body = await readJson(response);
|
|
55
|
+
if (signal?.aborted)
|
|
56
|
+
return failure('SESSION_UNAVAILABLE');
|
|
57
|
+
const { status } = response;
|
|
58
|
+
return response.ok ? { status, body } : classifyFailure(status, body);
|
|
54
59
|
}
|
|
55
60
|
catch {
|
|
56
61
|
return failure('SESSION_UNAVAILABLE');
|
|
57
62
|
}
|
|
58
|
-
|
|
63
|
+
finally {
|
|
64
|
+
release();
|
|
65
|
+
}
|
|
59
66
|
}
|
|
60
67
|
export async function readWebSession(options, { expectedUserId } = {}) {
|
|
61
68
|
const sent = await send(options, '/auth/session', {
|
|
62
69
|
method: 'GET',
|
|
63
70
|
cache: 'no-store'
|
|
64
71
|
});
|
|
65
|
-
if (
|
|
72
|
+
if ('code' in sent)
|
|
66
73
|
return sent;
|
|
67
|
-
const { status } = sent
|
|
68
|
-
const parsed = zGetSessionResponse.safeParse(
|
|
74
|
+
const { status } = sent;
|
|
75
|
+
const parsed = zGetSessionResponse.safeParse(sent.body);
|
|
69
76
|
if (!parsed.success)
|
|
70
77
|
return failure('SESSION_UNAVAILABLE', status);
|
|
71
78
|
const { user, csrf_token, expires_at, absolute_expires_at } = parsed.data;
|
|
@@ -98,22 +105,22 @@ export async function createWebSession(options, getIdentityProof, { expectedUser
|
|
|
98
105
|
method: 'POST',
|
|
99
106
|
headers: { Authorization: `Bearer ${proof}` }
|
|
100
107
|
});
|
|
101
|
-
if (
|
|
108
|
+
if ('code' in sent)
|
|
102
109
|
return sent;
|
|
103
|
-
const parsed = zCreateSessionResponse.safeParse(
|
|
110
|
+
const parsed = zCreateSessionResponse.safeParse(sent.body);
|
|
104
111
|
if (!parsed.success || !parsed.data.success) {
|
|
105
|
-
return failure('SESSION_UNAVAILABLE', sent.
|
|
112
|
+
return failure('SESSION_UNAVAILABLE', sent.status);
|
|
106
113
|
}
|
|
107
114
|
return readWebSession(options, { expectedUserId });
|
|
108
115
|
}
|
|
109
116
|
/** Sends no CSRF token: sign-out must work against a dead session. */
|
|
110
117
|
export async function deleteWebSession(options) {
|
|
111
118
|
const sent = await send(options, '/auth/session', { method: 'DELETE' });
|
|
112
|
-
if (
|
|
119
|
+
if ('code' in sent)
|
|
113
120
|
return sent;
|
|
114
|
-
const parsed = zDeleteSessionResponse.safeParse(
|
|
121
|
+
const parsed = zDeleteSessionResponse.safeParse(sent.body);
|
|
115
122
|
if (!parsed.success || !parsed.data.success) {
|
|
116
|
-
return failure('SESSION_UNAVAILABLE', sent.
|
|
123
|
+
return failure('SESSION_UNAVAILABLE', sent.status);
|
|
117
124
|
}
|
|
118
125
|
return { status: 'ok' };
|
|
119
126
|
}
|
|
@@ -127,11 +134,11 @@ export async function revokeAllWebSessions(options, csrfToken, getIdentityProof)
|
|
|
127
134
|
method: 'POST',
|
|
128
135
|
headers: { Authorization: `Bearer ${proof}`, 'X-CSRF-Token': csrfToken }
|
|
129
136
|
});
|
|
130
|
-
if (
|
|
137
|
+
if ('code' in sent)
|
|
131
138
|
return sent;
|
|
132
|
-
const parsed = zRevokeAllSessionsResponse.safeParse(
|
|
139
|
+
const parsed = zRevokeAllSessionsResponse.safeParse(sent.body);
|
|
133
140
|
if (!parsed.success) {
|
|
134
|
-
return failure('SESSION_UNAVAILABLE', sent.
|
|
141
|
+
return failure('SESSION_UNAVAILABLE', sent.status);
|
|
135
142
|
}
|
|
136
143
|
return { status: 'ok' };
|
|
137
144
|
}
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* `unified_web_session` for a site with no sign-in of its own to ask with.
|
|
3
3
|
* The anonymous `/api/features` document's global `web_session_probe` only
|
|
4
4
|
* decides whether to ask; the answer is the credentialed read's
|
|
5
|
-
* `unified_web_session` (cloud#10689).
|
|
6
|
-
* follow-up to cloud#10689 and not served yet. Every failure is `false`.
|
|
5
|
+
* `unified_web_session` (cloud#10689). Every failure is `false`.
|
|
7
6
|
*/
|
|
8
7
|
export interface FeaturesReadOptions {
|
|
9
8
|
/** Cloud origin, e.g. `https://cloud.comfy.org`. */
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* `unified_web_session` for a site with no sign-in of its own to ask with.
|
|
3
3
|
* The anonymous `/api/features` document's global `web_session_probe` only
|
|
4
4
|
* decides whether to ask; the answer is the credentialed read's
|
|
5
|
-
* `unified_web_session` (cloud#10689).
|
|
6
|
-
* follow-up to cloud#10689 and not served yet. Every failure is `false`.
|
|
5
|
+
* `unified_web_session` (cloud#10689). Every failure is `false`.
|
|
7
6
|
*/
|
|
8
7
|
const DEFAULT_TIMEOUT_MS = 5000;
|
|
9
8
|
function readsLiteralTrue(body, key) {
|
|
@@ -136,6 +136,12 @@ export interface WebSessionIdentity {
|
|
|
136
136
|
/** Only after an interactive sign-in; a token refresh must never call it. */
|
|
137
137
|
signedIn: (getProof: () => Promise<string>) => Promise<WebSessionResult>;
|
|
138
138
|
signOut: () => Promise<WebSessionCommandResult>;
|
|
139
|
+
/**
|
|
140
|
+
* Reads the session now, as a heartbeat would, and resolves once the answer
|
|
141
|
+
* and any restore it starts have settled. Reads nothing unless signed in as
|
|
142
|
+
* `expectedUserId` (any user when omitted).
|
|
143
|
+
*/
|
|
144
|
+
refresh: (expectedUserId?: string) => Promise<WebSessionIdentityState>;
|
|
139
145
|
/** Changes with the account; an answer started under an older epoch is stale. */
|
|
140
146
|
getEpoch: () => number;
|
|
141
147
|
/** Cancels timers, ignores answers in flight, and returns to idle. */
|
|
@@ -261,6 +261,7 @@ function createApiKeyIdentity() {
|
|
|
261
261
|
retryable: false
|
|
262
262
|
}),
|
|
263
263
|
signOut: async () => ({ status: 'ok' }),
|
|
264
|
+
refresh: async () => state,
|
|
264
265
|
getEpoch: () => 0,
|
|
265
266
|
dispose: () => undefined
|
|
266
267
|
};
|
|
@@ -280,6 +281,7 @@ function createAccountIdentity(options, login) {
|
|
|
280
281
|
let cancelBeat;
|
|
281
282
|
let releaseLeadership;
|
|
282
283
|
let stopWatching = [];
|
|
284
|
+
let restoring = { epoch, settled: Promise.resolve() };
|
|
283
285
|
function dispatch(event) {
|
|
284
286
|
const next = transitionWebSessionIdentity(state, event);
|
|
285
287
|
const changed = next.state !== state;
|
|
@@ -311,7 +313,7 @@ function createAccountIdentity(options, login) {
|
|
|
311
313
|
function beat() {
|
|
312
314
|
const started = epoch;
|
|
313
315
|
const observed = { type: 'session_observed', from: 'this_tab' };
|
|
314
|
-
|
|
316
|
+
return answer(started, observed, async () => {
|
|
315
317
|
const result = await readWebSession(options.session);
|
|
316
318
|
const shared = result.status === 'ok' || result.code === 'SESSION_REVOKED';
|
|
317
319
|
if (shared && started === epoch)
|
|
@@ -343,7 +345,7 @@ function createAccountIdentity(options, login) {
|
|
|
343
345
|
}
|
|
344
346
|
function armBeat() {
|
|
345
347
|
cancelBeat = schedule(() => {
|
|
346
|
-
beat();
|
|
348
|
+
void beat();
|
|
347
349
|
armBeat();
|
|
348
350
|
}, heartbeat?.intervalMs ?? HEARTBEAT_INTERVAL_MS);
|
|
349
351
|
}
|
|
@@ -377,7 +379,7 @@ function createAccountIdentity(options, login) {
|
|
|
377
379
|
stopWatching = [
|
|
378
380
|
visibility.onChange((visible) => {
|
|
379
381
|
if (visible && state.phase === 'signed_in')
|
|
380
|
-
beat();
|
|
382
|
+
void beat();
|
|
381
383
|
syncLeadership();
|
|
382
384
|
}),
|
|
383
385
|
...(crossTab ? [crossTab.onCredential(HEARTBEAT_KEY, adopt)] : [])
|
|
@@ -400,7 +402,7 @@ function createAccountIdentity(options, login) {
|
|
|
400
402
|
void answer(epoch, { type: 'read_answered' }, () => readWebSession(options.session));
|
|
401
403
|
return;
|
|
402
404
|
case 'restore':
|
|
403
|
-
|
|
405
|
+
restoring = { epoch, settled: restore(epoch, effect.expectedUserId) };
|
|
404
406
|
return;
|
|
405
407
|
case 'schedule_retry':
|
|
406
408
|
cancelRetry = schedule(() => {
|
|
@@ -477,6 +479,17 @@ function createAccountIdentity(options, login) {
|
|
|
477
479
|
publish('sign_out', revoked);
|
|
478
480
|
return result;
|
|
479
481
|
},
|
|
482
|
+
refresh: async (expectedUserId) => {
|
|
483
|
+
if (state.phase !== 'signed_in')
|
|
484
|
+
return state;
|
|
485
|
+
if ((expectedUserId ?? state.session.user.id) !== state.session.user.id) {
|
|
486
|
+
return state;
|
|
487
|
+
}
|
|
488
|
+
await beat();
|
|
489
|
+
if (restoring.epoch === epoch)
|
|
490
|
+
await restoring.settled;
|
|
491
|
+
return state;
|
|
492
|
+
},
|
|
480
493
|
getEpoch: () => epoch,
|
|
481
494
|
dispose: () => {
|
|
482
495
|
epoch += 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comfyorg/account-core",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "Shared account layer: a framework-free session core that exchanges a signed-in identity for the workspace-scoped Cloud JWT with valid-on-read freshness, plus the package-owned Firebase identity entry. It has no view layer and no Vue dependency; the composables and unstyled components ship from @comfyorg/account-ui",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://comfy.org",
|