@sendoracloud/sdk-react-native 0.8.0 → 0.10.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/index.cjs +130 -2
- package/dist/index.d.cts +60 -3
- package/dist/index.d.ts +60 -3
- package/dist/index.js +130 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -355,6 +355,59 @@ var Auth = class {
|
|
|
355
355
|
return parsed.data.user;
|
|
356
356
|
});
|
|
357
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Verify an IdP-issued credential (authorization code OR id_token)
|
|
360
|
+
* and mint a Sendora session. Customer's app handles the IdP
|
|
361
|
+
* dance — typically via expo-auth-session / react-native-app-auth
|
|
362
|
+
* — then hands the result here. See web SDK for full provider
|
|
363
|
+
* matrix; mirrors that surface 1:1.
|
|
364
|
+
*/
|
|
365
|
+
loginSocial(input) {
|
|
366
|
+
return this.serialize(async () => {
|
|
367
|
+
if (this.user !== null) await this.wipeLocalIdentity();
|
|
368
|
+
const res = await post(
|
|
369
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
370
|
+
"/api/v1/auth-service/login/social",
|
|
371
|
+
input
|
|
372
|
+
);
|
|
373
|
+
if (!res) throw new AuthError("NETWORK_ERROR", "Network request failed");
|
|
374
|
+
let parsed;
|
|
375
|
+
try {
|
|
376
|
+
parsed = await res.json();
|
|
377
|
+
} catch {
|
|
378
|
+
throw new AuthError("PARSE_ERROR", `HTTP ${res.status}`);
|
|
379
|
+
}
|
|
380
|
+
if (!res.ok || !parsed.success || !isAuthApiResponse(parsed.data)) {
|
|
381
|
+
throw new AuthError(
|
|
382
|
+
parsed.error?.code ?? "SOCIAL_LOGIN_FAILED",
|
|
383
|
+
parsed.error?.message ?? "Social login failed"
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
this.persist(parsed.data);
|
|
387
|
+
return parsed.data.user;
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
signInWithGoogle(code, redirectUri) {
|
|
391
|
+
return this.loginSocial({ provider: "google", code, redirectUri });
|
|
392
|
+
}
|
|
393
|
+
signInWithGitHub(code, redirectUri) {
|
|
394
|
+
return this.loginSocial({ provider: "github", code, redirectUri });
|
|
395
|
+
}
|
|
396
|
+
signInWithApple(input) {
|
|
397
|
+
return this.loginSocial({ provider: "apple", ...input });
|
|
398
|
+
}
|
|
399
|
+
signInWithMicrosoft(code, redirectUri) {
|
|
400
|
+
return this.loginSocial({ provider: "microsoft", code, redirectUri });
|
|
401
|
+
}
|
|
402
|
+
signInWithLinkedIn(code, redirectUri) {
|
|
403
|
+
return this.loginSocial({ provider: "linkedin", code, redirectUri });
|
|
404
|
+
}
|
|
405
|
+
signInWithFacebook(code, redirectUri) {
|
|
406
|
+
return this.loginSocial({ provider: "facebook", code, redirectUri });
|
|
407
|
+
}
|
|
408
|
+
signInWithDiscord(code, redirectUri) {
|
|
409
|
+
return this.loginSocial({ provider: "discord", code, redirectUri });
|
|
410
|
+
}
|
|
358
411
|
/**
|
|
359
412
|
* Exchange the MFA challenge token from `signIn` for a real session
|
|
360
413
|
* by submitting a TOTP or recovery code from the user's authenticator.
|
|
@@ -391,6 +444,77 @@ var Auth = class {
|
|
|
391
444
|
});
|
|
392
445
|
}
|
|
393
446
|
// ============================================================
|
|
447
|
+
// EMAIL OTP (6-digit cross-device code)
|
|
448
|
+
// ============================================================
|
|
449
|
+
async sendEmailOtp(email) {
|
|
450
|
+
const res = await post(
|
|
451
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
452
|
+
"/api/v1/auth-service/email-otp/request",
|
|
453
|
+
{ email }
|
|
454
|
+
);
|
|
455
|
+
if (!res || !res.ok) {
|
|
456
|
+
throw new AuthError("EMAIL_OTP_REQUEST_FAILED", "Failed to send code");
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
verifyEmailOtp(email, code) {
|
|
460
|
+
return this.serialize(async () => {
|
|
461
|
+
if (this.user !== null) await this.wipeLocalIdentity();
|
|
462
|
+
const data = await this.callAuth("/api/v1/auth-service/email-otp/verify", { email, code });
|
|
463
|
+
this.persist(data);
|
|
464
|
+
return data.user;
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
// ============================================================
|
|
468
|
+
// PASSWORD RESET + EMAIL VERIFICATION
|
|
469
|
+
// ============================================================
|
|
470
|
+
/** Trigger password-reset email. Resolves on 204 even when address is unknown (anti-enumeration). */
|
|
471
|
+
async requestPasswordReset(email) {
|
|
472
|
+
const res = await post(
|
|
473
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
474
|
+
"/api/v1/auth-service/password/forgot",
|
|
475
|
+
{ email }
|
|
476
|
+
);
|
|
477
|
+
if (!res || !res.ok) {
|
|
478
|
+
throw new AuthError("PASSWORD_RESET_REQUEST_FAILED", "Failed to send reset email");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/** Pair the reset-email token with the new password to complete the reset. */
|
|
482
|
+
async resetPassword(token, newPassword) {
|
|
483
|
+
const res = await post(
|
|
484
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
485
|
+
"/api/v1/auth-service/password/reset",
|
|
486
|
+
{ token, newPassword }
|
|
487
|
+
);
|
|
488
|
+
if (!res || !res.ok) {
|
|
489
|
+
throw new AuthError("PASSWORD_RESET_FAILED", "Reset failed");
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/** Verify the email-address token from the link Sendora sent on signup. */
|
|
493
|
+
async verifyEmail(token) {
|
|
494
|
+
const res = await post(
|
|
495
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
496
|
+
"/api/v1/auth-service/email/verify",
|
|
497
|
+
{ token }
|
|
498
|
+
);
|
|
499
|
+
if (!res || !res.ok) {
|
|
500
|
+
throw new AuthError("EMAIL_VERIFICATION_FAILED", "Verification failed");
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/** Re-send the email-verification email for the currently-signed-in user. */
|
|
504
|
+
async sendVerificationEmail() {
|
|
505
|
+
const headers = this.bearerHeaders();
|
|
506
|
+
if (!headers) throw new AuthError("UNAUTHORIZED", "Sign in before requesting a verification email");
|
|
507
|
+
const res = await post(
|
|
508
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
509
|
+
"/api/v1/auth-service/email/verify/resend",
|
|
510
|
+
{},
|
|
511
|
+
headers
|
|
512
|
+
);
|
|
513
|
+
if (!res || !res.ok) {
|
|
514
|
+
throw new AuthError("VERIFICATION_RESEND_FAILED", "Failed to send verification email");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// ============================================================
|
|
394
518
|
// TOTP MFA — enrollment management (Bearer-authenticated)
|
|
395
519
|
// ============================================================
|
|
396
520
|
async enrollMfa() {
|
|
@@ -643,7 +767,7 @@ function decodeJwtPayload(token) {
|
|
|
643
767
|
}
|
|
644
768
|
|
|
645
769
|
// src/index.ts
|
|
646
|
-
var SDK_VERSION = "0.
|
|
770
|
+
var SDK_VERSION = "0.10.0";
|
|
647
771
|
var DEFAULT_API_URL = "https://api.sendoracloud.com";
|
|
648
772
|
var ANON_KEY = "anon_id";
|
|
649
773
|
var USER_ID_KEY = "user_id";
|
|
@@ -692,7 +816,7 @@ var SendoraSDK = class {
|
|
|
692
816
|
}
|
|
693
817
|
/** Initialize the SDK. Must be awaited at app startup before identify/track. */
|
|
694
818
|
async init(config) {
|
|
695
|
-
const rawKey = String(config.publicKey ?? "").trim();
|
|
819
|
+
const rawKey = String(config.apiKey ?? config.publicKey ?? "").trim();
|
|
696
820
|
if (/^sk_/i.test(rawKey) || /sk_(prod|staging|dev|live|test)_/i.test(rawKey)) {
|
|
697
821
|
throw new Error(
|
|
698
822
|
"[sendoracloud] Secret key (sk_*) detected. The client SDK must only receive public keys (pk_*). Secret keys belong on your server."
|
|
@@ -737,6 +861,10 @@ var SendoraSDK = class {
|
|
|
737
861
|
environment: "prod",
|
|
738
862
|
consentedByDefault: true,
|
|
739
863
|
...config,
|
|
864
|
+
// Store rawKey on BOTH fields so any downstream reader that
|
|
865
|
+
// grabbed the legacy `publicKey` field keeps working, and
|
|
866
|
+
// future code reading `apiKey` does too.
|
|
867
|
+
apiKey: rawKey,
|
|
740
868
|
publicKey: rawKey
|
|
741
869
|
};
|
|
742
870
|
this.auth = new Auth({
|
package/dist/index.d.cts
CHANGED
|
@@ -137,6 +137,39 @@ declare class Auth {
|
|
|
137
137
|
mfaChallengeToken: string;
|
|
138
138
|
userId: string;
|
|
139
139
|
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Verify an IdP-issued credential (authorization code OR id_token)
|
|
142
|
+
* and mint a Sendora session. Customer's app handles the IdP
|
|
143
|
+
* dance — typically via expo-auth-session / react-native-app-auth
|
|
144
|
+
* — then hands the result here. See web SDK for full provider
|
|
145
|
+
* matrix; mirrors that surface 1:1.
|
|
146
|
+
*/
|
|
147
|
+
loginSocial(input: {
|
|
148
|
+
provider: "google" | "github" | "apple" | "microsoft" | "linkedin" | "facebook" | "twitter" | "discord";
|
|
149
|
+
code?: string;
|
|
150
|
+
idToken?: string;
|
|
151
|
+
redirectUri?: string;
|
|
152
|
+
codeVerifier?: string;
|
|
153
|
+
appleName?: {
|
|
154
|
+
firstName?: string;
|
|
155
|
+
lastName?: string;
|
|
156
|
+
};
|
|
157
|
+
}): Promise<AuthUser>;
|
|
158
|
+
signInWithGoogle(code: string, redirectUri: string): Promise<AuthUser>;
|
|
159
|
+
signInWithGitHub(code: string, redirectUri: string): Promise<AuthUser>;
|
|
160
|
+
signInWithApple(input: {
|
|
161
|
+
idToken?: string;
|
|
162
|
+
code?: string;
|
|
163
|
+
redirectUri?: string;
|
|
164
|
+
appleName?: {
|
|
165
|
+
firstName?: string;
|
|
166
|
+
lastName?: string;
|
|
167
|
+
};
|
|
168
|
+
}): Promise<AuthUser>;
|
|
169
|
+
signInWithMicrosoft(code: string, redirectUri: string): Promise<AuthUser>;
|
|
170
|
+
signInWithLinkedIn(code: string, redirectUri: string): Promise<AuthUser>;
|
|
171
|
+
signInWithFacebook(code: string, redirectUri: string): Promise<AuthUser>;
|
|
172
|
+
signInWithDiscord(code: string, redirectUri: string): Promise<AuthUser>;
|
|
140
173
|
/**
|
|
141
174
|
* Exchange the MFA challenge token from `signIn` for a real session
|
|
142
175
|
* by submitting a TOTP or recovery code from the user's authenticator.
|
|
@@ -144,6 +177,16 @@ declare class Auth {
|
|
|
144
177
|
challengeMfa(mfaChallengeToken: string, code: string): Promise<AuthUser>;
|
|
145
178
|
sendMagicLink(email: string): Promise<void>;
|
|
146
179
|
verifyMagicLink(token: string): Promise<AuthUser>;
|
|
180
|
+
sendEmailOtp(email: string): Promise<void>;
|
|
181
|
+
verifyEmailOtp(email: string, code: string): Promise<AuthUser>;
|
|
182
|
+
/** Trigger password-reset email. Resolves on 204 even when address is unknown (anti-enumeration). */
|
|
183
|
+
requestPasswordReset(email: string): Promise<void>;
|
|
184
|
+
/** Pair the reset-email token with the new password to complete the reset. */
|
|
185
|
+
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
186
|
+
/** Verify the email-address token from the link Sendora sent on signup. */
|
|
187
|
+
verifyEmail(token: string): Promise<void>;
|
|
188
|
+
/** Re-send the email-verification email for the currently-signed-in user. */
|
|
189
|
+
sendVerificationEmail(): Promise<void>;
|
|
147
190
|
enrollMfa(): Promise<{
|
|
148
191
|
secret: string;
|
|
149
192
|
otpauthUrl: string;
|
|
@@ -198,9 +241,23 @@ declare class Auth {
|
|
|
198
241
|
}
|
|
199
242
|
|
|
200
243
|
interface SendoraConfig {
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Public API key (`pk_live_…` or `pk_test_…`). Secret keys are refused.
|
|
246
|
+
* Canonical name as of 0.10.0 — pre-0.10.0 callers using `publicKey`
|
|
247
|
+
* still work via the deprecated alias below.
|
|
248
|
+
*/
|
|
249
|
+
apiKey?: string;
|
|
250
|
+
/**
|
|
251
|
+
* @deprecated Use `apiKey` instead (0.10.0+ canonical name across
|
|
252
|
+
* sdk-web / sdk-ios / sdk-android). Kept for back-compat — when
|
|
253
|
+
* both are set, `apiKey` wins.
|
|
254
|
+
*/
|
|
255
|
+
publicKey?: string;
|
|
256
|
+
/**
|
|
257
|
+
* @deprecated 0.10.0 — backend now derives org from the API key
|
|
258
|
+
* row server-side. Kept for test harnesses that need to override
|
|
259
|
+
* the bound org explicitly.
|
|
260
|
+
*/
|
|
204
261
|
orgId?: string;
|
|
205
262
|
/** Backend base URL. Defaults to Sendora's production API. */
|
|
206
263
|
apiUrl?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -137,6 +137,39 @@ declare class Auth {
|
|
|
137
137
|
mfaChallengeToken: string;
|
|
138
138
|
userId: string;
|
|
139
139
|
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Verify an IdP-issued credential (authorization code OR id_token)
|
|
142
|
+
* and mint a Sendora session. Customer's app handles the IdP
|
|
143
|
+
* dance — typically via expo-auth-session / react-native-app-auth
|
|
144
|
+
* — then hands the result here. See web SDK for full provider
|
|
145
|
+
* matrix; mirrors that surface 1:1.
|
|
146
|
+
*/
|
|
147
|
+
loginSocial(input: {
|
|
148
|
+
provider: "google" | "github" | "apple" | "microsoft" | "linkedin" | "facebook" | "twitter" | "discord";
|
|
149
|
+
code?: string;
|
|
150
|
+
idToken?: string;
|
|
151
|
+
redirectUri?: string;
|
|
152
|
+
codeVerifier?: string;
|
|
153
|
+
appleName?: {
|
|
154
|
+
firstName?: string;
|
|
155
|
+
lastName?: string;
|
|
156
|
+
};
|
|
157
|
+
}): Promise<AuthUser>;
|
|
158
|
+
signInWithGoogle(code: string, redirectUri: string): Promise<AuthUser>;
|
|
159
|
+
signInWithGitHub(code: string, redirectUri: string): Promise<AuthUser>;
|
|
160
|
+
signInWithApple(input: {
|
|
161
|
+
idToken?: string;
|
|
162
|
+
code?: string;
|
|
163
|
+
redirectUri?: string;
|
|
164
|
+
appleName?: {
|
|
165
|
+
firstName?: string;
|
|
166
|
+
lastName?: string;
|
|
167
|
+
};
|
|
168
|
+
}): Promise<AuthUser>;
|
|
169
|
+
signInWithMicrosoft(code: string, redirectUri: string): Promise<AuthUser>;
|
|
170
|
+
signInWithLinkedIn(code: string, redirectUri: string): Promise<AuthUser>;
|
|
171
|
+
signInWithFacebook(code: string, redirectUri: string): Promise<AuthUser>;
|
|
172
|
+
signInWithDiscord(code: string, redirectUri: string): Promise<AuthUser>;
|
|
140
173
|
/**
|
|
141
174
|
* Exchange the MFA challenge token from `signIn` for a real session
|
|
142
175
|
* by submitting a TOTP or recovery code from the user's authenticator.
|
|
@@ -144,6 +177,16 @@ declare class Auth {
|
|
|
144
177
|
challengeMfa(mfaChallengeToken: string, code: string): Promise<AuthUser>;
|
|
145
178
|
sendMagicLink(email: string): Promise<void>;
|
|
146
179
|
verifyMagicLink(token: string): Promise<AuthUser>;
|
|
180
|
+
sendEmailOtp(email: string): Promise<void>;
|
|
181
|
+
verifyEmailOtp(email: string, code: string): Promise<AuthUser>;
|
|
182
|
+
/** Trigger password-reset email. Resolves on 204 even when address is unknown (anti-enumeration). */
|
|
183
|
+
requestPasswordReset(email: string): Promise<void>;
|
|
184
|
+
/** Pair the reset-email token with the new password to complete the reset. */
|
|
185
|
+
resetPassword(token: string, newPassword: string): Promise<void>;
|
|
186
|
+
/** Verify the email-address token from the link Sendora sent on signup. */
|
|
187
|
+
verifyEmail(token: string): Promise<void>;
|
|
188
|
+
/** Re-send the email-verification email for the currently-signed-in user. */
|
|
189
|
+
sendVerificationEmail(): Promise<void>;
|
|
147
190
|
enrollMfa(): Promise<{
|
|
148
191
|
secret: string;
|
|
149
192
|
otpauthUrl: string;
|
|
@@ -198,9 +241,23 @@ declare class Auth {
|
|
|
198
241
|
}
|
|
199
242
|
|
|
200
243
|
interface SendoraConfig {
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Public API key (`pk_live_…` or `pk_test_…`). Secret keys are refused.
|
|
246
|
+
* Canonical name as of 0.10.0 — pre-0.10.0 callers using `publicKey`
|
|
247
|
+
* still work via the deprecated alias below.
|
|
248
|
+
*/
|
|
249
|
+
apiKey?: string;
|
|
250
|
+
/**
|
|
251
|
+
* @deprecated Use `apiKey` instead (0.10.0+ canonical name across
|
|
252
|
+
* sdk-web / sdk-ios / sdk-android). Kept for back-compat — when
|
|
253
|
+
* both are set, `apiKey` wins.
|
|
254
|
+
*/
|
|
255
|
+
publicKey?: string;
|
|
256
|
+
/**
|
|
257
|
+
* @deprecated 0.10.0 — backend now derives org from the API key
|
|
258
|
+
* row server-side. Kept for test harnesses that need to override
|
|
259
|
+
* the bound org explicitly.
|
|
260
|
+
*/
|
|
204
261
|
orgId?: string;
|
|
205
262
|
/** Backend base URL. Defaults to Sendora's production API. */
|
|
206
263
|
apiUrl?: string;
|
package/dist/index.js
CHANGED
|
@@ -315,6 +315,59 @@ var Auth = class {
|
|
|
315
315
|
return parsed.data.user;
|
|
316
316
|
});
|
|
317
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Verify an IdP-issued credential (authorization code OR id_token)
|
|
320
|
+
* and mint a Sendora session. Customer's app handles the IdP
|
|
321
|
+
* dance — typically via expo-auth-session / react-native-app-auth
|
|
322
|
+
* — then hands the result here. See web SDK for full provider
|
|
323
|
+
* matrix; mirrors that surface 1:1.
|
|
324
|
+
*/
|
|
325
|
+
loginSocial(input) {
|
|
326
|
+
return this.serialize(async () => {
|
|
327
|
+
if (this.user !== null) await this.wipeLocalIdentity();
|
|
328
|
+
const res = await post(
|
|
329
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
330
|
+
"/api/v1/auth-service/login/social",
|
|
331
|
+
input
|
|
332
|
+
);
|
|
333
|
+
if (!res) throw new AuthError("NETWORK_ERROR", "Network request failed");
|
|
334
|
+
let parsed;
|
|
335
|
+
try {
|
|
336
|
+
parsed = await res.json();
|
|
337
|
+
} catch {
|
|
338
|
+
throw new AuthError("PARSE_ERROR", `HTTP ${res.status}`);
|
|
339
|
+
}
|
|
340
|
+
if (!res.ok || !parsed.success || !isAuthApiResponse(parsed.data)) {
|
|
341
|
+
throw new AuthError(
|
|
342
|
+
parsed.error?.code ?? "SOCIAL_LOGIN_FAILED",
|
|
343
|
+
parsed.error?.message ?? "Social login failed"
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
this.persist(parsed.data);
|
|
347
|
+
return parsed.data.user;
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
signInWithGoogle(code, redirectUri) {
|
|
351
|
+
return this.loginSocial({ provider: "google", code, redirectUri });
|
|
352
|
+
}
|
|
353
|
+
signInWithGitHub(code, redirectUri) {
|
|
354
|
+
return this.loginSocial({ provider: "github", code, redirectUri });
|
|
355
|
+
}
|
|
356
|
+
signInWithApple(input) {
|
|
357
|
+
return this.loginSocial({ provider: "apple", ...input });
|
|
358
|
+
}
|
|
359
|
+
signInWithMicrosoft(code, redirectUri) {
|
|
360
|
+
return this.loginSocial({ provider: "microsoft", code, redirectUri });
|
|
361
|
+
}
|
|
362
|
+
signInWithLinkedIn(code, redirectUri) {
|
|
363
|
+
return this.loginSocial({ provider: "linkedin", code, redirectUri });
|
|
364
|
+
}
|
|
365
|
+
signInWithFacebook(code, redirectUri) {
|
|
366
|
+
return this.loginSocial({ provider: "facebook", code, redirectUri });
|
|
367
|
+
}
|
|
368
|
+
signInWithDiscord(code, redirectUri) {
|
|
369
|
+
return this.loginSocial({ provider: "discord", code, redirectUri });
|
|
370
|
+
}
|
|
318
371
|
/**
|
|
319
372
|
* Exchange the MFA challenge token from `signIn` for a real session
|
|
320
373
|
* by submitting a TOTP or recovery code from the user's authenticator.
|
|
@@ -351,6 +404,77 @@ var Auth = class {
|
|
|
351
404
|
});
|
|
352
405
|
}
|
|
353
406
|
// ============================================================
|
|
407
|
+
// EMAIL OTP (6-digit cross-device code)
|
|
408
|
+
// ============================================================
|
|
409
|
+
async sendEmailOtp(email) {
|
|
410
|
+
const res = await post(
|
|
411
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
412
|
+
"/api/v1/auth-service/email-otp/request",
|
|
413
|
+
{ email }
|
|
414
|
+
);
|
|
415
|
+
if (!res || !res.ok) {
|
|
416
|
+
throw new AuthError("EMAIL_OTP_REQUEST_FAILED", "Failed to send code");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
verifyEmailOtp(email, code) {
|
|
420
|
+
return this.serialize(async () => {
|
|
421
|
+
if (this.user !== null) await this.wipeLocalIdentity();
|
|
422
|
+
const data = await this.callAuth("/api/v1/auth-service/email-otp/verify", { email, code });
|
|
423
|
+
this.persist(data);
|
|
424
|
+
return data.user;
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
// ============================================================
|
|
428
|
+
// PASSWORD RESET + EMAIL VERIFICATION
|
|
429
|
+
// ============================================================
|
|
430
|
+
/** Trigger password-reset email. Resolves on 204 even when address is unknown (anti-enumeration). */
|
|
431
|
+
async requestPasswordReset(email) {
|
|
432
|
+
const res = await post(
|
|
433
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
434
|
+
"/api/v1/auth-service/password/forgot",
|
|
435
|
+
{ email }
|
|
436
|
+
);
|
|
437
|
+
if (!res || !res.ok) {
|
|
438
|
+
throw new AuthError("PASSWORD_RESET_REQUEST_FAILED", "Failed to send reset email");
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/** Pair the reset-email token with the new password to complete the reset. */
|
|
442
|
+
async resetPassword(token, newPassword) {
|
|
443
|
+
const res = await post(
|
|
444
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
445
|
+
"/api/v1/auth-service/password/reset",
|
|
446
|
+
{ token, newPassword }
|
|
447
|
+
);
|
|
448
|
+
if (!res || !res.ok) {
|
|
449
|
+
throw new AuthError("PASSWORD_RESET_FAILED", "Reset failed");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/** Verify the email-address token from the link Sendora sent on signup. */
|
|
453
|
+
async verifyEmail(token) {
|
|
454
|
+
const res = await post(
|
|
455
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
456
|
+
"/api/v1/auth-service/email/verify",
|
|
457
|
+
{ token }
|
|
458
|
+
);
|
|
459
|
+
if (!res || !res.ok) {
|
|
460
|
+
throw new AuthError("EMAIL_VERIFICATION_FAILED", "Verification failed");
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/** Re-send the email-verification email for the currently-signed-in user. */
|
|
464
|
+
async sendVerificationEmail() {
|
|
465
|
+
const headers = this.bearerHeaders();
|
|
466
|
+
if (!headers) throw new AuthError("UNAUTHORIZED", "Sign in before requesting a verification email");
|
|
467
|
+
const res = await post(
|
|
468
|
+
{ apiUrl: this.hooks.apiUrl, publicKey: this.hooks.publicKey, debug: this.hooks.debug },
|
|
469
|
+
"/api/v1/auth-service/email/verify/resend",
|
|
470
|
+
{},
|
|
471
|
+
headers
|
|
472
|
+
);
|
|
473
|
+
if (!res || !res.ok) {
|
|
474
|
+
throw new AuthError("VERIFICATION_RESEND_FAILED", "Failed to send verification email");
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// ============================================================
|
|
354
478
|
// TOTP MFA — enrollment management (Bearer-authenticated)
|
|
355
479
|
// ============================================================
|
|
356
480
|
async enrollMfa() {
|
|
@@ -603,7 +727,7 @@ function decodeJwtPayload(token) {
|
|
|
603
727
|
}
|
|
604
728
|
|
|
605
729
|
// src/index.ts
|
|
606
|
-
var SDK_VERSION = "0.
|
|
730
|
+
var SDK_VERSION = "0.10.0";
|
|
607
731
|
var DEFAULT_API_URL = "https://api.sendoracloud.com";
|
|
608
732
|
var ANON_KEY = "anon_id";
|
|
609
733
|
var USER_ID_KEY = "user_id";
|
|
@@ -652,7 +776,7 @@ var SendoraSDK = class {
|
|
|
652
776
|
}
|
|
653
777
|
/** Initialize the SDK. Must be awaited at app startup before identify/track. */
|
|
654
778
|
async init(config) {
|
|
655
|
-
const rawKey = String(config.publicKey ?? "").trim();
|
|
779
|
+
const rawKey = String(config.apiKey ?? config.publicKey ?? "").trim();
|
|
656
780
|
if (/^sk_/i.test(rawKey) || /sk_(prod|staging|dev|live|test)_/i.test(rawKey)) {
|
|
657
781
|
throw new Error(
|
|
658
782
|
"[sendoracloud] Secret key (sk_*) detected. The client SDK must only receive public keys (pk_*). Secret keys belong on your server."
|
|
@@ -697,6 +821,10 @@ var SendoraSDK = class {
|
|
|
697
821
|
environment: "prod",
|
|
698
822
|
consentedByDefault: true,
|
|
699
823
|
...config,
|
|
824
|
+
// Store rawKey on BOTH fields so any downstream reader that
|
|
825
|
+
// grabbed the legacy `publicKey` field keeps working, and
|
|
826
|
+
// future code reading `apiKey` does too.
|
|
827
|
+
apiKey: rawKey,
|
|
700
828
|
publicKey: rawKey
|
|
701
829
|
};
|
|
702
830
|
this.auth = new Auth({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth. Expo Go compatible, no native modules beyond AsyncStorage.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|