@slyxup/core 1.0.1 → 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/src/client.ts DELETED
@@ -1,634 +0,0 @@
1
- import {
2
- NetworkError,
3
- RateLimitError,
4
- SlyxupError,
5
- UnauthorizedError,
6
- } from './errors.js';
7
- import type {
8
- AuthResponse,
9
- ChangePasswordInput,
10
- CompleteSignInInput,
11
- ConnectedAccountsResponse,
12
- EnableTOTPResponse,
13
- ErrorResponse,
14
- Result,
15
- RevokeSessionsResponse,
16
- SessionResponse,
17
- SessionsResponse,
18
- SignInInput,
19
- SignInResponse,
20
- SignUpInput,
21
- SlyxupClientOptions,
22
- TOTPSetupResponse,
23
- TwoFactorRequiredResponse,
24
- TwoFactorStatusResponse,
25
- UpdateUserInput,
26
- UserResponse,
27
- } from './types.js';
28
-
29
- const DEFAULT_API_URL = 'https://auth.slyxup.online';
30
-
31
- /** Minimal cookie jar so the SDK works in Node/SSR (browsers manage cookies natively). */
32
- function createCookieJar() {
33
- let jar: string | undefined;
34
- return {
35
- capture(res: Response) {
36
- const setCookie = res.headers.get('set-cookie');
37
- if (setCookie) jar = setCookie.split(';')[0]?.trim();
38
- },
39
- header(): Record<string, string> {
40
- return jar ? { Cookie: jar } : {};
41
- },
42
- clear() {
43
- jar = undefined;
44
- },
45
- };
46
- }
47
-
48
- /**
49
- * SlyxUp Core Client.
50
- *
51
- * ```ts
52
- * const client = new SlyxupClient({ publishableKey: 'pk_test_xxx' });
53
- * await client.auth.signIn({ email: 'a@b.com', password: '12345678' });
54
- * const session = await client.sessions.get();
55
- * ```
56
- */
57
- export class SlyxupClient {
58
- readonly publishableKey?: string;
59
- readonly secretKey?: string;
60
- readonly apiUrl: string;
61
- private _getToken?: () => string | undefined;
62
- private _request?: <T>(path: string, init?: RequestInit) => Promise<T>;
63
-
64
- readonly auth: {
65
- signUp: (input: SignUpInput) => Promise<AuthResponse>;
66
- signIn: (input: SignInInput) => Promise<SignInResponse>;
67
- signOut: () => Promise<{ ok: true }>;
68
- resendVerification: (email: string) => Promise<{ ok: true }>;
69
- forgotPassword: (
70
- email: string,
71
- projectId?: string
72
- ) => Promise<{ ok: true }>;
73
- resetPassword: (token: string, password: string) => Promise<{ ok: true }>;
74
- verifyEmail: (token: string) => Promise<{ ok: true }>;
75
- /** Complete a 2FA challenge returned by signIn. */
76
- completeSignIn: (input: CompleteSignInInput) => Promise<AuthResponse>;
77
- };
78
-
79
- readonly sessions: {
80
- get: () => Promise<SessionResponse>;
81
- list: (opts?: {
82
- limit?: number;
83
- offset?: number;
84
- }) => Promise<SessionsResponse>;
85
- revoke: (sessionId: string) => Promise<{ ok: true }>;
86
- revokeOthers: () => Promise<RevokeSessionsResponse>;
87
- };
88
-
89
- readonly password: {
90
- change: (input: ChangePasswordInput) => Promise<{ ok: true }>;
91
- };
92
-
93
- readonly twoFactor: {
94
- setup: () => Promise<TOTPSetupResponse>;
95
- status: () => Promise<TwoFactorStatusResponse>;
96
- enable: (secret: string, code: string) => Promise<EnableTOTPResponse>;
97
- verify: (code: string) => Promise<{ ok: boolean; valid: boolean }>;
98
- disable: (code: string) => Promise<{ ok: true }>;
99
- };
100
-
101
- readonly accounts: {
102
- list: () => Promise<ConnectedAccountsResponse>;
103
- unlink: (
104
- accountId: string,
105
- provider: 'google' | 'github'
106
- ) => Promise<{ ok: true }>;
107
- };
108
-
109
- readonly users: {
110
- me: () => Promise<UserResponse>;
111
- update: (input: UpdateUserInput) => Promise<UserResponse>;
112
- delete: () => Promise<{ ok: true }>;
113
- };
114
-
115
- readonly admin: {
116
- /** Get current project details */
117
- getProject: () => Promise<{
118
- ok: true;
119
- project: { id: string; name: string; createdAt: string };
120
- }>;
121
- /** List users in the project */
122
- listUsers: (opts?: { limit?: number; offset?: number }) => Promise<{
123
- ok: true;
124
- users: Array<{
125
- id: string;
126
- email: string;
127
- firstName: string | null;
128
- lastName: string | null;
129
- emailVerified: boolean;
130
- blocked: boolean;
131
- createdAt: string;
132
- }>;
133
- total: number;
134
- }>;
135
- /** Get a single user by ID */
136
- getUser: (
137
- userId: string
138
- ) => Promise<{
139
- ok: true;
140
- user: {
141
- id: string;
142
- email: string;
143
- firstName: string | null;
144
- lastName: string | null;
145
- emailVerified: boolean;
146
- blocked: boolean;
147
- createdAt: string;
148
- updatedAt: string;
149
- };
150
- }>;
151
- /** List active sessions */
152
- listSessions: () => Promise<{
153
- ok: true;
154
- sessions: Array<{
155
- id: string;
156
- userId: string;
157
- ipAddress: string | null;
158
- userAgent: string | null;
159
- expiresAt: string;
160
- isExpired: boolean;
161
- createdAt: string;
162
- }>;
163
- }>;
164
- /** List API keys */
165
- listKeys: () => Promise<{
166
- ok: true;
167
- keys: Array<{
168
- id: string;
169
- name: string;
170
- prefix: string;
171
- environment: string;
172
- type: string;
173
- lastUsedAt: string | null;
174
- createdAt: string;
175
- }>;
176
- }>;
177
- /** Create an API key (full key returned only once) */
178
- createKey: (input: {
179
- name: string;
180
- type: 'publishable' | 'secret';
181
- environment: 'test' | 'live';
182
- }) => Promise<{ ok: true; id: string; key: string; prefix: string }>;
183
- /** Revoke an API key */
184
- revokeKey: (keyId: string) => Promise<{ ok: true }>;
185
- };
186
-
187
- /** Get current session token (for custom project APIs that need Bearer) */
188
- getToken(): string | undefined {
189
- return this._getToken?.();
190
- }
191
- /** Raw request for custom endpoints (uses same auth headers + cookies as SDK) */
192
- async request<T>(path: string, init?: RequestInit): Promise<T> {
193
- if (!this._request) throw new Error('Client not initialized');
194
- return this._request<T>(path, init);
195
- }
196
-
197
- constructor(options: SlyxupClientOptions = {}) {
198
- const jar = createCookieJar();
199
- // Persist token in localStorage so refresh keeps session for cross-origin (auth -> billing) and for dashboard project APIs
200
- const STORAGE_KEY = 'slyxup_session_token';
201
- let storedToken: string | undefined;
202
- try {
203
- if (typeof window !== 'undefined' && window.localStorage) {
204
- storedToken = window.localStorage.getItem(STORAGE_KEY) ?? undefined;
205
- }
206
- } catch {}
207
- const persistToken = (t: string | undefined) => {
208
- storedToken = t;
209
- try {
210
- if (typeof window !== 'undefined' && window.localStorage) {
211
- if (t) window.localStorage.setItem(STORAGE_KEY, t);
212
- else window.localStorage.removeItem(STORAGE_KEY);
213
- }
214
- } catch {}
215
- };
216
- this.publishableKey = options.publishableKey;
217
- this.secretKey = options.secretKey;
218
- this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '');
219
- this._getToken = () => storedToken;
220
- // _request will be assigned after `request` is defined below
221
-
222
- const requestInner = async <T>(
223
- path: string,
224
- init: RequestInit & { body?: string } = {},
225
- opts?: { captureError?: boolean }
226
- ): Promise<Result<T>> => {
227
- let res: Response;
228
- try {
229
- // Build headers: prefer cookie jar (SSR), fall back to stored Bearer token (browser cross-origin)
230
- const authHeaders: Record<string, string> = {};
231
- const jarHeader = jar.header();
232
- if (Object.keys(jarHeader).length > 0) {
233
- Object.assign(authHeaders, jarHeader);
234
- } else if (storedToken) {
235
- authHeaders.Authorization = `Bearer ${storedToken}`;
236
- }
237
- // Always send publishable key so server can scope auth to the correct project
238
- // and reject requests with invalid / missing keys (fixes demo-with-wrong-pk bug).
239
- if (this.publishableKey && this.publishableKey !== 'pk_test_missing') {
240
- authHeaders['X-Publishable-Key'] = this.publishableKey;
241
- }
242
-
243
- res = await fetch(`${this.apiUrl}${path}`, {
244
- ...init,
245
- headers: {
246
- 'Content-Type': 'application/json',
247
- ...authHeaders,
248
- ...init.headers,
249
- },
250
- credentials: 'include',
251
- });
252
- } catch {
253
- throw new NetworkError();
254
- }
255
- jar.capture(res);
256
-
257
- const data = (await res
258
- .json()
259
- .catch(() => ({ ok: false, error: 'Invalid response' }))) as object;
260
-
261
- if (!res.ok) {
262
- if (opts?.captureError) return data as Result<T>;
263
- if (res.status === 401)
264
- throw new UnauthorizedError(
265
- 'error' in data ? String(data.error) : undefined
266
- );
267
- if (res.status === 429) throw new RateLimitError();
268
- throw new SlyxupError(
269
- 'error' in data
270
- ? String(data.error)
271
- : `Request failed (${res.status})`,
272
- res.status,
273
- 'api_error'
274
- );
275
- }
276
-
277
- return data as Result<T>;
278
- };
279
-
280
- const post = <T>(
281
- path: string,
282
- body?: unknown,
283
- opts?: { captureError?: boolean }
284
- ) =>
285
- requestInner<T>(
286
- path,
287
- {
288
- method: 'POST',
289
- body: body === undefined ? undefined : JSON.stringify(body),
290
- },
291
- opts
292
- );
293
-
294
- this.auth = {
295
- signUp: async (input) => {
296
- const res = await post<AuthResponse>('/v1/auth/sign-up', input);
297
- if (!('user' in res))
298
- throw new SlyxupError(res.error, 400, 'api_error');
299
- if (res.sessionToken) persistToken(res.sessionToken);
300
- return res;
301
- },
302
- signIn: async (input) => {
303
- // The server may answer 403 with code 2FA_REQUIRED — we must surface
304
- // the challenge back to the caller instead of throwing a generic error.
305
- const res = await post<SignInResponse>('/v1/auth/sign-in', input, {
306
- captureError: true,
307
- });
308
- if ('challengeToken' in res) {
309
- return res as TwoFactorRequiredResponse;
310
- }
311
- if (!('user' in res))
312
- throw new UnauthorizedError(
313
- (res as { error?: string }).error ?? 'Sign in failed'
314
- );
315
- if (res.sessionToken) persistToken(res.sessionToken);
316
- return res;
317
- },
318
- completeSignIn: async (input) => {
319
- const res = await post<AuthResponse>('/v1/auth/sign-in/2fa', input);
320
- if (!('user' in res)) throw new UnauthorizedError(res.error);
321
- if (res.sessionToken) persistToken(res.sessionToken);
322
- return res;
323
- },
324
- signOut: async () => {
325
- const res = await post<{ ok: true }>('/v1/auth/sign-out');
326
- persistToken(undefined);
327
- jar.clear();
328
- return res as { ok: true };
329
- },
330
- resendVerification: async (email: string) => {
331
- const res = await post<{ ok: true }>('/v1/verification/resend', {
332
- email,
333
- });
334
- if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
335
- return res as { ok: true };
336
- },
337
- forgotPassword: async (email: string, projectId?: string) => {
338
- const res = await post<{ ok: true }>(
339
- '/v1/verification/password/forgot',
340
- {
341
- email,
342
- projectId,
343
- }
344
- );
345
- if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
346
- return res as { ok: true };
347
- },
348
- resetPassword: async (token: string, password: string) => {
349
- const res = await post<{ ok: true }>(
350
- '/v1/verification/password/reset',
351
- {
352
- token,
353
- password,
354
- }
355
- );
356
- if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
357
- return res as { ok: true };
358
- },
359
- verifyEmail: async (token: string) => {
360
- const res = await post<{ ok: true }>('/v1/verification/verify', {
361
- token,
362
- });
363
- if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
364
- return res as { ok: true };
365
- },
366
- };
367
-
368
- this.sessions = {
369
- get: async () => {
370
- const res = await requestInner<SessionResponse>('/v1/session');
371
- if (!('session' in res)) throw new UnauthorizedError(res.error);
372
- return res;
373
- },
374
- list: async (opts?: { limit?: number; offset?: number }) => {
375
- const params = new URLSearchParams();
376
- if (opts?.limit !== undefined) params.set('limit', String(opts.limit));
377
- if (opts?.offset !== undefined)
378
- params.set('offset', String(opts.offset));
379
- const qs = params.toString();
380
- const res = await requestInner<SessionsResponse>(
381
- `/v1/sessions${qs ? `?${qs}` : ''}`
382
- );
383
- if (!('sessions' in res))
384
- throw new SlyxupError(res.error, 400, 'api_error');
385
- return res;
386
- },
387
- revoke: async (sessionId: string) => {
388
- const res = await requestInner<{ ok: true }>(
389
- `/v1/sessions/${sessionId}`,
390
- {
391
- method: 'DELETE',
392
- }
393
- );
394
- return res as { ok: true };
395
- },
396
- revokeOthers: async () => {
397
- const res = await requestInner<RevokeSessionsResponse>('/v1/sessions', {
398
- method: 'DELETE',
399
- });
400
- if (!('revoked' in res))
401
- throw new SlyxupError(res.error, 400, 'api_error');
402
- return res;
403
- },
404
- };
405
-
406
- this.password = {
407
- change: async (input) => {
408
- const res = await post<{ ok: true }>('/v1/user/password', input);
409
- if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
410
- return res as { ok: true };
411
- },
412
- };
413
-
414
- this.twoFactor = {
415
- setup: async () => {
416
- const res = await requestInner<TOTPSetupResponse>('/v1/user/2fa/setup');
417
- if (!('secret' in res))
418
- throw new SlyxupError(res.error, 400, 'api_error');
419
- return res;
420
- },
421
- status: async () => {
422
- const res = await requestInner<TwoFactorStatusResponse>(
423
- '/v1/user/2fa/status'
424
- );
425
- return res as TwoFactorStatusResponse;
426
- },
427
- enable: async (secret, code) => {
428
- const res = await post<EnableTOTPResponse>(
429
- '/v1/user/2fa/enable',
430
- { secret, code },
431
- { captureError: true }
432
- );
433
- if (!('recoveryCodes' in res))
434
- throw new SlyxupError(res.error, 400, 'api_error');
435
- return res;
436
- },
437
- verify: async (code) => {
438
- const res = await post<{ ok: boolean; valid: boolean }>(
439
- '/v1/user/2fa/verify',
440
- { code },
441
- { captureError: true }
442
- );
443
- if (!('valid' in res))
444
- throw new SlyxupError(res.error, 400, 'api_error');
445
- return res as { ok: boolean; valid: boolean };
446
- },
447
- disable: async (code) => {
448
- const res = await post<{ ok: true }>('/v1/user/2fa/disable', { code });
449
- if (!res.ok) throw new SlyxupError(res.error, 400, 'api_error');
450
- return res as { ok: true };
451
- },
452
- };
453
-
454
- this.accounts = {
455
- list: async () => {
456
- const res =
457
- await requestInner<ConnectedAccountsResponse>('/v1/user/accounts');
458
- if (!('accounts' in res))
459
- throw new SlyxupError(res.error, 400, 'api_error');
460
- return res;
461
- },
462
- unlink: async (accountId, provider) => {
463
- const res = await requestInner<{ ok: true }>(
464
- `/v1/user/accounts/${accountId}?provider=${encodeURIComponent(provider)}`,
465
- {
466
- method: 'DELETE',
467
- }
468
- );
469
- return res as { ok: true };
470
- },
471
- };
472
-
473
- this.users = {
474
- me: async () => {
475
- const res = await requestInner<UserResponse>('/v1/user');
476
- if (!('user' in res)) throw new UnauthorizedError(res.error);
477
- return res;
478
- },
479
- update: async (input) => {
480
- const res = await requestInner<UserResponse>('/v1/user', {
481
- method: 'PATCH',
482
- body: JSON.stringify(input),
483
- });
484
- if (!('user' in res))
485
- throw new SlyxupError(res.error, 400, 'api_error');
486
- return res;
487
- },
488
- delete: async () => {
489
- const res = await requestInner<{ ok: true }>('/v1/user', {
490
- method: 'DELETE',
491
- });
492
- return res as { ok: true };
493
- },
494
- };
495
-
496
- // ── Admin API (requires secretKey) ──
497
- const sk = options.secretKey;
498
- const adminRequest = async <T>(
499
- path: string,
500
- init: RequestInit = {}
501
- ): Promise<T> => {
502
- if (!sk)
503
- throw new SlyxupError(
504
- 'Secret key required for admin API',
505
- 400,
506
- 'api_error'
507
- );
508
- const res = await fetch(`${this.apiUrl}${path}`, {
509
- ...init,
510
- headers: {
511
- 'Content-Type': 'application/json',
512
- Authorization: `Bearer ${sk}`,
513
- ...init.headers,
514
- },
515
- });
516
- const data = (await res
517
- .json()
518
- .catch(() => ({ ok: false, error: 'Invalid response' }))) as object;
519
- if (!res.ok) {
520
- throw new SlyxupError(
521
- 'error' in data
522
- ? String((data as { error: unknown }).error)
523
- : `Request failed (${res.status})`,
524
- res.status,
525
- 'api_error'
526
- );
527
- }
528
- return data as T;
529
- };
530
-
531
- this.admin = {
532
- getProject: async () => {
533
- const res = await adminRequest<{
534
- ok: true;
535
- project: { id: string; name: string; createdAt: string };
536
- }>('/v1/admin/project');
537
- return res;
538
- },
539
- listUsers: async (opts) => {
540
- const params = new URLSearchParams();
541
- if (opts?.limit !== undefined) params.set('limit', String(opts.limit));
542
- if (opts?.offset !== undefined)
543
- params.set('offset', String(opts.offset));
544
- const qs = params.toString();
545
- const res = await adminRequest<{
546
- ok: true;
547
- users: Array<{
548
- id: string;
549
- email: string;
550
- firstName: string | null;
551
- lastName: string | null;
552
- emailVerified: boolean;
553
- blocked: boolean;
554
- createdAt: string;
555
- }>;
556
- total: number;
557
- }>(`/v1/admin/users${qs ? `?${qs}` : ''}`);
558
- return res;
559
- },
560
- getUser: async (userId) => {
561
- const res = await adminRequest<{
562
- ok: true;
563
- user: {
564
- id: string;
565
- email: string;
566
- firstName: string | null;
567
- lastName: string | null;
568
- emailVerified: boolean;
569
- blocked: boolean;
570
- createdAt: string;
571
- updatedAt: string;
572
- };
573
- }>(`/v1/admin/users/${encodeURIComponent(userId)}`);
574
- return res;
575
- },
576
- listSessions: async () => {
577
- const res = await adminRequest<{
578
- ok: true;
579
- sessions: Array<{
580
- id: string;
581
- userId: string;
582
- ipAddress: string | null;
583
- userAgent: string | null;
584
- expiresAt: string;
585
- isExpired: boolean;
586
- createdAt: string;
587
- }>;
588
- }>('/v1/admin/sessions');
589
- return res;
590
- },
591
- listKeys: async () => {
592
- const res = await adminRequest<{
593
- ok: true;
594
- keys: Array<{
595
- id: string;
596
- name: string;
597
- prefix: string;
598
- environment: string;
599
- type: string;
600
- lastUsedAt: string | null;
601
- createdAt: string;
602
- }>;
603
- }>('/v1/admin/keys');
604
- return res;
605
- },
606
- createKey: async (input) => {
607
- const res = await adminRequest<{
608
- ok: true;
609
- id: string;
610
- key: string;
611
- prefix: string;
612
- }>('/v1/admin/keys', { method: 'POST', body: JSON.stringify(input) });
613
- return res;
614
- },
615
- revokeKey: async (keyId) => {
616
- const res = await adminRequest<{ ok: true }>(
617
- `/v1/admin/keys/${encodeURIComponent(keyId)}`,
618
- { method: 'DELETE' }
619
- );
620
- return res as { ok: true };
621
- },
622
- };
623
-
624
- this._request = requestInner as unknown as <T>(
625
- path: string,
626
- init?: RequestInit
627
- ) => Promise<T>;
628
- }
629
- }
630
-
631
- // Re-exports so consumers can import from '@slyxup/core'
632
- export * from './types.js';
633
- export * from './errors.js';
634
- // test publish after fix
package/src/errors.ts DELETED
@@ -1,41 +0,0 @@
1
- // SlyxUp Core SDK — typed errors
2
-
3
- export class SlyxupError extends Error {
4
- readonly status: number;
5
- readonly code: string;
6
-
7
- constructor(message: string, status: number, code: string) {
8
- super(message);
9
- this.name = 'SlyxupError';
10
- this.status = status;
11
- this.code = code;
12
- }
13
- }
14
-
15
- export class UnauthorizedError extends SlyxupError {
16
- constructor(message = 'Unauthorized') {
17
- super(message, 401, 'unauthorized');
18
- this.name = 'UnauthorizedError';
19
- }
20
- }
21
-
22
- export class ValidationError extends SlyxupError {
23
- constructor(message = 'Validation failed') {
24
- super(message, 400, 'validation_error');
25
- this.name = 'ValidationError';
26
- }
27
- }
28
-
29
- export class NetworkError extends SlyxupError {
30
- constructor(message = 'Network request failed') {
31
- super(message, 0, 'network_error');
32
- this.name = 'NetworkError';
33
- }
34
- }
35
-
36
- export class RateLimitError extends SlyxupError {
37
- constructor(message = 'Too many requests') {
38
- super(message, 429, 'rate_limited');
39
- this.name = 'RateLimitError';
40
- }
41
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export { SlyxupClient } from './client.js';
2
- export * from './types.js';
3
- export * from './errors.js';