@hyperyai/sdk 1.0.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.
Files changed (102) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +391 -0
  3. package/dist/components/AuthButton.d.ts +51 -0
  4. package/dist/components/AuthButton.js +60 -0
  5. package/dist/components/AuthModal.d.ts +20 -0
  6. package/dist/components/AuthModal.js +62 -0
  7. package/dist/components/BuyButton.d.ts +47 -0
  8. package/dist/components/BuyButton.js +74 -0
  9. package/dist/components/ErrorBoundary.d.ts +13 -0
  10. package/dist/components/ErrorBoundary.js +24 -0
  11. package/dist/components/HyperyModals.d.ts +30 -0
  12. package/dist/components/HyperyModals.js +30 -0
  13. package/dist/components/InsufficientCreditsAlert.d.ts +11 -0
  14. package/dist/components/InsufficientCreditsAlert.js +12 -0
  15. package/dist/components/ModernAuthForm.d.ts +52 -0
  16. package/dist/components/ModernAuthForm.js +83 -0
  17. package/dist/components/RestrictionModal.d.ts +43 -0
  18. package/dist/components/RestrictionModal.js +167 -0
  19. package/dist/components/SignIn.d.ts +26 -0
  20. package/dist/components/SignIn.js +47 -0
  21. package/dist/components/SignInForm.d.ts +41 -0
  22. package/dist/components/SignInForm.js +86 -0
  23. package/dist/components/SignUp.d.ts +26 -0
  24. package/dist/components/SignUp.js +52 -0
  25. package/dist/components/SpendingLimitAlert.d.ts +12 -0
  26. package/dist/components/SpendingLimitAlert.js +14 -0
  27. package/dist/components/UserButton.d.ts +26 -0
  28. package/dist/components/UserButton.js +35 -0
  29. package/dist/components/UserProfile.d.ts +22 -0
  30. package/dist/components/UserProfile.js +26 -0
  31. package/dist/components/WorkspaceSwitcher.d.ts +29 -0
  32. package/dist/components/WorkspaceSwitcher.js +66 -0
  33. package/dist/components/control.d.ts +64 -0
  34. package/dist/components/control.js +89 -0
  35. package/dist/components/ui/dialog.d.ts +19 -0
  36. package/dist/components/ui/dialog.js +53 -0
  37. package/dist/hooks/index.d.ts +22 -0
  38. package/dist/hooks/index.js +23 -0
  39. package/dist/hooks/useBuyerWallet.d.ts +37 -0
  40. package/dist/hooks/useBuyerWallet.js +66 -0
  41. package/dist/hooks/useCheckout.d.ts +27 -0
  42. package/dist/hooks/useCheckout.js +246 -0
  43. package/dist/hooks/useError.d.ts +19 -0
  44. package/dist/hooks/useError.js +36 -0
  45. package/dist/hooks/useMarketplace.d.ts +50 -0
  46. package/dist/hooks/useMarketplace.js +69 -0
  47. package/dist/hooks/useMemberships.d.ts +71 -0
  48. package/dist/hooks/useMemberships.js +138 -0
  49. package/dist/hooks/useWallet.d.ts +62 -0
  50. package/dist/hooks/useWallet.js +123 -0
  51. package/dist/index.d.ts +55 -0
  52. package/dist/index.js +52 -0
  53. package/dist/lib/context.d.ts +50 -0
  54. package/dist/lib/context.js +409 -0
  55. package/dist/lib/oauth.d.ts +49 -0
  56. package/dist/lib/oauth.js +149 -0
  57. package/dist/lib/parse-error.d.ts +45 -0
  58. package/dist/lib/parse-error.js +185 -0
  59. package/dist/lib/parse-stream.d.ts +43 -0
  60. package/dist/lib/parse-stream.js +89 -0
  61. package/dist/lib/popup.d.ts +42 -0
  62. package/dist/lib/popup.js +80 -0
  63. package/dist/lib/storage.d.ts +43 -0
  64. package/dist/lib/storage.js +125 -0
  65. package/dist/lib/utils.d.ts +8 -0
  66. package/dist/lib/utils.js +11 -0
  67. package/dist/types/index.d.ts +191 -0
  68. package/dist/types/index.js +4 -0
  69. package/package.json +78 -0
  70. package/src/components/AuthButton.tsx +123 -0
  71. package/src/components/AuthModal.tsx +240 -0
  72. package/src/components/BuyButton.tsx +150 -0
  73. package/src/components/ErrorBoundary.tsx +97 -0
  74. package/src/components/HyperyModals.tsx +83 -0
  75. package/src/components/InsufficientCreditsAlert.tsx +73 -0
  76. package/src/components/ModernAuthForm.tsx +322 -0
  77. package/src/components/RestrictionModal.tsx +373 -0
  78. package/src/components/SignIn.tsx +84 -0
  79. package/src/components/SignInForm.tsx +256 -0
  80. package/src/components/SignUp.tsx +86 -0
  81. package/src/components/SpendingLimitAlert.tsx +91 -0
  82. package/src/components/UserButton.tsx +106 -0
  83. package/src/components/UserProfile.tsx +106 -0
  84. package/src/components/WorkspaceSwitcher.tsx +199 -0
  85. package/src/components/control.tsx +133 -0
  86. package/src/components/ui/dialog.tsx +151 -0
  87. package/src/hooks/index.ts +65 -0
  88. package/src/hooks/useBuyerWallet.ts +117 -0
  89. package/src/hooks/useCheckout.ts +312 -0
  90. package/src/hooks/useError.ts +60 -0
  91. package/src/hooks/useMarketplace.ts +129 -0
  92. package/src/hooks/useMemberships.ts +203 -0
  93. package/src/hooks/useWallet.ts +170 -0
  94. package/src/index.ts +147 -0
  95. package/src/lib/context.tsx +478 -0
  96. package/src/lib/oauth.ts +215 -0
  97. package/src/lib/parse-error.ts +224 -0
  98. package/src/lib/parse-stream.ts +121 -0
  99. package/src/lib/popup.ts +98 -0
  100. package/src/lib/storage.ts +140 -0
  101. package/src/lib/utils.ts +14 -0
  102. package/src/types/index.ts +216 -0
