@sendoracloud/sdk-react-native 0.4.0 → 0.7.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 +452 -79
- package/dist/index.d.cts +111 -15
- package/dist/index.d.ts +111 -15
- package/dist/index.js +452 -79
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -21,9 +21,11 @@ declare class Storage {
|
|
|
21
21
|
set(key: string, value: string): void;
|
|
22
22
|
remove(key: string): void;
|
|
23
23
|
/**
|
|
24
|
-
* Remove every sendora_*-prefixed key
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Remove every sendora_*-prefixed key from both memory AND
|
|
25
|
+
* AsyncStorage. Uses getAllKeys() to find keys this process never
|
|
26
|
+
* cached (e.g. orphaned writes from a prior SDK version that added
|
|
27
|
+
* a key not in our hydrate list). Falls back to cache enumeration
|
|
28
|
+
* if the AsyncStorage backend doesn't expose getAllKeys.
|
|
27
29
|
*/
|
|
28
30
|
clearAll(): Promise<void>;
|
|
29
31
|
}
|
|
@@ -39,12 +41,25 @@ declare class Storage {
|
|
|
39
41
|
* user_id is preserved on success. Throws
|
|
40
42
|
* EmailAlreadyTakenError on 409. Otherwise calls
|
|
41
43
|
* /auth-service/signup as a fresh account.
|
|
42
|
-
* signIn(email, password) — POST /auth-service/login.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
44
|
+
* signIn(email, password) — POST /auth-service/login. Wipes local
|
|
45
|
+
* identity (anonId rotated, userId cleared, tokens dropped)
|
|
46
|
+
* BEFORE the network call so any track() fired during the
|
|
47
|
+
* round-trip can't attach to the prior identity.
|
|
45
48
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
49
|
+
* All public auth ops are serialized through a single-flight chain
|
|
50
|
+
* so a UI double-submit can't mint two anonymous users or interleave
|
|
51
|
+
* a signIn + signOut.
|
|
52
|
+
*
|
|
53
|
+
* Tokens persist via the configured secure-storage adapter (defaults
|
|
54
|
+
* to AsyncStorage; consumers handling sensitive accounts should pass
|
|
55
|
+
* a Keychain / SecureStore-backed adapter via `secureStorage`).
|
|
56
|
+
*
|
|
57
|
+
* Response payloads are validated non-empty before persisting so a
|
|
58
|
+
* malformed/MITM'd response can't install an `id = ""` user.
|
|
59
|
+
*
|
|
60
|
+
* Access token expiry is tracked locally — getAccessToken() returns
|
|
61
|
+
* null past expiry and triggers a single-flight refresh on the next
|
|
62
|
+
* call.
|
|
48
63
|
*/
|
|
49
64
|
|
|
50
65
|
interface AuthUser {
|
|
@@ -74,24 +89,36 @@ interface AuthHooks {
|
|
|
74
89
|
* identity (analytics userId, push token registration, etc). */
|
|
75
90
|
onIdentityChange: (userId: string | null) => void;
|
|
76
91
|
/**
|
|
77
|
-
* Called when the SDK switches from
|
|
78
|
-
*
|
|
79
|
-
* the new user can't carry over
|
|
92
|
+
* Called when the SDK switches from one identity to another via
|
|
93
|
+
* signIn() or non-anonymous signUp(). The parent rotates anonId
|
|
94
|
+
* + drops any pending events so the new user can't carry over
|
|
95
|
+
* orphaned attribution.
|
|
80
96
|
*/
|
|
81
97
|
onAnonymousWipe: () => Promise<void>;
|
|
82
98
|
apiUrl: string;
|
|
83
99
|
publicKey: string;
|
|
100
|
+
debug: boolean;
|
|
84
101
|
}
|
|
85
102
|
declare class Auth {
|
|
86
103
|
private hooks;
|
|
87
104
|
private user;
|
|
88
105
|
private accessToken;
|
|
106
|
+
private accessExpiresAt;
|
|
107
|
+
private inflight;
|
|
108
|
+
private refreshInflight;
|
|
89
109
|
constructor(hooks: AuthHooks);
|
|
90
|
-
/** Re-hydrate session from
|
|
110
|
+
/** Re-hydrate session from storage. Called by the parent SDK
|
|
91
111
|
* during init() after Storage.hydrate() has run. */
|
|
92
112
|
hydrate(): void;
|
|
93
113
|
getCurrentUser(): AuthUser | null;
|
|
94
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Returns a non-expired access token or null. If the cached token
|
|
116
|
+
* is expired but a refresh token exists, transparently refreshes
|
|
117
|
+
* (single-flight — concurrent callers share one network call).
|
|
118
|
+
*/
|
|
119
|
+
getAccessToken(): Promise<string | null>;
|
|
120
|
+
/** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
|
|
121
|
+
getAccessTokenSync(): string | null;
|
|
95
122
|
signInAnonymously(opts?: {
|
|
96
123
|
name?: string;
|
|
97
124
|
metadata?: Record<string, unknown>;
|
|
@@ -100,8 +127,71 @@ declare class Auth {
|
|
|
100
127
|
name?: string;
|
|
101
128
|
metadata?: Record<string, unknown>;
|
|
102
129
|
}): Promise<AuthUser>;
|
|
103
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Sign in with email + password. When the user has confirmed MFA,
|
|
132
|
+
* returns `{mfaRequired, mfaChallengeToken}` instead of an
|
|
133
|
+
* `AuthUser`; caller must follow up with `challengeMfa()`.
|
|
134
|
+
*/
|
|
135
|
+
signIn(email: string, password: string): Promise<AuthUser | {
|
|
136
|
+
mfaRequired: true;
|
|
137
|
+
mfaChallengeToken: string;
|
|
138
|
+
userId: string;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Exchange the MFA challenge token from `signIn` for a real session
|
|
142
|
+
* by submitting a TOTP or recovery code from the user's authenticator.
|
|
143
|
+
*/
|
|
144
|
+
challengeMfa(mfaChallengeToken: string, code: string): Promise<AuthUser>;
|
|
145
|
+
sendMagicLink(email: string): Promise<void>;
|
|
146
|
+
verifyMagicLink(token: string): Promise<AuthUser>;
|
|
147
|
+
enrollMfa(): Promise<{
|
|
148
|
+
secret: string;
|
|
149
|
+
otpauthUrl: string;
|
|
150
|
+
recoveryCodes: string[];
|
|
151
|
+
}>;
|
|
152
|
+
confirmMfa(code: string): Promise<boolean>;
|
|
153
|
+
disableMfa(): Promise<void>;
|
|
154
|
+
listMySessions(): Promise<Array<{
|
|
155
|
+
id: string;
|
|
156
|
+
deviceInfo: string | null;
|
|
157
|
+
ipHash: string | null;
|
|
158
|
+
lastUsedAt: string | null;
|
|
159
|
+
createdAt: string;
|
|
160
|
+
}>>;
|
|
161
|
+
revokeSession(sessionId: string): Promise<void>;
|
|
162
|
+
revokeAllSessions(): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Start OIDC sign-in. Returns the IdP's authorization URL — caller
|
|
165
|
+
* opens it via React Native's `Linking.openURL()` (or
|
|
166
|
+
* `WebBrowser.openAuthSessionAsync` from `expo-web-browser` when
|
|
167
|
+
* inside Expo). The IdP redirects through Sendora's callback to the
|
|
168
|
+
* `returnTo` URL with `#sendora_oidc_token=...` in the fragment.
|
|
169
|
+
*
|
|
170
|
+
* On the customer's app side, `returnTo` MUST be a custom URL scheme
|
|
171
|
+
* registered in Info.plist (iOS) / AndroidManifest.xml intent-filter
|
|
172
|
+
* (Android), e.g. `myapp://oidc-callback`. When the deep link fires,
|
|
173
|
+
* call `consumeSsoFragment(url)` on the captured URL.
|
|
174
|
+
*/
|
|
175
|
+
startSso(returnTo: string): Promise<{
|
|
176
|
+
authorizationUrl: string;
|
|
177
|
+
}>;
|
|
178
|
+
/**
|
|
179
|
+
* Consume an OIDC callback URL captured via React Native's
|
|
180
|
+
* `Linking.addEventListener('url', ...)`. Reads the fragment for
|
|
181
|
+
* `sendora_oidc_token`, exchanges it for a session, and persists.
|
|
182
|
+
* Throws AuthError on `sendora_oidc_error=...` fragment.
|
|
183
|
+
*/
|
|
184
|
+
consumeSsoFragment(callbackUrl: string): Promise<AuthUser>;
|
|
185
|
+
private bearerHeaders;
|
|
186
|
+
private unwrap;
|
|
187
|
+
/**
|
|
188
|
+
* Sign out: wipe local identity FIRST so the user is logged out on
|
|
189
|
+
* device even if the revoke network call hangs (airplane mode, 5xx).
|
|
190
|
+
* Revoke is fire-and-forget after the wipe.
|
|
191
|
+
*/
|
|
104
192
|
signOut(): Promise<void>;
|
|
193
|
+
private serialize;
|
|
194
|
+
private refreshAccessToken;
|
|
105
195
|
private callAuth;
|
|
106
196
|
private persist;
|
|
107
197
|
private wipeLocalIdentity;
|
|
@@ -122,8 +212,10 @@ interface SendoraConfig {
|
|
|
122
212
|
environment?: "prod" | "staging" | "dev";
|
|
123
213
|
/** Optional project id scoping. */
|
|
124
214
|
projectId?: string;
|
|
125
|
-
/** If false, events
|
|
215
|
+
/** If false, events drop until setConsent(true) is called. Default true (mobile apps usually collect OS-level consent). */
|
|
126
216
|
consentedByDefault?: boolean;
|
|
217
|
+
/** Verbose error logging (auth-error codes + Zod field errors). Default false. */
|
|
218
|
+
debug?: boolean;
|
|
127
219
|
}
|
|
128
220
|
type TraitValue = string | number | boolean | null | undefined;
|
|
129
221
|
interface IdentifyTraits {
|
|
@@ -176,6 +268,8 @@ declare class SendoraSDK {
|
|
|
176
268
|
private storage;
|
|
177
269
|
private anonId;
|
|
178
270
|
private userId;
|
|
271
|
+
private consentGranted;
|
|
272
|
+
private debug;
|
|
179
273
|
/** Lazy-initialised in init() once we know the apiUrl + publicKey. */
|
|
180
274
|
auth: Auth;
|
|
181
275
|
/** Initialize the SDK. Must be awaited at app startup before identify/track. */
|
|
@@ -184,6 +278,8 @@ declare class SendoraSDK {
|
|
|
184
278
|
getAnonymousId(): string;
|
|
185
279
|
/** Current identified user id, if any. */
|
|
186
280
|
getUserId(): string | null;
|
|
281
|
+
/** Toggle consent at runtime — when false, events drop instead of fire. */
|
|
282
|
+
setConsent(granted: boolean): void;
|
|
187
283
|
/** Associate a user id + traits with the current anonymous install. */
|
|
188
284
|
identify(userId: string, traits?: IdentifyTraits): void;
|
|
189
285
|
/** Fire a custom product event. */
|
package/dist/index.d.ts
CHANGED
|
@@ -21,9 +21,11 @@ declare class Storage {
|
|
|
21
21
|
set(key: string, value: string): void;
|
|
22
22
|
remove(key: string): void;
|
|
23
23
|
/**
|
|
24
|
-
* Remove every sendora_*-prefixed key
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Remove every sendora_*-prefixed key from both memory AND
|
|
25
|
+
* AsyncStorage. Uses getAllKeys() to find keys this process never
|
|
26
|
+
* cached (e.g. orphaned writes from a prior SDK version that added
|
|
27
|
+
* a key not in our hydrate list). Falls back to cache enumeration
|
|
28
|
+
* if the AsyncStorage backend doesn't expose getAllKeys.
|
|
27
29
|
*/
|
|
28
30
|
clearAll(): Promise<void>;
|
|
29
31
|
}
|
|
@@ -39,12 +41,25 @@ declare class Storage {
|
|
|
39
41
|
* user_id is preserved on success. Throws
|
|
40
42
|
* EmailAlreadyTakenError on 409. Otherwise calls
|
|
41
43
|
* /auth-service/signup as a fresh account.
|
|
42
|
-
* signIn(email, password) — POST /auth-service/login.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
44
|
+
* signIn(email, password) — POST /auth-service/login. Wipes local
|
|
45
|
+
* identity (anonId rotated, userId cleared, tokens dropped)
|
|
46
|
+
* BEFORE the network call so any track() fired during the
|
|
47
|
+
* round-trip can't attach to the prior identity.
|
|
45
48
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
49
|
+
* All public auth ops are serialized through a single-flight chain
|
|
50
|
+
* so a UI double-submit can't mint two anonymous users or interleave
|
|
51
|
+
* a signIn + signOut.
|
|
52
|
+
*
|
|
53
|
+
* Tokens persist via the configured secure-storage adapter (defaults
|
|
54
|
+
* to AsyncStorage; consumers handling sensitive accounts should pass
|
|
55
|
+
* a Keychain / SecureStore-backed adapter via `secureStorage`).
|
|
56
|
+
*
|
|
57
|
+
* Response payloads are validated non-empty before persisting so a
|
|
58
|
+
* malformed/MITM'd response can't install an `id = ""` user.
|
|
59
|
+
*
|
|
60
|
+
* Access token expiry is tracked locally — getAccessToken() returns
|
|
61
|
+
* null past expiry and triggers a single-flight refresh on the next
|
|
62
|
+
* call.
|
|
48
63
|
*/
|
|
49
64
|
|
|
50
65
|
interface AuthUser {
|
|
@@ -74,24 +89,36 @@ interface AuthHooks {
|
|
|
74
89
|
* identity (analytics userId, push token registration, etc). */
|
|
75
90
|
onIdentityChange: (userId: string | null) => void;
|
|
76
91
|
/**
|
|
77
|
-
* Called when the SDK switches from
|
|
78
|
-
*
|
|
79
|
-
* the new user can't carry over
|
|
92
|
+
* Called when the SDK switches from one identity to another via
|
|
93
|
+
* signIn() or non-anonymous signUp(). The parent rotates anonId
|
|
94
|
+
* + drops any pending events so the new user can't carry over
|
|
95
|
+
* orphaned attribution.
|
|
80
96
|
*/
|
|
81
97
|
onAnonymousWipe: () => Promise<void>;
|
|
82
98
|
apiUrl: string;
|
|
83
99
|
publicKey: string;
|
|
100
|
+
debug: boolean;
|
|
84
101
|
}
|
|
85
102
|
declare class Auth {
|
|
86
103
|
private hooks;
|
|
87
104
|
private user;
|
|
88
105
|
private accessToken;
|
|
106
|
+
private accessExpiresAt;
|
|
107
|
+
private inflight;
|
|
108
|
+
private refreshInflight;
|
|
89
109
|
constructor(hooks: AuthHooks);
|
|
90
|
-
/** Re-hydrate session from
|
|
110
|
+
/** Re-hydrate session from storage. Called by the parent SDK
|
|
91
111
|
* during init() after Storage.hydrate() has run. */
|
|
92
112
|
hydrate(): void;
|
|
93
113
|
getCurrentUser(): AuthUser | null;
|
|
94
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Returns a non-expired access token or null. If the cached token
|
|
116
|
+
* is expired but a refresh token exists, transparently refreshes
|
|
117
|
+
* (single-flight — concurrent callers share one network call).
|
|
118
|
+
*/
|
|
119
|
+
getAccessToken(): Promise<string | null>;
|
|
120
|
+
/** Sync read — returns whatever's cached even if expired. Prefer the async variant. */
|
|
121
|
+
getAccessTokenSync(): string | null;
|
|
95
122
|
signInAnonymously(opts?: {
|
|
96
123
|
name?: string;
|
|
97
124
|
metadata?: Record<string, unknown>;
|
|
@@ -100,8 +127,71 @@ declare class Auth {
|
|
|
100
127
|
name?: string;
|
|
101
128
|
metadata?: Record<string, unknown>;
|
|
102
129
|
}): Promise<AuthUser>;
|
|
103
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Sign in with email + password. When the user has confirmed MFA,
|
|
132
|
+
* returns `{mfaRequired, mfaChallengeToken}` instead of an
|
|
133
|
+
* `AuthUser`; caller must follow up with `challengeMfa()`.
|
|
134
|
+
*/
|
|
135
|
+
signIn(email: string, password: string): Promise<AuthUser | {
|
|
136
|
+
mfaRequired: true;
|
|
137
|
+
mfaChallengeToken: string;
|
|
138
|
+
userId: string;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Exchange the MFA challenge token from `signIn` for a real session
|
|
142
|
+
* by submitting a TOTP or recovery code from the user's authenticator.
|
|
143
|
+
*/
|
|
144
|
+
challengeMfa(mfaChallengeToken: string, code: string): Promise<AuthUser>;
|
|
145
|
+
sendMagicLink(email: string): Promise<void>;
|
|
146
|
+
verifyMagicLink(token: string): Promise<AuthUser>;
|
|
147
|
+
enrollMfa(): Promise<{
|
|
148
|
+
secret: string;
|
|
149
|
+
otpauthUrl: string;
|
|
150
|
+
recoveryCodes: string[];
|
|
151
|
+
}>;
|
|
152
|
+
confirmMfa(code: string): Promise<boolean>;
|
|
153
|
+
disableMfa(): Promise<void>;
|
|
154
|
+
listMySessions(): Promise<Array<{
|
|
155
|
+
id: string;
|
|
156
|
+
deviceInfo: string | null;
|
|
157
|
+
ipHash: string | null;
|
|
158
|
+
lastUsedAt: string | null;
|
|
159
|
+
createdAt: string;
|
|
160
|
+
}>>;
|
|
161
|
+
revokeSession(sessionId: string): Promise<void>;
|
|
162
|
+
revokeAllSessions(): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Start OIDC sign-in. Returns the IdP's authorization URL — caller
|
|
165
|
+
* opens it via React Native's `Linking.openURL()` (or
|
|
166
|
+
* `WebBrowser.openAuthSessionAsync` from `expo-web-browser` when
|
|
167
|
+
* inside Expo). The IdP redirects through Sendora's callback to the
|
|
168
|
+
* `returnTo` URL with `#sendora_oidc_token=...` in the fragment.
|
|
169
|
+
*
|
|
170
|
+
* On the customer's app side, `returnTo` MUST be a custom URL scheme
|
|
171
|
+
* registered in Info.plist (iOS) / AndroidManifest.xml intent-filter
|
|
172
|
+
* (Android), e.g. `myapp://oidc-callback`. When the deep link fires,
|
|
173
|
+
* call `consumeSsoFragment(url)` on the captured URL.
|
|
174
|
+
*/
|
|
175
|
+
startSso(returnTo: string): Promise<{
|
|
176
|
+
authorizationUrl: string;
|
|
177
|
+
}>;
|
|
178
|
+
/**
|
|
179
|
+
* Consume an OIDC callback URL captured via React Native's
|
|
180
|
+
* `Linking.addEventListener('url', ...)`. Reads the fragment for
|
|
181
|
+
* `sendora_oidc_token`, exchanges it for a session, and persists.
|
|
182
|
+
* Throws AuthError on `sendora_oidc_error=...` fragment.
|
|
183
|
+
*/
|
|
184
|
+
consumeSsoFragment(callbackUrl: string): Promise<AuthUser>;
|
|
185
|
+
private bearerHeaders;
|
|
186
|
+
private unwrap;
|
|
187
|
+
/**
|
|
188
|
+
* Sign out: wipe local identity FIRST so the user is logged out on
|
|
189
|
+
* device even if the revoke network call hangs (airplane mode, 5xx).
|
|
190
|
+
* Revoke is fire-and-forget after the wipe.
|
|
191
|
+
*/
|
|
104
192
|
signOut(): Promise<void>;
|
|
193
|
+
private serialize;
|
|
194
|
+
private refreshAccessToken;
|
|
105
195
|
private callAuth;
|
|
106
196
|
private persist;
|
|
107
197
|
private wipeLocalIdentity;
|
|
@@ -122,8 +212,10 @@ interface SendoraConfig {
|
|
|
122
212
|
environment?: "prod" | "staging" | "dev";
|
|
123
213
|
/** Optional project id scoping. */
|
|
124
214
|
projectId?: string;
|
|
125
|
-
/** If false, events
|
|
215
|
+
/** If false, events drop until setConsent(true) is called. Default true (mobile apps usually collect OS-level consent). */
|
|
126
216
|
consentedByDefault?: boolean;
|
|
217
|
+
/** Verbose error logging (auth-error codes + Zod field errors). Default false. */
|
|
218
|
+
debug?: boolean;
|
|
127
219
|
}
|
|
128
220
|
type TraitValue = string | number | boolean | null | undefined;
|
|
129
221
|
interface IdentifyTraits {
|
|
@@ -176,6 +268,8 @@ declare class SendoraSDK {
|
|
|
176
268
|
private storage;
|
|
177
269
|
private anonId;
|
|
178
270
|
private userId;
|
|
271
|
+
private consentGranted;
|
|
272
|
+
private debug;
|
|
179
273
|
/** Lazy-initialised in init() once we know the apiUrl + publicKey. */
|
|
180
274
|
auth: Auth;
|
|
181
275
|
/** Initialize the SDK. Must be awaited at app startup before identify/track. */
|
|
@@ -184,6 +278,8 @@ declare class SendoraSDK {
|
|
|
184
278
|
getAnonymousId(): string;
|
|
185
279
|
/** Current identified user id, if any. */
|
|
186
280
|
getUserId(): string | null;
|
|
281
|
+
/** Toggle consent at runtime — when false, events drop instead of fire. */
|
|
282
|
+
setConsent(granted: boolean): void;
|
|
187
283
|
/** Associate a user id + traits with the current anonymous install. */
|
|
188
284
|
identify(userId: string, traits?: IdentifyTraits): void;
|
|
189
285
|
/** Fire a custom product event. */
|