@rakomi/react-native 0.0.0 → 0.1.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/COMPLIANCE.md +60 -0
- package/LICENSE +21 -0
- package/README.md +80 -2
- package/SECURITY.md +206 -0
- package/THIRD-PARTY-NOTICES +11 -0
- package/dist/expo-adapter-CtPCEgue.d.cts +299 -0
- package/dist/expo-adapter-CtPCEgue.d.ts +299 -0
- package/dist/index.cjs +2109 -0
- package/dist/index.d.cts +634 -0
- package/dist/index.d.ts +634 -0
- package/dist/index.js +2004 -0
- package/dist/native/index.cjs +381 -0
- package/dist/native/index.d.cts +113 -0
- package/dist/native/index.d.ts +113 -0
- package/dist/native/index.js +377 -0
- package/package.json +96 -5
- package/sbom.cdx.json +66 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/native/bare-rn-example.ts
|
|
4
|
+
function createBareRnAdapter(options) {
|
|
5
|
+
const servicePrefix = options.keychainServicePrefix ?? "rakomi";
|
|
6
|
+
const crypto = {
|
|
7
|
+
getRandomBytes: async (length) => new Uint8Array(options.quickCrypto.randomBytes(length)),
|
|
8
|
+
digestSha256: async (input) => new Uint8Array(options.quickCrypto.createHash("sha256").update(input).digest())
|
|
9
|
+
};
|
|
10
|
+
const storage = {
|
|
11
|
+
getItem: async (key, opts) => {
|
|
12
|
+
const result = await options.keychain.getGenericPassword({
|
|
13
|
+
service: `${servicePrefix}.${key}`,
|
|
14
|
+
...opts?.requireAuthentication ? { authenticationPrompt: { title: opts.authenticationPrompt ?? "Authenticate" } } : {}
|
|
15
|
+
});
|
|
16
|
+
return result ? result.password : null;
|
|
17
|
+
},
|
|
18
|
+
setItem: async (key, value, opts) => {
|
|
19
|
+
await options.keychain.setGenericPassword("rakomi", value, {
|
|
20
|
+
service: `${servicePrefix}.${key}`,
|
|
21
|
+
accessible: opts?.keychainAccessible ?? "AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY"
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
removeItem: async (key) => {
|
|
25
|
+
await options.keychain.resetGenericPassword({ service: `${servicePrefix}.${key}` });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const browser = {
|
|
29
|
+
openAuthSession: async (authUrl, redirectUri, opts) => {
|
|
30
|
+
const available = await options.inAppBrowser.isAvailable();
|
|
31
|
+
if (!available) return { type: "locked" };
|
|
32
|
+
const result = await options.inAppBrowser.openAuth(authUrl, redirectUri, {
|
|
33
|
+
ephemeralWebSession: opts?.preferEphemeralSession ?? true
|
|
34
|
+
});
|
|
35
|
+
if (result.type === "success" && result.url) return { type: "success", url: result.url };
|
|
36
|
+
if (result.type === "cancel") return { type: "cancel" };
|
|
37
|
+
return { type: "dismiss" };
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const deepLink = {
|
|
41
|
+
getInitialUrl: () => options.linking.getInitialURL(),
|
|
42
|
+
addListener: (listener) => options.linking.addEventListener("url", (e) => listener(e.url))
|
|
43
|
+
};
|
|
44
|
+
const biometric = {
|
|
45
|
+
isAvailable: async () => {
|
|
46
|
+
const sensor = await options.biometrics.isSensorAvailable();
|
|
47
|
+
return sensor.available;
|
|
48
|
+
},
|
|
49
|
+
authenticate: async ({ promptMessage }) => {
|
|
50
|
+
const result = await options.biometrics.simplePrompt({ promptMessage });
|
|
51
|
+
if (result.success) return { success: true };
|
|
52
|
+
return { success: false, reason: "cancelled" };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const appLifecycle = {
|
|
56
|
+
addStateChangeListener: (listener) => options.appState.addEventListener("change", listener),
|
|
57
|
+
getCurrent: () => options.appState.currentState
|
|
58
|
+
};
|
|
59
|
+
const connectivity = {
|
|
60
|
+
isConnected: async () => {
|
|
61
|
+
const state = await options.netInfo.fetch();
|
|
62
|
+
return state.isConnected === true;
|
|
63
|
+
},
|
|
64
|
+
addListener: (listener) => {
|
|
65
|
+
const unsub = options.netInfo.addEventListener((state) => listener(state.isConnected === true));
|
|
66
|
+
return { remove: unsub };
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
crypto,
|
|
71
|
+
storage,
|
|
72
|
+
browser,
|
|
73
|
+
deepLink,
|
|
74
|
+
biometric,
|
|
75
|
+
appLifecycle,
|
|
76
|
+
connectivity
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/native/dpop-prover.ts
|
|
81
|
+
function createNativeDpopProver(options) {
|
|
82
|
+
const mod = options.module;
|
|
83
|
+
return {
|
|
84
|
+
async createProof(input) {
|
|
85
|
+
const proof = await mod.createDpopProof(input.htm, input.htu, input.nonce ?? null);
|
|
86
|
+
if (typeof proof !== "string" || proof.length === 0) {
|
|
87
|
+
throw new Error("native DPoP signer returned an empty proof");
|
|
88
|
+
}
|
|
89
|
+
return proof;
|
|
90
|
+
},
|
|
91
|
+
async jktHint() {
|
|
92
|
+
return mod.dpopJktThumbprint();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/native/expo-adapter.ts
|
|
98
|
+
function createDefaultExpoAdapter(options = {}) {
|
|
99
|
+
const crypto = createExpoCrypto();
|
|
100
|
+
const storage = createExpoSecureStore({ tokenCache: options.tokenCache });
|
|
101
|
+
const browser = createExpoBrowser();
|
|
102
|
+
const deepLink = createExpoDeepLink();
|
|
103
|
+
const biometric = createExpoBiometricGate();
|
|
104
|
+
const appLifecycle = createRnAppLifecycle();
|
|
105
|
+
const connectivity = createNetInfoConnectivity();
|
|
106
|
+
return {
|
|
107
|
+
crypto,
|
|
108
|
+
storage,
|
|
109
|
+
browser,
|
|
110
|
+
deepLink,
|
|
111
|
+
biometric,
|
|
112
|
+
appLifecycle,
|
|
113
|
+
connectivity
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function createExpoCrypto() {
|
|
117
|
+
let mod = null;
|
|
118
|
+
async function load() {
|
|
119
|
+
if (!mod) {
|
|
120
|
+
mod = await import('expo-crypto').catch(() => null);
|
|
121
|
+
if (!mod) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
"@rakomi/react-native: expo-crypto is required for the default adapter. Install it (Expo-managed) or pass a custom nativeAdapter (bare RN)."
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return mod;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
getRandomBytes: async (n) => {
|
|
131
|
+
const m = await load();
|
|
132
|
+
const result = await m.getRandomBytesAsync(n);
|
|
133
|
+
return new Uint8Array(result);
|
|
134
|
+
},
|
|
135
|
+
digestSha256: async (input) => {
|
|
136
|
+
const m = await load();
|
|
137
|
+
let str = "";
|
|
138
|
+
for (let i = 0; i < input.length; i++) str += String.fromCharCode(input[i]);
|
|
139
|
+
const hex = await m.digestStringAsync(
|
|
140
|
+
m.CryptoDigestAlgorithm.SHA256,
|
|
141
|
+
str,
|
|
142
|
+
{ encoding: m.CryptoEncoding.HEX }
|
|
143
|
+
);
|
|
144
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
145
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
146
|
+
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
147
|
+
}
|
|
148
|
+
return bytes;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function createExpoSecureStore(opts) {
|
|
153
|
+
if (opts.tokenCache) {
|
|
154
|
+
const tc = opts.tokenCache;
|
|
155
|
+
return {
|
|
156
|
+
getItem: (key) => tc.getToken(key),
|
|
157
|
+
setItem: async (key, value) => {
|
|
158
|
+
await tc.saveToken(key, value);
|
|
159
|
+
},
|
|
160
|
+
removeItem: (key) => tc.clearToken(key)
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
let mod = null;
|
|
164
|
+
async function load() {
|
|
165
|
+
if (!mod) {
|
|
166
|
+
mod = await import('expo-secure-store').catch(() => null);
|
|
167
|
+
if (!mod) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
"@rakomi/react-native: expo-secure-store is required for the default adapter. Install it (Expo-managed) or pass `tokenCache` / a custom nativeAdapter."
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return mod;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
getItem: async (key, options) => {
|
|
177
|
+
const m = await load();
|
|
178
|
+
return m.getItemAsync(key, {
|
|
179
|
+
requireAuthentication: options?.requireAuthentication,
|
|
180
|
+
authenticationPrompt: options?.authenticationPrompt
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
setItem: async (key, value, options) => {
|
|
184
|
+
const m = await load();
|
|
185
|
+
const accessibility = mapKeychainAccessible(
|
|
186
|
+
m,
|
|
187
|
+
options?.keychainAccessible ?? "AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY"
|
|
188
|
+
);
|
|
189
|
+
await m.setItemAsync(key, value, {
|
|
190
|
+
keychainAccessible: accessibility,
|
|
191
|
+
requireAuthentication: options?.requireAuthentication,
|
|
192
|
+
authenticationPrompt: options?.authenticationPrompt
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
removeItem: async (key) => {
|
|
196
|
+
const m = await load();
|
|
197
|
+
await m.deleteItemAsync(key);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function mapKeychainAccessible(m, level) {
|
|
202
|
+
switch (level) {
|
|
203
|
+
case "WHEN_UNLOCKED":
|
|
204
|
+
return m.WHEN_UNLOCKED;
|
|
205
|
+
case "AFTER_FIRST_UNLOCK":
|
|
206
|
+
return m.AFTER_FIRST_UNLOCK;
|
|
207
|
+
case "WHEN_UNLOCKED_THIS_DEVICE_ONLY":
|
|
208
|
+
return m.WHEN_UNLOCKED_THIS_DEVICE_ONLY;
|
|
209
|
+
case "AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY":
|
|
210
|
+
return m.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY;
|
|
211
|
+
case "WHEN_PASSCODE_SET_THIS_DEVICE_ONLY":
|
|
212
|
+
return m.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function createExpoBrowser() {
|
|
216
|
+
let mod = null;
|
|
217
|
+
async function load() {
|
|
218
|
+
if (!mod) {
|
|
219
|
+
mod = await import('expo-web-browser').catch(() => null);
|
|
220
|
+
if (!mod) {
|
|
221
|
+
throw new Error("@rakomi/react-native: expo-web-browser is required for OAuth flows.");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return mod;
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
openAuthSession: async (authUrl, redirectUri, options) => {
|
|
228
|
+
const m = await load();
|
|
229
|
+
const result = await m.openAuthSessionAsync(authUrl, redirectUri, {
|
|
230
|
+
preferEphemeralSession: options?.preferEphemeralSession ?? true
|
|
231
|
+
});
|
|
232
|
+
if (result.type === "success" && result.url) {
|
|
233
|
+
return { type: "success", url: result.url };
|
|
234
|
+
}
|
|
235
|
+
if (result.type === "cancel") return { type: "cancel" };
|
|
236
|
+
if (result.type === "dismiss") return { type: "dismiss" };
|
|
237
|
+
if (result.type === "locked") return { type: "locked" };
|
|
238
|
+
return { type: "cancel" };
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function createExpoDeepLink() {
|
|
243
|
+
let mod = null;
|
|
244
|
+
async function load() {
|
|
245
|
+
if (!mod) {
|
|
246
|
+
mod = await import('expo-linking').catch(() => null);
|
|
247
|
+
if (!mod) {
|
|
248
|
+
throw new Error("@rakomi/react-native: expo-linking is required for OAuth deep-link callback.");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return mod;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
getInitialUrl: async () => {
|
|
255
|
+
const m = await load();
|
|
256
|
+
return m.getInitialURL();
|
|
257
|
+
},
|
|
258
|
+
addListener: (listener) => {
|
|
259
|
+
let sub = null;
|
|
260
|
+
void load().then((m) => {
|
|
261
|
+
sub = m.addEventListener("url", (event) => listener(event.url));
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
remove: () => {
|
|
265
|
+
sub?.remove();
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function createExpoBiometricGate() {
|
|
272
|
+
let mod = null;
|
|
273
|
+
async function load() {
|
|
274
|
+
if (mod !== null) return mod;
|
|
275
|
+
try {
|
|
276
|
+
mod = await import('expo-local-authentication');
|
|
277
|
+
} catch {
|
|
278
|
+
mod = null;
|
|
279
|
+
}
|
|
280
|
+
return mod;
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
isAvailable: async () => {
|
|
284
|
+
const m = await load();
|
|
285
|
+
if (!m) return false;
|
|
286
|
+
const has = await m.hasHardwareAsync();
|
|
287
|
+
if (!has) return false;
|
|
288
|
+
const enrolled = await m.isEnrolledAsync();
|
|
289
|
+
return enrolled;
|
|
290
|
+
},
|
|
291
|
+
authenticate: async ({ promptMessage, strict = false }) => {
|
|
292
|
+
const m = await load();
|
|
293
|
+
if (!m) return { success: false, reason: "unavailable" };
|
|
294
|
+
const result = await m.authenticateAsync({
|
|
295
|
+
promptMessage,
|
|
296
|
+
disableDeviceFallback: strict,
|
|
297
|
+
fallbackLabel: "Use passcode"
|
|
298
|
+
});
|
|
299
|
+
if (result.success) return { success: true };
|
|
300
|
+
switch (result.error) {
|
|
301
|
+
case "user_cancel":
|
|
302
|
+
case "system_cancel":
|
|
303
|
+
case "app_cancel":
|
|
304
|
+
return { success: false, reason: "cancelled" };
|
|
305
|
+
case "lockout":
|
|
306
|
+
case "lockout_permanent":
|
|
307
|
+
return { success: false, reason: "lockout" };
|
|
308
|
+
case "not_enrolled":
|
|
309
|
+
return { success: false, reason: "not_enrolled" };
|
|
310
|
+
case "not_available":
|
|
311
|
+
case "no_space":
|
|
312
|
+
return { success: false, reason: "unavailable" };
|
|
313
|
+
default:
|
|
314
|
+
return { success: false, reason: "unknown" };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function createRnAppLifecycle() {
|
|
320
|
+
let rn = null;
|
|
321
|
+
let pending = null;
|
|
322
|
+
async function ensure() {
|
|
323
|
+
if (rn) return rn;
|
|
324
|
+
if (!pending) {
|
|
325
|
+
pending = import('react-native').then((m) => {
|
|
326
|
+
rn = m.default ?? m;
|
|
327
|
+
return rn;
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
return pending;
|
|
331
|
+
}
|
|
332
|
+
void ensure().catch(() => void 0);
|
|
333
|
+
return {
|
|
334
|
+
addStateChangeListener: (listener) => {
|
|
335
|
+
let sub = null;
|
|
336
|
+
void ensure().then((r) => {
|
|
337
|
+
sub = r.AppState.addEventListener("change", (next) => {
|
|
338
|
+
listener(coerceAppState(next));
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
return { remove: () => sub?.remove() };
|
|
342
|
+
},
|
|
343
|
+
getCurrent: () => rn ? coerceAppState(rn.AppState.currentState) : "unknown"
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function coerceAppState(value) {
|
|
347
|
+
if (value === "active" || value === "background" || value === "inactive") return value;
|
|
348
|
+
return "unknown";
|
|
349
|
+
}
|
|
350
|
+
function createNetInfoConnectivity() {
|
|
351
|
+
let mod = null;
|
|
352
|
+
async function load() {
|
|
353
|
+
if (mod !== null) return mod;
|
|
354
|
+
try {
|
|
355
|
+
mod = (await import('@react-native-community/netinfo')).default ?? null;
|
|
356
|
+
} catch {
|
|
357
|
+
mod = null;
|
|
358
|
+
}
|
|
359
|
+
return mod;
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
isConnected: async () => {
|
|
363
|
+
const m = await load();
|
|
364
|
+
if (!m) return true;
|
|
365
|
+
const state = await m.fetch();
|
|
366
|
+
return state.isConnected ?? true;
|
|
367
|
+
},
|
|
368
|
+
addListener: (listener) => {
|
|
369
|
+
let unsubscribe = null;
|
|
370
|
+
void load().then((m) => {
|
|
371
|
+
if (!m) return;
|
|
372
|
+
unsubscribe = m.addEventListener((state) => listener(state.isConnected ?? true));
|
|
373
|
+
});
|
|
374
|
+
return { remove: () => unsubscribe?.() };
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
exports.createBareRnAdapter = createBareRnAdapter;
|
|
380
|
+
exports.createDefaultExpoAdapter = createDefaultExpoAdapter;
|
|
381
|
+
exports.createNativeDpopProver = createNativeDpopProver;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { N as NativeAuthAdapter } from '../expo-adapter-CtPCEgue.cjs';
|
|
2
|
+
export { A as AppLifecycle, n as AppStateSubscription, a as AppStateValue, b as AttestationVerifier, B as BackgroundTask, c as BiometricGate, d as BiometricResult, e as BrowserAuthSessionOptions, f as BrowserAuthSessionResult, C as ConnectivityProvider, g as CreateDefaultExpoAdapterOptions, h as CreateNativeDpopProverOptions, i as DeepLinkProvider, o as DeepLinkSubscription, j as DpopProofInput, D as DpopProver, k as NativeDpopModuleSpec, p as NetInfoSubscription, P as ParClient, S as SystemBrowser, T as TokenCache, l as createDefaultExpoAdapter, m as createNativeDpopProver } from '../expo-adapter-CtPCEgue.cjs';
|
|
3
|
+
import '@rakomi/sdk-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reference implementation: bare React Native `NativeAuthAdapter`.
|
|
7
|
+
*
|
|
8
|
+
* For consumers NOT on Expo. Wires the same contract the default Expo adapter
|
|
9
|
+
* implements (`createDefaultExpoAdapter`) but using bare-RN-friendly community
|
|
10
|
+
* packages:
|
|
11
|
+
*
|
|
12
|
+
* - `react-native-keychain` for secure storage
|
|
13
|
+
* - `react-native-inappbrowser-reborn` for system browser (Custom Tabs / SFSafariViewController)
|
|
14
|
+
* - `react-native-quick-crypto` for `getRandomBytes` + `digestSha256`
|
|
15
|
+
* - `react-native/Linking` for deep-link callback ingest
|
|
16
|
+
* - `react-native-biometrics` for biometric gate
|
|
17
|
+
* - `react-native/AppState` for app-lifecycle transitions
|
|
18
|
+
* - `@react-native-community/netinfo` for connectivity
|
|
19
|
+
*
|
|
20
|
+
* **Not a default export** bare-RN consumers MUST call `createBareRnAdapter`
|
|
21
|
+
* explicitly and pass the result via `<RakomiProvider nativeAdapter={...}>`. We
|
|
22
|
+
* keep this in `src/native/` as documentation/reference; the actual implementation
|
|
23
|
+
* stubs are intentional — `NotImplementedError` thrown lines indicate where the
|
|
24
|
+
* bare-RN package import goes. Replace the throws with the real package binding.
|
|
25
|
+
*
|
|
26
|
+
* Why stubbed: bare-RN packages have install-time native code that can't be
|
|
27
|
+
* imported at SDK build time. Consumer's bundler links them at app build.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface CreateBareRnAdapterOptions {
|
|
31
|
+
/** Required injection — `react-native-quick-crypto` named exports. */
|
|
32
|
+
quickCrypto: {
|
|
33
|
+
randomBytes(length: number): Uint8Array;
|
|
34
|
+
createHash(alg: 'sha256'): {
|
|
35
|
+
update(b: Uint8Array): {
|
|
36
|
+
digest(): Uint8Array;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/** Required injection — `react-native-keychain` namespace. */
|
|
41
|
+
keychain: {
|
|
42
|
+
setGenericPassword(username: string, password: string, options: {
|
|
43
|
+
service: string;
|
|
44
|
+
accessible?: string;
|
|
45
|
+
}): Promise<unknown>;
|
|
46
|
+
getGenericPassword(options: {
|
|
47
|
+
service: string;
|
|
48
|
+
authenticationPrompt?: {
|
|
49
|
+
title: string;
|
|
50
|
+
};
|
|
51
|
+
}): Promise<{
|
|
52
|
+
username: string;
|
|
53
|
+
password: string;
|
|
54
|
+
} | false>;
|
|
55
|
+
resetGenericPassword(options: {
|
|
56
|
+
service: string;
|
|
57
|
+
}): Promise<boolean>;
|
|
58
|
+
};
|
|
59
|
+
/** Required injection — `react-native-inappbrowser-reborn` namespace. */
|
|
60
|
+
inAppBrowser: {
|
|
61
|
+
isAvailable(): Promise<boolean>;
|
|
62
|
+
openAuth(url: string, redirectUri: string, options?: {
|
|
63
|
+
ephemeralWebSession?: boolean;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
type: 'success' | 'cancel' | 'dismiss';
|
|
66
|
+
url?: string;
|
|
67
|
+
}>;
|
|
68
|
+
};
|
|
69
|
+
/** Required injection — `react-native-biometrics` instance. */
|
|
70
|
+
biometrics: {
|
|
71
|
+
isSensorAvailable(): Promise<{
|
|
72
|
+
available: boolean;
|
|
73
|
+
biometryType?: string;
|
|
74
|
+
}>;
|
|
75
|
+
simplePrompt(options: {
|
|
76
|
+
promptMessage: string;
|
|
77
|
+
cancelButtonText?: string;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
success: boolean;
|
|
80
|
+
error?: string;
|
|
81
|
+
}>;
|
|
82
|
+
};
|
|
83
|
+
/** Required injection — `react-native/Linking` namespace. */
|
|
84
|
+
linking: {
|
|
85
|
+
getInitialURL(): Promise<string | null>;
|
|
86
|
+
addEventListener(event: 'url', handler: (e: {
|
|
87
|
+
url: string;
|
|
88
|
+
}) => void): {
|
|
89
|
+
remove(): void;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
/** Required injection — `react-native/AppState` namespace. */
|
|
93
|
+
appState: {
|
|
94
|
+
currentState: 'active' | 'background' | 'inactive' | 'unknown';
|
|
95
|
+
addEventListener(event: 'change', handler: (state: 'active' | 'background' | 'inactive' | 'unknown') => void): {
|
|
96
|
+
remove(): void;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/** Required injection — `@react-native-community/netinfo` namespace. */
|
|
100
|
+
netInfo: {
|
|
101
|
+
fetch(): Promise<{
|
|
102
|
+
isConnected: boolean | null;
|
|
103
|
+
}>;
|
|
104
|
+
addEventListener(handler: (state: {
|
|
105
|
+
isConnected: boolean | null;
|
|
106
|
+
}) => void): () => void;
|
|
107
|
+
};
|
|
108
|
+
/** Tenant-scoped Keychain service prefix. Default `'rakomi'`. */
|
|
109
|
+
keychainServicePrefix?: string;
|
|
110
|
+
}
|
|
111
|
+
declare function createBareRnAdapter(options: CreateBareRnAdapterOptions): NativeAuthAdapter;
|
|
112
|
+
|
|
113
|
+
export { type CreateBareRnAdapterOptions, NativeAuthAdapter, createBareRnAdapter };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { N as NativeAuthAdapter } from '../expo-adapter-CtPCEgue.js';
|
|
2
|
+
export { A as AppLifecycle, n as AppStateSubscription, a as AppStateValue, b as AttestationVerifier, B as BackgroundTask, c as BiometricGate, d as BiometricResult, e as BrowserAuthSessionOptions, f as BrowserAuthSessionResult, C as ConnectivityProvider, g as CreateDefaultExpoAdapterOptions, h as CreateNativeDpopProverOptions, i as DeepLinkProvider, o as DeepLinkSubscription, j as DpopProofInput, D as DpopProver, k as NativeDpopModuleSpec, p as NetInfoSubscription, P as ParClient, S as SystemBrowser, T as TokenCache, l as createDefaultExpoAdapter, m as createNativeDpopProver } from '../expo-adapter-CtPCEgue.js';
|
|
3
|
+
import '@rakomi/sdk-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reference implementation: bare React Native `NativeAuthAdapter`.
|
|
7
|
+
*
|
|
8
|
+
* For consumers NOT on Expo. Wires the same contract the default Expo adapter
|
|
9
|
+
* implements (`createDefaultExpoAdapter`) but using bare-RN-friendly community
|
|
10
|
+
* packages:
|
|
11
|
+
*
|
|
12
|
+
* - `react-native-keychain` for secure storage
|
|
13
|
+
* - `react-native-inappbrowser-reborn` for system browser (Custom Tabs / SFSafariViewController)
|
|
14
|
+
* - `react-native-quick-crypto` for `getRandomBytes` + `digestSha256`
|
|
15
|
+
* - `react-native/Linking` for deep-link callback ingest
|
|
16
|
+
* - `react-native-biometrics` for biometric gate
|
|
17
|
+
* - `react-native/AppState` for app-lifecycle transitions
|
|
18
|
+
* - `@react-native-community/netinfo` for connectivity
|
|
19
|
+
*
|
|
20
|
+
* **Not a default export** bare-RN consumers MUST call `createBareRnAdapter`
|
|
21
|
+
* explicitly and pass the result via `<RakomiProvider nativeAdapter={...}>`. We
|
|
22
|
+
* keep this in `src/native/` as documentation/reference; the actual implementation
|
|
23
|
+
* stubs are intentional — `NotImplementedError` thrown lines indicate where the
|
|
24
|
+
* bare-RN package import goes. Replace the throws with the real package binding.
|
|
25
|
+
*
|
|
26
|
+
* Why stubbed: bare-RN packages have install-time native code that can't be
|
|
27
|
+
* imported at SDK build time. Consumer's bundler links them at app build.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface CreateBareRnAdapterOptions {
|
|
31
|
+
/** Required injection — `react-native-quick-crypto` named exports. */
|
|
32
|
+
quickCrypto: {
|
|
33
|
+
randomBytes(length: number): Uint8Array;
|
|
34
|
+
createHash(alg: 'sha256'): {
|
|
35
|
+
update(b: Uint8Array): {
|
|
36
|
+
digest(): Uint8Array;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/** Required injection — `react-native-keychain` namespace. */
|
|
41
|
+
keychain: {
|
|
42
|
+
setGenericPassword(username: string, password: string, options: {
|
|
43
|
+
service: string;
|
|
44
|
+
accessible?: string;
|
|
45
|
+
}): Promise<unknown>;
|
|
46
|
+
getGenericPassword(options: {
|
|
47
|
+
service: string;
|
|
48
|
+
authenticationPrompt?: {
|
|
49
|
+
title: string;
|
|
50
|
+
};
|
|
51
|
+
}): Promise<{
|
|
52
|
+
username: string;
|
|
53
|
+
password: string;
|
|
54
|
+
} | false>;
|
|
55
|
+
resetGenericPassword(options: {
|
|
56
|
+
service: string;
|
|
57
|
+
}): Promise<boolean>;
|
|
58
|
+
};
|
|
59
|
+
/** Required injection — `react-native-inappbrowser-reborn` namespace. */
|
|
60
|
+
inAppBrowser: {
|
|
61
|
+
isAvailable(): Promise<boolean>;
|
|
62
|
+
openAuth(url: string, redirectUri: string, options?: {
|
|
63
|
+
ephemeralWebSession?: boolean;
|
|
64
|
+
}): Promise<{
|
|
65
|
+
type: 'success' | 'cancel' | 'dismiss';
|
|
66
|
+
url?: string;
|
|
67
|
+
}>;
|
|
68
|
+
};
|
|
69
|
+
/** Required injection — `react-native-biometrics` instance. */
|
|
70
|
+
biometrics: {
|
|
71
|
+
isSensorAvailable(): Promise<{
|
|
72
|
+
available: boolean;
|
|
73
|
+
biometryType?: string;
|
|
74
|
+
}>;
|
|
75
|
+
simplePrompt(options: {
|
|
76
|
+
promptMessage: string;
|
|
77
|
+
cancelButtonText?: string;
|
|
78
|
+
}): Promise<{
|
|
79
|
+
success: boolean;
|
|
80
|
+
error?: string;
|
|
81
|
+
}>;
|
|
82
|
+
};
|
|
83
|
+
/** Required injection — `react-native/Linking` namespace. */
|
|
84
|
+
linking: {
|
|
85
|
+
getInitialURL(): Promise<string | null>;
|
|
86
|
+
addEventListener(event: 'url', handler: (e: {
|
|
87
|
+
url: string;
|
|
88
|
+
}) => void): {
|
|
89
|
+
remove(): void;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
/** Required injection — `react-native/AppState` namespace. */
|
|
93
|
+
appState: {
|
|
94
|
+
currentState: 'active' | 'background' | 'inactive' | 'unknown';
|
|
95
|
+
addEventListener(event: 'change', handler: (state: 'active' | 'background' | 'inactive' | 'unknown') => void): {
|
|
96
|
+
remove(): void;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
/** Required injection — `@react-native-community/netinfo` namespace. */
|
|
100
|
+
netInfo: {
|
|
101
|
+
fetch(): Promise<{
|
|
102
|
+
isConnected: boolean | null;
|
|
103
|
+
}>;
|
|
104
|
+
addEventListener(handler: (state: {
|
|
105
|
+
isConnected: boolean | null;
|
|
106
|
+
}) => void): () => void;
|
|
107
|
+
};
|
|
108
|
+
/** Tenant-scoped Keychain service prefix. Default `'rakomi'`. */
|
|
109
|
+
keychainServicePrefix?: string;
|
|
110
|
+
}
|
|
111
|
+
declare function createBareRnAdapter(options: CreateBareRnAdapterOptions): NativeAuthAdapter;
|
|
112
|
+
|
|
113
|
+
export { type CreateBareRnAdapterOptions, NativeAuthAdapter, createBareRnAdapter };
|