@reauth-dev/sdk 0.2.0 → 0.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/chunk-5LFJ5PXQ.mjs +1042 -0
- package/dist/index.d.mts +182 -4
- package/dist/index.d.ts +182 -4
- package/dist/index.js +706 -0
- package/dist/index.mjs +1 -1
- package/dist/react/index.d.mts +62 -2
- package/dist/react/index.d.ts +62 -2
- package/dist/react/index.js +848 -11
- package/dist/react/index.mjs +134 -4
- package/dist/server.d.mts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +4 -0
- package/dist/server.mjs +4 -0
- package/dist/{types-BqZzje-y.d.mts → types-DKUKhCNE.d.mts} +165 -3
- package/dist/{types-BqZzje-y.d.ts → types-DKUKhCNE.d.ts} +165 -3
- package/package.json +1 -1
- package/dist/chunk-DMNMTW2C.mjs +0 -336
package/dist/index.js
CHANGED
|
@@ -160,6 +160,126 @@ function createReauthClient(config) {
|
|
|
160
160
|
}
|
|
161
161
|
},
|
|
162
162
|
// ========================================================================
|
|
163
|
+
// Headless Auth Methods
|
|
164
|
+
// ========================================================================
|
|
165
|
+
/**
|
|
166
|
+
* Get the domain's public auth configuration.
|
|
167
|
+
* Returns enabled auth methods and whether headless auth is available.
|
|
168
|
+
*/
|
|
169
|
+
async getConfig() {
|
|
170
|
+
const res = await fetch(`${baseUrl}/config`, {
|
|
171
|
+
credentials: "include",
|
|
172
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
173
|
+
});
|
|
174
|
+
if (!res.ok) {
|
|
175
|
+
throw new Error(`Failed to get config: ${res.status}`);
|
|
176
|
+
}
|
|
177
|
+
const data = await res.json();
|
|
178
|
+
return {
|
|
179
|
+
domain: data.domain,
|
|
180
|
+
authMethods: {
|
|
181
|
+
magicLink: data.auth_methods.magic_link,
|
|
182
|
+
googleOauth: data.auth_methods.google_oauth,
|
|
183
|
+
twitterOauth: data.auth_methods.twitter_oauth
|
|
184
|
+
},
|
|
185
|
+
redirectUrl: data.redirect_url,
|
|
186
|
+
headlessEnabled: data.headless_enabled
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* Request a magic link email for headless authentication.
|
|
191
|
+
* When callbackUrl is provided, the email link will point to your custom URL
|
|
192
|
+
* with the token as a query parameter.
|
|
193
|
+
* @param opts Options including email and optional callbackUrl
|
|
194
|
+
*/
|
|
195
|
+
async requestMagicLink(opts) {
|
|
196
|
+
const body = { email: opts.email };
|
|
197
|
+
if (opts.callbackUrl) {
|
|
198
|
+
body.callback_url = opts.callbackUrl;
|
|
199
|
+
}
|
|
200
|
+
const res = await fetch(`${baseUrl}/auth/request-magic-link`, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: { "Content-Type": "application/json" },
|
|
203
|
+
credentials: "include",
|
|
204
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
205
|
+
body: JSON.stringify(body)
|
|
206
|
+
});
|
|
207
|
+
if (!res.ok) {
|
|
208
|
+
const err = await res.json().catch(() => ({}));
|
|
209
|
+
throw new Error(err.message || `Failed to request magic link: ${res.status}`);
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
/**
|
|
213
|
+
* Verify a magic link token received from the email callback URL.
|
|
214
|
+
* On success, session cookies are set automatically.
|
|
215
|
+
* @param opts Options including the token from the callback URL
|
|
216
|
+
*/
|
|
217
|
+
async verifyMagicLink(opts) {
|
|
218
|
+
const res = await fetch(`${baseUrl}/auth/verify-magic-link`, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: { "Content-Type": "application/json" },
|
|
221
|
+
credentials: "include",
|
|
222
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
223
|
+
body: JSON.stringify({ token: opts.token })
|
|
224
|
+
});
|
|
225
|
+
if (!res.ok) {
|
|
226
|
+
const err = await res.json().catch(() => ({}));
|
|
227
|
+
throw new Error(err.message || `Failed to verify magic link: ${res.status}`);
|
|
228
|
+
}
|
|
229
|
+
const data = await res.json();
|
|
230
|
+
return {
|
|
231
|
+
success: data.success,
|
|
232
|
+
redirectUrl: data.redirect_url,
|
|
233
|
+
endUserId: data.end_user_id,
|
|
234
|
+
email: data.email,
|
|
235
|
+
waitlistPosition: data.waitlist_position
|
|
236
|
+
};
|
|
237
|
+
},
|
|
238
|
+
/**
|
|
239
|
+
* Start a Google OAuth flow for headless authentication.
|
|
240
|
+
* Returns the Google authorization URL to redirect the user to.
|
|
241
|
+
* After Google authentication, the portal handles the callback and
|
|
242
|
+
* redirects to the domain's configured redirect_url.
|
|
243
|
+
*/
|
|
244
|
+
async startGoogleOAuth() {
|
|
245
|
+
const res = await fetch(`${baseUrl}/auth/google/start`, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
credentials: "include",
|
|
248
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
249
|
+
});
|
|
250
|
+
if (!res.ok) {
|
|
251
|
+
const err = await res.json().catch(() => ({}));
|
|
252
|
+
throw new Error(err.message || `Failed to start Google OAuth: ${res.status}`);
|
|
253
|
+
}
|
|
254
|
+
const data = await res.json();
|
|
255
|
+
return {
|
|
256
|
+
authUrl: data.auth_url,
|
|
257
|
+
state: data.state
|
|
258
|
+
};
|
|
259
|
+
},
|
|
260
|
+
/**
|
|
261
|
+
* Start an X (Twitter) OAuth flow for headless authentication.
|
|
262
|
+
* Returns the X authorization URL to redirect the user to.
|
|
263
|
+
* After X authentication, the portal handles the callback and
|
|
264
|
+
* redirects to the domain's configured redirect_url.
|
|
265
|
+
*/
|
|
266
|
+
async startTwitterOAuth() {
|
|
267
|
+
const res = await fetch(`${baseUrl}/auth/twitter/start`, {
|
|
268
|
+
method: "POST",
|
|
269
|
+
credentials: "include",
|
|
270
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
271
|
+
});
|
|
272
|
+
if (!res.ok) {
|
|
273
|
+
const err = await res.json().catch(() => ({}));
|
|
274
|
+
throw new Error(err.message || `Failed to start Twitter OAuth: ${res.status}`);
|
|
275
|
+
}
|
|
276
|
+
const data = await res.json();
|
|
277
|
+
return {
|
|
278
|
+
authUrl: data.auth_url,
|
|
279
|
+
state: data.state
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
// ========================================================================
|
|
163
283
|
// Billing Methods
|
|
164
284
|
// ========================================================================
|
|
165
285
|
/**
|
|
@@ -354,9 +474,595 @@ function createReauthClient(config) {
|
|
|
354
474
|
})
|
|
355
475
|
)
|
|
356
476
|
};
|
|
477
|
+
},
|
|
478
|
+
// ========================================================================
|
|
479
|
+
// Credits Methods
|
|
480
|
+
// ========================================================================
|
|
481
|
+
/**
|
|
482
|
+
* Get the domain's credits configuration.
|
|
483
|
+
* Returns exchange rate, display settings, and purchase limits.
|
|
484
|
+
*/
|
|
485
|
+
async getCreditsConfig() {
|
|
486
|
+
const res = await fetch(`${baseUrl}/billing/credits-config`, {
|
|
487
|
+
credentials: "include",
|
|
488
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
489
|
+
});
|
|
490
|
+
if (!res.ok) {
|
|
491
|
+
if (res.status === 401) throw new Error("Not authenticated");
|
|
492
|
+
throw new Error(`Failed to get credits config: ${res.status}`);
|
|
493
|
+
}
|
|
494
|
+
const data = await res.json();
|
|
495
|
+
return {
|
|
496
|
+
creditsEnabled: data.credits_enabled,
|
|
497
|
+
creditsPerDollar: data.credits_per_dollar,
|
|
498
|
+
displayName: data.display_name,
|
|
499
|
+
displaySymbol: data.display_symbol,
|
|
500
|
+
displaySymbolPosition: data.display_symbol_position,
|
|
501
|
+
displayDecimals: data.display_decimals,
|
|
502
|
+
minPurchaseCents: data.min_purchase_cents,
|
|
503
|
+
maxPurchaseCents: data.max_purchase_cents,
|
|
504
|
+
manualTopUpAvailable: data.manual_top_up_available,
|
|
505
|
+
autoTopUpAvailable: data.auto_top_up_available,
|
|
506
|
+
overdrawEnabled: data.overdraw_enabled
|
|
507
|
+
};
|
|
508
|
+
},
|
|
509
|
+
// ========================================================================
|
|
510
|
+
// Payment Method Methods
|
|
511
|
+
// ========================================================================
|
|
512
|
+
/**
|
|
513
|
+
* List the current user's stored payment methods.
|
|
514
|
+
* Sorted by priority (lower number = higher priority).
|
|
515
|
+
*/
|
|
516
|
+
async getPaymentMethods() {
|
|
517
|
+
const res = await fetch(`${baseUrl}/billing/payment-methods`, {
|
|
518
|
+
credentials: "include",
|
|
519
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
520
|
+
});
|
|
521
|
+
if (!res.ok) {
|
|
522
|
+
if (res.status === 401) throw new Error("Not authenticated");
|
|
523
|
+
throw new Error(`Failed to get payment methods: ${res.status}`);
|
|
524
|
+
}
|
|
525
|
+
const data = await res.json();
|
|
526
|
+
return data.map(
|
|
527
|
+
(pm) => ({
|
|
528
|
+
id: pm.id,
|
|
529
|
+
provider: pm.provider,
|
|
530
|
+
methodType: pm.method_type,
|
|
531
|
+
cardBrand: pm.card_brand,
|
|
532
|
+
cardLast4: pm.card_last4,
|
|
533
|
+
cardExpMonth: pm.card_exp_month,
|
|
534
|
+
cardExpYear: pm.card_exp_year,
|
|
535
|
+
priority: pm.priority,
|
|
536
|
+
createdAt: pm.created_at
|
|
537
|
+
})
|
|
538
|
+
);
|
|
539
|
+
},
|
|
540
|
+
/**
|
|
541
|
+
* Create a Stripe SetupIntent for adding a new payment method.
|
|
542
|
+
* Use the returned clientSecret with Stripe.js `confirmCardSetup()`.
|
|
543
|
+
*/
|
|
544
|
+
async createSetupIntent() {
|
|
545
|
+
const res = await fetch(`${baseUrl}/billing/setup-intent`, {
|
|
546
|
+
method: "POST",
|
|
547
|
+
credentials: "include",
|
|
548
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
549
|
+
});
|
|
550
|
+
if (!res.ok) {
|
|
551
|
+
if (res.status === 401) throw new Error("Not authenticated");
|
|
552
|
+
throw new Error(`Failed to create setup intent: ${res.status}`);
|
|
553
|
+
}
|
|
554
|
+
const data = await res.json();
|
|
555
|
+
return {
|
|
556
|
+
clientSecret: data.client_secret,
|
|
557
|
+
setupIntentId: data.setup_intent_id
|
|
558
|
+
};
|
|
559
|
+
},
|
|
560
|
+
/**
|
|
561
|
+
* Delete a stored payment method.
|
|
562
|
+
* @param id UUID of the payment method to delete
|
|
563
|
+
*/
|
|
564
|
+
async deletePaymentMethod(id) {
|
|
565
|
+
const res = await fetch(`${baseUrl}/billing/payment-methods/${id}`, {
|
|
566
|
+
method: "DELETE",
|
|
567
|
+
credentials: "include",
|
|
568
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
569
|
+
});
|
|
570
|
+
if (!res.ok) {
|
|
571
|
+
if (res.status === 401) throw new Error("Not authenticated");
|
|
572
|
+
if (res.status === 404) throw new Error("Payment method not found");
|
|
573
|
+
throw new Error(`Failed to delete payment method: ${res.status}`);
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
/**
|
|
577
|
+
* Reorder payment method priorities.
|
|
578
|
+
* @param paymentMethodIds Array of payment method UUIDs in desired priority order
|
|
579
|
+
*/
|
|
580
|
+
async reorderPaymentMethods(paymentMethodIds) {
|
|
581
|
+
const res = await fetch(`${baseUrl}/billing/payment-methods/reorder`, {
|
|
582
|
+
method: "PUT",
|
|
583
|
+
headers: { "Content-Type": "application/json" },
|
|
584
|
+
credentials: "include",
|
|
585
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
586
|
+
body: JSON.stringify({ payment_method_ids: paymentMethodIds })
|
|
587
|
+
});
|
|
588
|
+
if (!res.ok) {
|
|
589
|
+
if (res.status === 401) throw new Error("Not authenticated");
|
|
590
|
+
throw new Error(`Failed to reorder payment methods: ${res.status}`);
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
/**
|
|
594
|
+
* Purchase credits using a stored payment method.
|
|
595
|
+
* @param opts Purchase options (amount, payment method, idempotency key)
|
|
596
|
+
*/
|
|
597
|
+
async purchaseCredits(opts) {
|
|
598
|
+
const res = await fetch(`${baseUrl}/billing/credits/purchase`, {
|
|
599
|
+
method: "POST",
|
|
600
|
+
headers: { "Content-Type": "application/json" },
|
|
601
|
+
credentials: "include",
|
|
602
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
603
|
+
body: JSON.stringify({
|
|
604
|
+
amount_cents: opts.amountCents,
|
|
605
|
+
payment_method_id: opts.paymentMethodId,
|
|
606
|
+
idempotency_key: opts.idempotencyKey
|
|
607
|
+
})
|
|
608
|
+
});
|
|
609
|
+
if (!res.ok) {
|
|
610
|
+
if (res.status === 401) throw new Error("Not authenticated");
|
|
611
|
+
const err = await res.json().catch(() => ({}));
|
|
612
|
+
throw new Error(err.message || `Failed to purchase credits: ${res.status}`);
|
|
613
|
+
}
|
|
614
|
+
const data = await res.json();
|
|
615
|
+
return {
|
|
616
|
+
creditsPurchased: data.credits_purchased,
|
|
617
|
+
newBalance: data.new_balance,
|
|
618
|
+
paymentIntentId: data.payment_intent_id
|
|
619
|
+
};
|
|
620
|
+
},
|
|
621
|
+
// ========================================================================
|
|
622
|
+
// Auto Top-Up Methods
|
|
623
|
+
// ========================================================================
|
|
624
|
+
/**
|
|
625
|
+
* Get the current user's auto top-up configuration and status.
|
|
626
|
+
*/
|
|
627
|
+
async getAutoTopUpStatus() {
|
|
628
|
+
const res = await fetch(`${baseUrl}/billing/auto-top-up`, {
|
|
629
|
+
credentials: "include",
|
|
630
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
631
|
+
});
|
|
632
|
+
if (!res.ok) {
|
|
633
|
+
if (res.status === 401) throw new Error("Not authenticated");
|
|
634
|
+
throw new Error(`Failed to get auto top-up status: ${res.status}`);
|
|
635
|
+
}
|
|
636
|
+
const data = await res.json();
|
|
637
|
+
return {
|
|
638
|
+
enabled: data.enabled,
|
|
639
|
+
thresholdCents: data.threshold_cents,
|
|
640
|
+
purchaseAmountCents: data.purchase_amount_cents,
|
|
641
|
+
status: data.status,
|
|
642
|
+
lastFailureReason: data.last_failure_reason,
|
|
643
|
+
retriesRemaining: data.retries_remaining,
|
|
644
|
+
nextRetryAt: data.next_retry_at
|
|
645
|
+
};
|
|
646
|
+
},
|
|
647
|
+
/**
|
|
648
|
+
* Update the current user's auto top-up configuration.
|
|
649
|
+
* @param opts New auto top-up settings
|
|
650
|
+
*/
|
|
651
|
+
async updateAutoTopUp(opts) {
|
|
652
|
+
const res = await fetch(`${baseUrl}/billing/auto-top-up`, {
|
|
653
|
+
method: "PUT",
|
|
654
|
+
headers: { "Content-Type": "application/json" },
|
|
655
|
+
credentials: "include",
|
|
656
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
657
|
+
body: JSON.stringify({
|
|
658
|
+
enabled: opts.enabled,
|
|
659
|
+
threshold_cents: opts.thresholdCents,
|
|
660
|
+
purchase_amount_cents: opts.purchaseAmountCents
|
|
661
|
+
})
|
|
662
|
+
});
|
|
663
|
+
if (!res.ok) {
|
|
664
|
+
if (res.status === 401) throw new Error("Not authenticated");
|
|
665
|
+
const err = await res.json().catch(() => ({}));
|
|
666
|
+
throw new Error(err.message || `Failed to update auto top-up: ${res.status}`);
|
|
667
|
+
}
|
|
668
|
+
const data = await res.json();
|
|
669
|
+
return {
|
|
670
|
+
enabled: data.enabled,
|
|
671
|
+
thresholdCents: data.threshold_cents,
|
|
672
|
+
purchaseAmountCents: data.purchase_amount_cents,
|
|
673
|
+
status: data.status,
|
|
674
|
+
lastFailureReason: data.last_failure_reason,
|
|
675
|
+
retriesRemaining: data.retries_remaining,
|
|
676
|
+
nextRetryAt: data.next_retry_at
|
|
677
|
+
};
|
|
678
|
+
},
|
|
679
|
+
// ========================================================================
|
|
680
|
+
// Organization Methods
|
|
681
|
+
// ========================================================================
|
|
682
|
+
/**
|
|
683
|
+
* Create a new organization.
|
|
684
|
+
* @param name The organization name
|
|
685
|
+
* @returns The created organization
|
|
686
|
+
*/
|
|
687
|
+
async createOrg(name) {
|
|
688
|
+
const res = await fetch(`${baseUrl}/auth/orgs`, {
|
|
689
|
+
method: "POST",
|
|
690
|
+
headers: { "Content-Type": "application/json" },
|
|
691
|
+
credentials: "include",
|
|
692
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
693
|
+
body: JSON.stringify({ name })
|
|
694
|
+
});
|
|
695
|
+
if (!res.ok) {
|
|
696
|
+
const err = await res.json().catch(() => ({}));
|
|
697
|
+
throw new Error(err.message || `Failed to create org: ${res.status}`);
|
|
698
|
+
}
|
|
699
|
+
const data = await res.json();
|
|
700
|
+
return transformOrg(data);
|
|
701
|
+
},
|
|
702
|
+
/**
|
|
703
|
+
* List all organizations the current user belongs to.
|
|
704
|
+
*/
|
|
705
|
+
async listOrgs() {
|
|
706
|
+
const res = await fetch(`${baseUrl}/auth/orgs`, {
|
|
707
|
+
credentials: "include",
|
|
708
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
709
|
+
});
|
|
710
|
+
if (!res.ok) {
|
|
711
|
+
throw new Error(`Failed to list orgs: ${res.status}`);
|
|
712
|
+
}
|
|
713
|
+
const data = await res.json();
|
|
714
|
+
return data.map(transformOrg);
|
|
715
|
+
},
|
|
716
|
+
/**
|
|
717
|
+
* Get organization details.
|
|
718
|
+
* @param orgId The organization ID
|
|
719
|
+
*/
|
|
720
|
+
async getOrg(orgId) {
|
|
721
|
+
const res = await fetch(`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}`, {
|
|
722
|
+
credentials: "include",
|
|
723
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
724
|
+
});
|
|
725
|
+
if (!res.ok) {
|
|
726
|
+
if (res.status === 404) throw new Error("Organization not found");
|
|
727
|
+
throw new Error(`Failed to get org: ${res.status}`);
|
|
728
|
+
}
|
|
729
|
+
const data = await res.json();
|
|
730
|
+
return transformOrg(data);
|
|
731
|
+
},
|
|
732
|
+
/**
|
|
733
|
+
* Update an organization's name. Requires owner role.
|
|
734
|
+
* @param orgId The organization ID
|
|
735
|
+
* @param name The new name
|
|
736
|
+
*/
|
|
737
|
+
async updateOrg(orgId, name) {
|
|
738
|
+
const res = await fetch(`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}`, {
|
|
739
|
+
method: "PATCH",
|
|
740
|
+
headers: { "Content-Type": "application/json" },
|
|
741
|
+
credentials: "include",
|
|
742
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
743
|
+
body: JSON.stringify({ name })
|
|
744
|
+
});
|
|
745
|
+
if (!res.ok) {
|
|
746
|
+
const err = await res.json().catch(() => ({}));
|
|
747
|
+
throw new Error(err.message || `Failed to update org: ${res.status}`);
|
|
748
|
+
}
|
|
749
|
+
const data = await res.json();
|
|
750
|
+
return transformOrg(data);
|
|
751
|
+
},
|
|
752
|
+
/**
|
|
753
|
+
* Delete an organization. Requires owner role.
|
|
754
|
+
* @param orgId The organization ID
|
|
755
|
+
*/
|
|
756
|
+
async deleteOrg(orgId) {
|
|
757
|
+
const res = await fetch(`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}`, {
|
|
758
|
+
method: "DELETE",
|
|
759
|
+
credentials: "include",
|
|
760
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
761
|
+
});
|
|
762
|
+
if (!res.ok) {
|
|
763
|
+
throw new Error(`Failed to delete org: ${res.status}`);
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
// ========================================================================
|
|
767
|
+
// Organization Member Methods
|
|
768
|
+
// ========================================================================
|
|
769
|
+
/**
|
|
770
|
+
* List members of an organization. Requires membership.
|
|
771
|
+
* @param orgId The organization ID
|
|
772
|
+
*/
|
|
773
|
+
async listOrgMembers(orgId) {
|
|
774
|
+
const res = await fetch(`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/members`, {
|
|
775
|
+
credentials: "include",
|
|
776
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
777
|
+
});
|
|
778
|
+
if (!res.ok) {
|
|
779
|
+
throw new Error(`Failed to list org members: ${res.status}`);
|
|
780
|
+
}
|
|
781
|
+
const data = await res.json();
|
|
782
|
+
return data.map(transformOrgMember);
|
|
783
|
+
},
|
|
784
|
+
/**
|
|
785
|
+
* Remove a member from an organization. Requires owner role.
|
|
786
|
+
* @param orgId The organization ID
|
|
787
|
+
* @param userId The user ID to remove
|
|
788
|
+
*/
|
|
789
|
+
async removeOrgMember(orgId, userId) {
|
|
790
|
+
const res = await fetch(
|
|
791
|
+
`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`,
|
|
792
|
+
{
|
|
793
|
+
method: "DELETE",
|
|
794
|
+
credentials: "include",
|
|
795
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
796
|
+
}
|
|
797
|
+
);
|
|
798
|
+
if (!res.ok) {
|
|
799
|
+
throw new Error(`Failed to remove org member: ${res.status}`);
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
/**
|
|
803
|
+
* Update a member's role. Requires owner role.
|
|
804
|
+
* @param orgId The organization ID
|
|
805
|
+
* @param userId The user ID to update
|
|
806
|
+
* @param role The new role
|
|
807
|
+
*/
|
|
808
|
+
async updateMemberRole(orgId, userId, role) {
|
|
809
|
+
const res = await fetch(
|
|
810
|
+
`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/members/${encodeURIComponent(userId)}`,
|
|
811
|
+
{
|
|
812
|
+
method: "PATCH",
|
|
813
|
+
headers: { "Content-Type": "application/json" },
|
|
814
|
+
credentials: "include",
|
|
815
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
816
|
+
body: JSON.stringify({ role })
|
|
817
|
+
}
|
|
818
|
+
);
|
|
819
|
+
if (!res.ok) {
|
|
820
|
+
const err = await res.json().catch(() => ({}));
|
|
821
|
+
throw new Error(err.message || `Failed to update member role: ${res.status}`);
|
|
822
|
+
}
|
|
823
|
+
const data = await res.json();
|
|
824
|
+
return transformOrgMember(data);
|
|
825
|
+
},
|
|
826
|
+
// ========================================================================
|
|
827
|
+
// Organization Context Methods
|
|
828
|
+
// ========================================================================
|
|
829
|
+
/**
|
|
830
|
+
* Switch the active organization. Re-issues session tokens with new org context.
|
|
831
|
+
* @param orgId The organization ID to switch to
|
|
832
|
+
* @returns The new active org context with subscription info
|
|
833
|
+
*/
|
|
834
|
+
async switchOrg(orgId) {
|
|
835
|
+
const res = await fetch(`${baseUrl}/auth/switch-org`, {
|
|
836
|
+
method: "POST",
|
|
837
|
+
headers: { "Content-Type": "application/json" },
|
|
838
|
+
credentials: "include",
|
|
839
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
840
|
+
body: JSON.stringify({ org_id: orgId })
|
|
841
|
+
});
|
|
842
|
+
if (!res.ok) {
|
|
843
|
+
const err = await res.json().catch(() => ({}));
|
|
844
|
+
throw new Error(err.message || `Failed to switch org: ${res.status}`);
|
|
845
|
+
}
|
|
846
|
+
const data = await res.json();
|
|
847
|
+
return {
|
|
848
|
+
activeOrgId: data.active_org_id,
|
|
849
|
+
orgRole: data.org_role,
|
|
850
|
+
subscription: {
|
|
851
|
+
status: data.subscription.status,
|
|
852
|
+
planCode: data.subscription.plan_code,
|
|
853
|
+
planName: data.subscription.plan_name,
|
|
854
|
+
currentPeriodEnd: data.subscription.current_period_end,
|
|
855
|
+
cancelAtPeriodEnd: data.subscription.cancel_at_period_end,
|
|
856
|
+
trialEndsAt: data.subscription.trial_ends_at
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
},
|
|
860
|
+
/**
|
|
861
|
+
* Get the active organization from the current session.
|
|
862
|
+
* @returns Active org ID and role, or null if no active org
|
|
863
|
+
*/
|
|
864
|
+
async getActiveOrg() {
|
|
865
|
+
const session = await this.getSession();
|
|
866
|
+
if (!session.valid || !session.active_org_id || !session.org_role) {
|
|
867
|
+
return null;
|
|
868
|
+
}
|
|
869
|
+
return {
|
|
870
|
+
id: session.active_org_id,
|
|
871
|
+
role: session.org_role
|
|
872
|
+
};
|
|
873
|
+
},
|
|
874
|
+
// ========================================================================
|
|
875
|
+
// Invitation Methods
|
|
876
|
+
// ========================================================================
|
|
877
|
+
/**
|
|
878
|
+
* Send an email invite to join an organization. Requires owner role.
|
|
879
|
+
* @param orgId The organization ID
|
|
880
|
+
* @param email The email address to invite
|
|
881
|
+
* @param role Optional role (defaults to "member")
|
|
882
|
+
*/
|
|
883
|
+
async inviteToOrg(orgId, email, role) {
|
|
884
|
+
const res = await fetch(`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/invites`, {
|
|
885
|
+
method: "POST",
|
|
886
|
+
headers: { "Content-Type": "application/json" },
|
|
887
|
+
credentials: "include",
|
|
888
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
889
|
+
body: JSON.stringify({ email, role: role ?? "member" })
|
|
890
|
+
});
|
|
891
|
+
if (!res.ok) {
|
|
892
|
+
const err = await res.json().catch(() => ({}));
|
|
893
|
+
throw new Error(err.message || `Failed to send invite: ${res.status}`);
|
|
894
|
+
}
|
|
895
|
+
const data = await res.json();
|
|
896
|
+
return transformInvitation(data);
|
|
897
|
+
},
|
|
898
|
+
/**
|
|
899
|
+
* List pending invites for an organization. Requires owner role.
|
|
900
|
+
* @param orgId The organization ID
|
|
901
|
+
*/
|
|
902
|
+
async listPendingInvites(orgId) {
|
|
903
|
+
const res = await fetch(`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/invites`, {
|
|
904
|
+
credentials: "include",
|
|
905
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
906
|
+
});
|
|
907
|
+
if (!res.ok) {
|
|
908
|
+
throw new Error(`Failed to list invites: ${res.status}`);
|
|
909
|
+
}
|
|
910
|
+
const data = await res.json();
|
|
911
|
+
return data.map(transformInvitation);
|
|
912
|
+
},
|
|
913
|
+
/**
|
|
914
|
+
* Revoke a pending invite. Requires owner role.
|
|
915
|
+
* @param orgId The organization ID
|
|
916
|
+
* @param inviteId The invite ID to revoke
|
|
917
|
+
*/
|
|
918
|
+
async revokeInvite(orgId, inviteId) {
|
|
919
|
+
const res = await fetch(
|
|
920
|
+
`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/invites/${encodeURIComponent(inviteId)}`,
|
|
921
|
+
{
|
|
922
|
+
method: "DELETE",
|
|
923
|
+
credentials: "include",
|
|
924
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
925
|
+
}
|
|
926
|
+
);
|
|
927
|
+
if (!res.ok) {
|
|
928
|
+
throw new Error(`Failed to revoke invite: ${res.status}`);
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
/**
|
|
932
|
+
* Accept an email invite using the invite token.
|
|
933
|
+
* @param token The invite token from the email
|
|
934
|
+
* @returns The org ID and role assigned
|
|
935
|
+
*/
|
|
936
|
+
async acceptInvite(token) {
|
|
937
|
+
const res = await fetch(
|
|
938
|
+
`${baseUrl}/auth/orgs/invites/${encodeURIComponent(token)}/accept`,
|
|
939
|
+
{
|
|
940
|
+
method: "POST",
|
|
941
|
+
credentials: "include",
|
|
942
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
943
|
+
}
|
|
944
|
+
);
|
|
945
|
+
if (!res.ok) {
|
|
946
|
+
const err = await res.json().catch(() => ({}));
|
|
947
|
+
throw new Error(err.message || `Failed to accept invite: ${res.status}`);
|
|
948
|
+
}
|
|
949
|
+
const data = await res.json();
|
|
950
|
+
return {
|
|
951
|
+
orgId: data.org_id,
|
|
952
|
+
role: data.role
|
|
953
|
+
};
|
|
954
|
+
},
|
|
955
|
+
/**
|
|
956
|
+
* Create a shareable invite link for an organization. Requires owner role.
|
|
957
|
+
* @param orgId The organization ID
|
|
958
|
+
* @param role Optional role for joiners (defaults to "member")
|
|
959
|
+
*/
|
|
960
|
+
async createInviteLink(orgId, role) {
|
|
961
|
+
const res = await fetch(`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/invite-links`, {
|
|
962
|
+
method: "POST",
|
|
963
|
+
headers: { "Content-Type": "application/json" },
|
|
964
|
+
credentials: "include",
|
|
965
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
966
|
+
body: JSON.stringify({ role: role ?? "member" })
|
|
967
|
+
});
|
|
968
|
+
if (!res.ok) {
|
|
969
|
+
const err = await res.json().catch(() => ({}));
|
|
970
|
+
throw new Error(err.message || `Failed to create invite link: ${res.status}`);
|
|
971
|
+
}
|
|
972
|
+
const data = await res.json();
|
|
973
|
+
return transformInviteLink(data);
|
|
974
|
+
},
|
|
975
|
+
/**
|
|
976
|
+
* List invite links for an organization. Requires owner role.
|
|
977
|
+
* @param orgId The organization ID
|
|
978
|
+
*/
|
|
979
|
+
async listInviteLinks(orgId) {
|
|
980
|
+
const res = await fetch(`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/invite-links`, {
|
|
981
|
+
credentials: "include",
|
|
982
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
983
|
+
});
|
|
984
|
+
if (!res.ok) {
|
|
985
|
+
throw new Error(`Failed to list invite links: ${res.status}`);
|
|
986
|
+
}
|
|
987
|
+
const data = await res.json();
|
|
988
|
+
return data.map(transformInviteLink);
|
|
989
|
+
},
|
|
990
|
+
/**
|
|
991
|
+
* Revoke an invite link. Requires owner role.
|
|
992
|
+
* @param orgId The organization ID
|
|
993
|
+
* @param linkId The invite link ID to revoke
|
|
994
|
+
*/
|
|
995
|
+
async revokeInviteLink(orgId, linkId) {
|
|
996
|
+
const res = await fetch(
|
|
997
|
+
`${baseUrl}/auth/orgs/${encodeURIComponent(orgId)}/invite-links/${encodeURIComponent(linkId)}`,
|
|
998
|
+
{
|
|
999
|
+
method: "DELETE",
|
|
1000
|
+
credentials: "include",
|
|
1001
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1002
|
+
}
|
|
1003
|
+
);
|
|
1004
|
+
if (!res.ok) {
|
|
1005
|
+
throw new Error(`Failed to revoke invite link: ${res.status}`);
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
/**
|
|
1009
|
+
* Join an organization via an invite link token.
|
|
1010
|
+
* @param token The invite link token
|
|
1011
|
+
*/
|
|
1012
|
+
async joinViaLink(token) {
|
|
1013
|
+
const res = await fetch(`${baseUrl}/auth/orgs/join/${encodeURIComponent(token)}`, {
|
|
1014
|
+
method: "POST",
|
|
1015
|
+
credentials: "include",
|
|
1016
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
1017
|
+
});
|
|
1018
|
+
if (!res.ok) {
|
|
1019
|
+
const err = await res.json().catch(() => ({}));
|
|
1020
|
+
throw new Error(err.message || `Failed to join via link: ${res.status}`);
|
|
1021
|
+
}
|
|
357
1022
|
}
|
|
358
1023
|
};
|
|
359
1024
|
}
|
|
1025
|
+
function transformOrg(data) {
|
|
1026
|
+
return {
|
|
1027
|
+
id: data.id,
|
|
1028
|
+
name: data.name,
|
|
1029
|
+
isPersonal: data.is_personal,
|
|
1030
|
+
createdAt: data.created_at
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
function transformOrgMember(data) {
|
|
1034
|
+
return {
|
|
1035
|
+
id: data.id,
|
|
1036
|
+
orgId: data.org_id,
|
|
1037
|
+
endUserId: data.end_user_id,
|
|
1038
|
+
role: data.role,
|
|
1039
|
+
joinedAt: data.joined_at
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function transformInvitation(data) {
|
|
1043
|
+
return {
|
|
1044
|
+
id: data.id,
|
|
1045
|
+
orgId: data.org_id,
|
|
1046
|
+
email: data.email,
|
|
1047
|
+
token: data.token,
|
|
1048
|
+
role: data.role,
|
|
1049
|
+
status: data.status,
|
|
1050
|
+
expiresAt: data.expires_at,
|
|
1051
|
+
acceptedAt: data.accepted_at,
|
|
1052
|
+
createdAt: data.created_at
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
function transformInviteLink(data) {
|
|
1056
|
+
return {
|
|
1057
|
+
id: data.id,
|
|
1058
|
+
orgId: data.org_id,
|
|
1059
|
+
token: data.token,
|
|
1060
|
+
role: data.role,
|
|
1061
|
+
isActive: data.is_active,
|
|
1062
|
+
expiresAt: data.expires_at,
|
|
1063
|
+
createdAt: data.created_at
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
360
1066
|
|
|
361
1067
|
// src/errors.ts
|
|
362
1068
|
var ReauthErrorCode = /* @__PURE__ */ ((ReauthErrorCode2) => {
|