@@ -0,0 +1,185 @@
1
+ const SPENDING_LIMIT = 'SPENDING_LIMIT_EXCEEDED';
2
+ const INSUFFICIENT_CREDITS = 'INSUFFICIENT_CREDITS';
3
+ const PAYMENT_METHOD_REQUIRED = 'PAYMENT_METHOD_REQUIRED';
4
+ const PAYMENT_DECLINED = 'PAYMENT_DECLINED';
5
+ const UNAUTHENTICATED = 'UNAUTHENTICATED';
6
+ const PERMISSION_DENIED = 'PERMISSION_DENIED';
7
+ const INSUFFICIENT_SCOPE = 'INSUFFICIENT_SCOPE';
8
+ const RATE_LIMITED = 'RATE_LIMITED';
9
+ /**
10
+ * Extract the inner error object from any of the shapes the gateway / a caller
11
+ * may hand us:
12
+ * - the raw server envelope: `{ error: { code, message, type, ... } }`
13
+ * - an already-unwrapped error object: `{ code, message, type, ... }`
14
+ * Returns undefined if neither shape carries a `code`.
15
+ */
16
+ function unwrap(error) {
17
+ if (error && typeof error === 'object') {
18
+ if (error.error && typeof error.error === 'object' && 'code' in error.error) {
19
+ return error.error;
20
+ }
21
+ if (typeof error.code === 'string') {
22
+ return error;
23
+ }
24
+ }
25
+ return undefined;
26
+ }
27
+ /** Pull an HTTP status off a Response-like / thrown-error object, if present. */
28
+ function statusOf(error) {
29
+ const s = error?.status ?? error?.statusCode ?? error?.response?.status;
30
+ return typeof s === 'number' ? s : undefined;
31
+ }
32
+ /**
33
+ * Parse an error (a parsed JSON body, a thrown error, or a Response-like object)
34
+ * from the Hypery API into a normalized, classified shape. Tolerant of both the
35
+ * `{ error: { code } }` envelope and an already-unwrapped object, and falls back
36
+ * to the HTTP status when no recognized `code` is present.
37
+ */
38
+ export function parseError(error) {
39
+ const errorData = unwrap(error);
40
+ const status = statusOf(error);
41
+ if (errorData) {
42
+ const code = errorData.code;
43
+ return {
44
+ code,
45
+ message: errorData.message,
46
+ type: errorData.type,
47
+ status,
48
+ isSpendingLimit: code === SPENDING_LIMIT,
49
+ isInsufficientCredits: code === INSUFFICIENT_CREDITS,
50
+ isPaymentMethodRequired: code === PAYMENT_METHOD_REQUIRED,
51
+ isPaymentDeclined: code === PAYMENT_DECLINED,
52
+ isAuth: code === UNAUTHENTICATED || errorData.type === 'authentication_error',
53
+ isPermissionDenied: code === PERMISSION_DENIED ||
54
+ code === INSUFFICIENT_SCOPE ||
55
+ errorData.type === 'permission_error' ||
56
+ errorData.type === 'authorization_error',
57
+ isRateLimit: code === RATE_LIMITED || errorData.type === 'rate_limit_error',
58
+ data: errorData,
59
+ };
60
+ }
61
+ // No recognized code — classify from HTTP status so a payment/auth modal can
62
+ // still open for an endpoint that returns a non-canonical body.
63
+ if (status === 402) {
64
+ return statusFallback(INSUFFICIENT_CREDITS, 'Insufficient credits.', status, {
65
+ isInsufficientCredits: true,
66
+ });
67
+ }
68
+ if (status === 401) {
69
+ return statusFallback(UNAUTHENTICATED, 'Authentication required.', status, { isAuth: true });
70
+ }
71
+ if (status === 403) {
72
+ return statusFallback(PERMISSION_DENIED, 'You do not have permission to perform this action.', status, {
73
+ isPermissionDenied: true,
74
+ });
75
+ }
76
+ if (status === 429) {
77
+ return statusFallback(RATE_LIMITED, 'Rate limit exceeded. Please retry shortly.', status, {
78
+ isRateLimit: true,
79
+ });
80
+ }
81
+ // Handle generic errors
82
+ const message = error?.message || 'An unknown error occurred';
83
+ return {
84
+ code: 'UNKNOWN_ERROR',
85
+ message,
86
+ status,
87
+ isSpendingLimit: false,
88
+ isInsufficientCredits: false,
89
+ isPaymentMethodRequired: false,
90
+ isPaymentDeclined: false,
91
+ isAuth: false,
92
+ isPermissionDenied: false,
93
+ isRateLimit: false,
94
+ data: {
95
+ code: 'UNKNOWN_ERROR',
96
+ message,
97
+ },
98
+ };
99
+ }
100
+ function statusFallback(code, message, status, flags) {
101
+ return {
102
+ code,
103
+ message,
104
+ status,
105
+ isSpendingLimit: false,
106
+ isInsufficientCredits: false,
107
+ isPaymentMethodRequired: false,
108
+ isPaymentDeclined: false,
109
+ isAuth: false,
110
+ isPermissionDenied: false,
111
+ isRateLimit: false,
112
+ ...flags,
113
+ data: { code, message },
114
+ };
115
+ }
116
+ /**
117
+ * Check if an error is a spending limit error
118
+ */
119
+ export function isSpendingLimitError(error) {
120
+ return parseError(error).isSpendingLimit;
121
+ }
122
+ /**
123
+ * Check if an error is an insufficient credits error
124
+ */
125
+ export function isInsufficientCreditsError(error) {
126
+ return parseError(error).isInsufficientCredits;
127
+ }
128
+ /**
129
+ * Check if an error means the user must add a payment method (metered billing).
130
+ */
131
+ export function isPaymentMethodRequiredError(error) {
132
+ return parseError(error).isPaymentMethodRequired;
133
+ }
134
+ /**
135
+ * Check if an error means the user's card was declined (metered billing).
136
+ */
137
+ export function isPaymentDeclinedError(error) {
138
+ return parseError(error).isPaymentDeclined;
139
+ }
140
+ /**
141
+ * Check if an error is an authentication failure (re-auth / open the auth modal).
142
+ */
143
+ export function isAuthError(error) {
144
+ return parseError(error).isAuth;
145
+ }
146
+ /**
147
+ * Check if an error is a scope/permission denial (403) — NOT an auth failure,
148
+ * so it should not trigger re-auth.
149
+ */
150
+ export function isPermissionDeniedError(error) {
151
+ return parseError(error).isPermissionDenied;
152
+ }
153
+ /**
154
+ * Check if an error is a rate-limit denial (429 / RATE_LIMITED).
155
+ */
156
+ export function isRateLimitError(error) {
157
+ return parseError(error).isRateLimit;
158
+ }
159
+ /**
160
+ * Any billing restriction the funds widget should surface a CTA for.
161
+ */
162
+ export function isBillingRestriction(error) {
163
+ return (isSpendingLimitError(error) ||
164
+ isInsufficientCreditsError(error) ||
165
+ isPaymentMethodRequiredError(error) ||
166
+ isPaymentDeclinedError(error));
167
+ }
168
+ /**
169
+ * Format time until reset
170
+ */
171
+ export function formatTimeUntilReset(resetsAt) {
172
+ if (!resetsAt)
173
+ return '';
174
+ const reset = new Date(resetsAt);
175
+ const now = new Date();
176
+ const diff = reset.getTime() - now.getTime();
177
+ if (diff < 0)
178
+ return 'soon';
179
+ const hours = Math.floor(diff / (1000 * 60 * 60));
180
+ const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
181
+ if (hours > 0) {
182
+ return `in ${hours}h ${minutes}m`;
183
+ }
184
+ return `in ${minutes}m`;
185
+ }
@@ -0,0 +1,43 @@
1
+ import type { ParsedError } from "../types";
2
+ /**
3
+ * Streaming (SSE) error classification for the gateway's chat/completions stream.
4
+ *
5
+ * A pre-stream denial arrives as a normal HTTP 402/429/403 with a JSON body —
6
+ * `parseError` already handles that. But a failure AFTER the stream has started
7
+ * (e.g. a billing/finalize error) is delivered as an SSE `event: error` frame,
8
+ * which a fetch-stream consumer must parse itself. These helpers surface that
9
+ * terminal error in the EXACT same classified shape as a non-streaming error,
10
+ * because the gateway now emits the canonical `{ error: { code, type, message } }`
11
+ * envelope inside the frame's `data:` (see lib/errors/api-error.ts).
12
+ */
13
+ export interface SSEEvent {
14
+ /** The `event:` name, if the frame set one (e.g. `error`). */
15
+ event?: string;
16
+ /** The concatenated `data:` payload. */
17
+ data: string;
18
+ }
19
+ /** Parse a raw SSE frame block (the text between blank-line delimiters). */
20
+ export declare function parseSSEFrame(block: string): SSEEvent | null;
21
+ /**
22
+ * If a frame is a terminal error — either `event: error`, or a `data:` payload
23
+ * carrying the canonical `{ error: { code } }` envelope — return the classified
24
+ * ParsedError (identical to `parseError()` for a non-streaming failure).
25
+ * Returns null for a normal data frame or the `[DONE]` sentinel.
26
+ */
27
+ export declare function parseSSEError(frame: SSEEvent | string): ParsedError | null;
28
+ export interface SSEStreamHandlers {
29
+ /** Called with each normal SSE `data:` payload (e.g. an OpenAI delta chunk). */
30
+ onData?: (data: string) => void;
31
+ /** Called ONCE with a classified error if the stream ends in an error frame. */
32
+ onError?: (error: ParsedError) => void;
33
+ /** Called when the stream completes (after `[DONE]` or the body closing). */
34
+ onDone?: () => void;
35
+ }
36
+ /**
37
+ * Consume a streaming Response body (`fetch(...).body`), splitting it into SSE
38
+ * frames. Normal data frames go to `onData`; a terminal `event: error` frame is
39
+ * classified and delivered to `onError` (NOT thrown), so a consumer using raw
40
+ * fetch-stream parsing gets the same `ParsedError` it would from a non-streaming
41
+ * call and can drive the same billing/auth modal. Resolves when the stream ends.
42
+ */
43
+ export declare function consumeSSEStream(body: ReadableStream<Uint8Array> | null | undefined, handlers: SSEStreamHandlers): Promise<void>;
@@ -0,0 +1,89 @@
1
+ import { parseError } from "./parse-error";
2
+ /** Parse a raw SSE frame block (the text between blank-line delimiters). */
3
+ export function parseSSEFrame(block) {
4
+ let event;
5
+ const dataLines = [];
6
+ for (const line of block.split("\n")) {
7
+ if (line.startsWith("event:")) {
8
+ event = line.slice(6).trim();
9
+ }
10
+ else if (line.startsWith("data:")) {
11
+ dataLines.push(line.slice(5).replace(/^ /, ""));
12
+ }
13
+ }
14
+ if (!event && dataLines.length === 0)
15
+ return null;
16
+ return { event, data: dataLines.join("\n") };
17
+ }
18
+ /**
19
+ * If a frame is a terminal error — either `event: error`, or a `data:` payload
20
+ * carrying the canonical `{ error: { code } }` envelope — return the classified
21
+ * ParsedError (identical to `parseError()` for a non-streaming failure).
22
+ * Returns null for a normal data frame or the `[DONE]` sentinel.
23
+ */
24
+ export function parseSSEError(frame) {
25
+ const f = typeof frame === "string" ? parseSSEFrame(frame) : frame;
26
+ if (!f)
27
+ return null;
28
+ if (f.data === "[DONE]")
29
+ return null;
30
+ const isErrorEvent = f.event === "error";
31
+ let payload;
32
+ try {
33
+ payload = JSON.parse(f.data);
34
+ }
35
+ catch {
36
+ // Non-JSON data on an error frame — still surface it as a classified error.
37
+ return isErrorEvent ? parseError({ message: f.data }) : null;
38
+ }
39
+ const hasEnvelope = !!payload &&
40
+ typeof payload === "object" &&
41
+ typeof payload.error === "object" &&
42
+ payload.error !== null &&
43
+ "code" in payload.error;
44
+ return isErrorEvent || hasEnvelope ? parseError(payload) : null;
45
+ }
46
+ /**
47
+ * Consume a streaming Response body (`fetch(...).body`), splitting it into SSE
48
+ * frames. Normal data frames go to `onData`; a terminal `event: error` frame is
49
+ * classified and delivered to `onError` (NOT thrown), so a consumer using raw
50
+ * fetch-stream parsing gets the same `ParsedError` it would from a non-streaming
51
+ * call and can drive the same billing/auth modal. Resolves when the stream ends.
52
+ */
53
+ export async function consumeSSEStream(body, handlers) {
54
+ if (!body) {
55
+ handlers.onDone?.();
56
+ return;
57
+ }
58
+ const reader = body.getReader();
59
+ const decoder = new TextDecoder();
60
+ let buffer = "";
61
+ try {
62
+ while (true) {
63
+ const { value, done } = await reader.read();
64
+ if (done)
65
+ break;
66
+ buffer += decoder.decode(value, { stream: true });
67
+ let idx;
68
+ while ((idx = buffer.indexOf("\n\n")) !== -1) {
69
+ const block = buffer.slice(0, idx);
70
+ buffer = buffer.slice(idx + 2);
71
+ const frame = parseSSEFrame(block);
72
+ if (!frame)
73
+ continue;
74
+ const err = parseSSEError(frame);
75
+ if (err) {
76
+ handlers.onError?.(err);
77
+ continue;
78
+ }
79
+ if (frame.data === "[DONE]")
80
+ continue;
81
+ handlers.onData?.(frame.data);
82
+ }
83
+ }
84
+ }
85
+ finally {
86
+ reader.releaseLock();
87
+ }
88
+ handlers.onDone?.();
89
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Popup helpers for the seamless (in-page) auth + charge flow.
3
+ *
4
+ * Both auth and Stripe card-entry can run in a popup so the whole
5
+ * press-buy → login → add-card → charge chain completes without leaving the
6
+ * developer's page (the PayPal / Firebase `signInWithPopup` model). Each helper
7
+ * resolves when the popup posts a completion message from the expected origin,
8
+ * or when the popup closes (treated as a cancel). If the browser blocks the
9
+ * popup, the helper returns `'blocked'` so the caller can fall back to redirect.
10
+ */
11
+ import type { InteractionMode, ResolvedMode } from '../types';
12
+ /**
13
+ * Resolve the effective mode. `auto` prefers popup on desktop but falls back to
14
+ * redirect on mobile / in-app webviews, where popups are unreliable (this mirrors
15
+ * Firebase's "popup on web, redirect on mobile" guidance).
16
+ */
17
+ export declare function resolveInteractionMode(mode: InteractionMode | undefined): ResolvedMode;
18
+ interface PopupOptions {
19
+ url: string;
20
+ /** window.open target name; also read back in the callback to detect popup context. */
21
+ name: string;
22
+ /** Only completion messages from this origin are trusted. */
23
+ expectedOrigin: string;
24
+ /** Message discriminator to accept (e.g. 'hypery:auth', 'hypery:payment-method'). */
25
+ messageType: string;
26
+ width?: number;
27
+ height?: number;
28
+ }
29
+ export type PopupResult<T> = {
30
+ blocked: true;
31
+ } | {
32
+ blocked: false;
33
+ cancelled: boolean;
34
+ data?: T;
35
+ };
36
+ /**
37
+ * Open `url` in a popup and resolve when it posts `{ type: messageType, ... }`
38
+ * from `expectedOrigin`, or when it closes (cancelled). Returns `{ blocked: true }`
39
+ * if the browser blocked the popup.
40
+ */
41
+ export declare function openPopup<T = any>(opts: PopupOptions): Promise<PopupResult<T>>;
42
+ export {};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Popup helpers for the seamless (in-page) auth + charge flow.
3
+ *
4
+ * Both auth and Stripe card-entry can run in a popup so the whole
5
+ * press-buy → login → add-card → charge chain completes without leaving the
6
+ * developer's page (the PayPal / Firebase `signInWithPopup` model). Each helper
7
+ * resolves when the popup posts a completion message from the expected origin,
8
+ * or when the popup closes (treated as a cancel). If the browser blocks the
9
+ * popup, the helper returns `'blocked'` so the caller can fall back to redirect.
10
+ */
11
+ /**
12
+ * Resolve the effective mode. `auto` prefers popup on desktop but falls back to
13
+ * redirect on mobile / in-app webviews, where popups are unreliable (this mirrors
14
+ * Firebase's "popup on web, redirect on mobile" guidance).
15
+ */
16
+ export function resolveInteractionMode(mode) {
17
+ const m = mode || 'auto';
18
+ if (m === 'popup' || m === 'redirect')
19
+ return m;
20
+ if (typeof navigator === 'undefined')
21
+ return 'redirect';
22
+ const ua = navigator.userAgent || '';
23
+ const isMobile = /Android|iPhone|iPad|iPod|Mobile|Silk|Kindle|BlackBerry|Opera Mini/i.test(ua);
24
+ return isMobile ? 'redirect' : 'popup';
25
+ }
26
+ function centeredFeatures(w, h) {
27
+ // Best-effort centering; harmless if the values are unavailable (SSR-guarded caller).
28
+ const dualLeft = typeof window !== 'undefined' ? (window.screenLeft ?? 0) : 0;
29
+ const dualTop = typeof window !== 'undefined' ? (window.screenTop ?? 0) : 0;
30
+ const width = typeof window !== 'undefined' ? window.innerWidth || w : w;
31
+ const height = typeof window !== 'undefined' ? window.innerHeight || h : h;
32
+ const left = dualLeft + Math.max(0, (width - w) / 2);
33
+ const top = dualTop + Math.max(0, (height - h) / 2);
34
+ return `width=${w},height=${h},left=${Math.round(left)},top=${Math.round(top)},resizable=yes,scrollbars=yes`;
35
+ }
36
+ /**
37
+ * Open `url` in a popup and resolve when it posts `{ type: messageType, ... }`
38
+ * from `expectedOrigin`, or when it closes (cancelled). Returns `{ blocked: true }`
39
+ * if the browser blocked the popup.
40
+ */
41
+ export function openPopup(opts) {
42
+ if (typeof window === 'undefined') {
43
+ return Promise.resolve({ blocked: false, cancelled: true });
44
+ }
45
+ const popup = window.open(opts.url, opts.name, centeredFeatures(opts.width || 480, opts.height || 720));
46
+ if (!popup) {
47
+ return Promise.resolve({ blocked: true });
48
+ }
49
+ return new Promise((resolve) => {
50
+ let settled = false;
51
+ const finish = (result) => {
52
+ if (settled)
53
+ return;
54
+ settled = true;
55
+ window.removeEventListener('message', onMessage);
56
+ clearInterval(poll);
57
+ resolve(result);
58
+ };
59
+ const onMessage = (event) => {
60
+ if (event.origin !== opts.expectedOrigin)
61
+ return;
62
+ const data = event.data;
63
+ if (data && typeof data === 'object' && data.type === opts.messageType) {
64
+ try {
65
+ popup.close();
66
+ }
67
+ catch {
68
+ /* cross-origin close may throw; ignore */
69
+ }
70
+ finish({ blocked: false, cancelled: false, data: data });
71
+ }
72
+ };
73
+ window.addEventListener('message', onMessage);
74
+ // Fallback: if the popup is closed without a message, treat it as a cancel.
75
+ const poll = setInterval(() => {
76
+ if (popup.closed)
77
+ finish({ blocked: false, cancelled: true });
78
+ }, 500);
79
+ });
80
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Token storage utilities
3
+ */
4
+ import type { AuthTokens, User } from '../types';
5
+ export declare class TokenStorage {
6
+ private storage;
7
+ private storageType;
8
+ constructor(storageType?: 'localStorage' | 'sessionStorage' | 'memory');
9
+ /**
10
+ * Save auth tokens
11
+ */
12
+ saveTokens(tokens: AuthTokens): void;
13
+ /**
14
+ * Get auth tokens
15
+ */
16
+ getTokens(): (AuthTokens & {
17
+ savedAt: number;
18
+ }) | null;
19
+ /**
20
+ * Check if access token is expired
21
+ */
22
+ isTokenExpired(): boolean;
23
+ /**
24
+ * Clear auth tokens
25
+ */
26
+ clearTokens(): void;
27
+ /**
28
+ * Save user info
29
+ */
30
+ saveUser(user: User): void;
31
+ /**
32
+ * Get user info
33
+ */
34
+ getUser(): User | null;
35
+ /**
36
+ * Clear user info
37
+ */
38
+ clearUser(): void;
39
+ /**
40
+ * Clear all auth data
41
+ */
42
+ clear(): void;
43
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Token storage utilities
3
+ */
4
+ const TOKEN_KEY = 'hypery_auth_tokens';
5
+ const USER_KEY = 'hypery_auth_user';
6
+ export class TokenStorage {
7
+ constructor(storageType = 'localStorage') {
8
+ this.storageType = storageType;
9
+ if (storageType === 'memory' || typeof window === 'undefined') {
10
+ // In-memory storage for SSR or memory mode
11
+ this.storage = new Map();
12
+ }
13
+ else {
14
+ this.storage = storageType === 'localStorage' ? localStorage : sessionStorage;
15
+ }
16
+ }
17
+ /**
18
+ * Save auth tokens
19
+ */
20
+ saveTokens(tokens) {
21
+ const data = JSON.stringify({
22
+ ...tokens,
23
+ savedAt: Date.now(),
24
+ });
25
+ if (this.storage instanceof Map) {
26
+ this.storage.set(TOKEN_KEY, data);
27
+ }
28
+ else {
29
+ this.storage.setItem(TOKEN_KEY, data);
30
+ }
31
+ }
32
+ /**
33
+ * Get auth tokens
34
+ */
35
+ getTokens() {
36
+ let data = null;
37
+ if (this.storage instanceof Map) {
38
+ data = this.storage.get(TOKEN_KEY) || null;
39
+ }
40
+ else {
41
+ data = this.storage.getItem(TOKEN_KEY);
42
+ }
43
+ if (!data)
44
+ return null;
45
+ try {
46
+ return JSON.parse(data);
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ /**
53
+ * Check if access token is expired
54
+ */
55
+ isTokenExpired() {
56
+ const tokens = this.getTokens();
57
+ if (!tokens)
58
+ return true;
59
+ const expiresAt = tokens.savedAt + tokens.expiresIn * 1000;
60
+ const now = Date.now();
61
+ // Add 60s buffer to refresh before expiration
62
+ return now >= expiresAt - 60000;
63
+ }
64
+ /**
65
+ * Clear auth tokens
66
+ */
67
+ clearTokens() {
68
+ if (this.storage instanceof Map) {
69
+ this.storage.delete(TOKEN_KEY);
70
+ }
71
+ else {
72
+ this.storage.removeItem(TOKEN_KEY);
73
+ }
74
+ }
75
+ /**
76
+ * Save user info
77
+ */
78
+ saveUser(user) {
79
+ const data = JSON.stringify(user);
80
+ if (this.storage instanceof Map) {
81
+ this.storage.set(USER_KEY, data);
82
+ }
83
+ else {
84
+ this.storage.setItem(USER_KEY, data);
85
+ }
86
+ }
87
+ /**
88
+ * Get user info
89
+ */
90
+ getUser() {
91
+ let data = null;
92
+ if (this.storage instanceof Map) {
93
+ data = this.storage.get(USER_KEY) || null;
94
+ }
95
+ else {
96
+ data = this.storage.getItem(USER_KEY);
97
+ }
98
+ if (!data)
99
+ return null;
100
+ try {
101
+ return JSON.parse(data);
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }
107
+ /**
108
+ * Clear user info
109
+ */
110
+ clearUser() {
111
+ if (this.storage instanceof Map) {
112
+ this.storage.delete(USER_KEY);
113
+ }
114
+ else {
115
+ this.storage.removeItem(USER_KEY);
116
+ }
117
+ }
118
+ /**
119
+ * Clear all auth data
120
+ */
121
+ clear() {
122
+ this.clearTokens();
123
+ this.clearUser();
124
+ }
125
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Utility functions for @hyperyai/sdk
3
+ */
4
+ import { type ClassValue } from "clsx";
5
+ /**
6
+ * Merge Tailwind CSS classes with proper precedence
7
+ */
8
+ export declare function cn(...inputs: ClassValue[]): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Utility functions for @hyperyai/sdk
3
+ */
4
+ import { clsx } from "clsx";
5
+ import { twMerge } from "tailwind-merge";
6
+ /**
7
+ * Merge Tailwind CSS classes with proper precedence
8
+ */
9
+ export function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }