@rownd/react-native 0.1.1

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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/android/build.gradle +59 -0
  4. package/android/src/main/AndroidManifest.xml +4 -0
  5. package/android/src/main/java/com/reactnative/ReactNativePackage.java +22 -0
  6. package/android/src/main/java/com/reactnative/ReactNativeViewManager.java +31 -0
  7. package/ios/ReactNative.xcodeproj/project.pbxproj +282 -0
  8. package/ios/ReactNative.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
  9. package/ios/ReactNativeViewManager.m +34 -0
  10. package/lib/commonjs/assets/images/checkmark--filled.svg +12 -0
  11. package/lib/commonjs/assets/images/email-verify-waiting.svg +36 -0
  12. package/lib/commonjs/assets/images/phone-verify-waiting.svg +26 -0
  13. package/lib/commonjs/components/AuthenticatedComponent.js +35 -0
  14. package/lib/commonjs/components/AuthenticatedComponent.js.map +1 -0
  15. package/lib/commonjs/components/AutoSigninDialog.js +119 -0
  16. package/lib/commonjs/components/AutoSigninDialog.js.map +1 -0
  17. package/lib/commonjs/components/DefaultContext.js +269 -0
  18. package/lib/commonjs/components/DefaultContext.js.map +1 -0
  19. package/lib/commonjs/components/GlobalContext.js +340 -0
  20. package/lib/commonjs/components/GlobalContext.js.map +1 -0
  21. package/lib/commonjs/components/RowndComponents.js +29 -0
  22. package/lib/commonjs/components/RowndComponents.js.map +1 -0
  23. package/lib/commonjs/components/RowndProvider.js +55 -0
  24. package/lib/commonjs/components/RowndProvider.js.map +1 -0
  25. package/lib/commonjs/components/SignIn.js +622 -0
  26. package/lib/commonjs/components/SignIn.js.map +1 -0
  27. package/lib/commonjs/data/actions.js +26 -0
  28. package/lib/commonjs/data/actions.js.map +1 -0
  29. package/lib/commonjs/hooks/api.js +157 -0
  30. package/lib/commonjs/hooks/api.js.map +1 -0
  31. package/lib/commonjs/hooks/debounce.js +38 -0
  32. package/lib/commonjs/hooks/debounce.js.map +1 -0
  33. package/lib/commonjs/hooks/fingerprint.js +176 -0
  34. package/lib/commonjs/hooks/fingerprint.js.map +1 -0
  35. package/lib/commonjs/hooks/index.js +48 -0
  36. package/lib/commonjs/hooks/index.js.map +1 -0
  37. package/lib/commonjs/hooks/interval.js +31 -0
  38. package/lib/commonjs/hooks/interval.js.map +1 -0
  39. package/lib/commonjs/hooks/nav.js +39 -0
  40. package/lib/commonjs/hooks/nav.js.map +1 -0
  41. package/lib/commonjs/hooks/rownd.js +163 -0
  42. package/lib/commonjs/hooks/rownd.js.map +1 -0
  43. package/lib/commonjs/index.js +32 -0
  44. package/lib/commonjs/index.js.map +1 -0
  45. package/lib/commonjs/index.tsx.bak +26 -0
  46. package/lib/commonjs/types.js +2 -0
  47. package/lib/commonjs/types.js.map +1 -0
  48. package/lib/commonjs/utils/config.js +28 -0
  49. package/lib/commonjs/utils/config.js.map +1 -0
  50. package/lib/commonjs/utils/events.js +57 -0
  51. package/lib/commonjs/utils/events.js.map +1 -0
  52. package/lib/commonjs/utils/form.js +46 -0
  53. package/lib/commonjs/utils/form.js.map +1 -0
  54. package/lib/commonjs/utils/queue.js +117 -0
  55. package/lib/commonjs/utils/queue.js.map +1 -0
  56. package/lib/commonjs/utils/storage.js +15 -0
  57. package/lib/commonjs/utils/storage.js.map +1 -0
  58. package/lib/commonjs/utils/tailwind.js +17 -0
  59. package/lib/commonjs/utils/tailwind.js.map +1 -0
  60. package/lib/commonjs/utils/tokens.js +35 -0
  61. package/lib/commonjs/utils/tokens.js.map +1 -0
  62. package/lib/commonjs/utils/user-data.js +21 -0
  63. package/lib/commonjs/utils/user-data.js.map +1 -0
  64. package/lib/module/assets/images/checkmark--filled.svg +12 -0
  65. package/lib/module/assets/images/email-verify-waiting.svg +36 -0
  66. package/lib/module/assets/images/phone-verify-waiting.svg +26 -0
  67. package/lib/module/components/AuthenticatedComponent.js +24 -0
  68. package/lib/module/components/AuthenticatedComponent.js.map +1 -0
  69. package/lib/module/components/AutoSigninDialog.js +100 -0
  70. package/lib/module/components/AutoSigninDialog.js.map +1 -0
  71. package/lib/module/components/DefaultContext.js +244 -0
  72. package/lib/module/components/DefaultContext.js.map +1 -0
  73. package/lib/module/components/GlobalContext.js +318 -0
  74. package/lib/module/components/GlobalContext.js.map +1 -0
  75. package/lib/module/components/RowndComponents.js +14 -0
  76. package/lib/module/components/RowndComponents.js.map +1 -0
  77. package/lib/module/components/RowndProvider.js +39 -0
  78. package/lib/module/components/RowndProvider.js.map +1 -0
  79. package/lib/module/components/SignIn.js +593 -0
  80. package/lib/module/components/SignIn.js.map +1 -0
  81. package/lib/module/data/actions.js +19 -0
  82. package/lib/module/data/actions.js.map +1 -0
  83. package/lib/module/hooks/api.js +138 -0
  84. package/lib/module/hooks/api.js.map +1 -0
  85. package/lib/module/hooks/debounce.js +29 -0
  86. package/lib/module/hooks/debounce.js.map +1 -0
  87. package/lib/module/hooks/fingerprint.js +157 -0
  88. package/lib/module/hooks/fingerprint.js.map +1 -0
  89. package/lib/module/hooks/index.js +7 -0
  90. package/lib/module/hooks/index.js.map +1 -0
  91. package/lib/module/hooks/interval.js +23 -0
  92. package/lib/module/hooks/interval.js.map +1 -0
  93. package/lib/module/hooks/nav.js +30 -0
  94. package/lib/module/hooks/nav.js.map +1 -0
  95. package/lib/module/hooks/rownd.js +148 -0
  96. package/lib/module/hooks/rownd.js.map +1 -0
  97. package/lib/module/index.js +6 -0
  98. package/lib/module/index.js.map +1 -0
  99. package/lib/module/index.tsx.bak +26 -0
  100. package/lib/module/types.js +2 -0
  101. package/lib/module/types.js.map +1 -0
  102. package/lib/module/utils/config.js +17 -0
  103. package/lib/module/utils/config.js.map +1 -0
  104. package/lib/module/utils/events.js +45 -0
  105. package/lib/module/utils/events.js.map +1 -0
  106. package/lib/module/utils/form.js +34 -0
  107. package/lib/module/utils/form.js.map +1 -0
  108. package/lib/module/utils/queue.js +109 -0
  109. package/lib/module/utils/queue.js.map +1 -0
  110. package/lib/module/utils/storage.js +6 -0
  111. package/lib/module/utils/storage.js.map +1 -0
  112. package/lib/module/utils/tailwind.js +5 -0
  113. package/lib/module/utils/tailwind.js.map +1 -0
  114. package/lib/module/utils/tokens.js +24 -0
  115. package/lib/module/utils/tokens.js.map +1 -0
  116. package/lib/module/utils/user-data.js +14 -0
  117. package/lib/module/utils/user-data.js.map +1 -0
  118. package/lib/typescript/example2/App.d.ts +11 -0
  119. package/lib/typescript/src/components/AuthenticatedComponent.d.ts +7 -0
  120. package/lib/typescript/src/components/AutoSigninDialog.d.ts +1 -0
  121. package/lib/typescript/src/components/DefaultContext.d.ts +12 -0
  122. package/lib/typescript/src/components/GlobalContext.d.ts +111 -0
  123. package/lib/typescript/src/components/RowndComponents.d.ts +1 -0
  124. package/lib/typescript/src/components/RowndProvider.d.ts +8 -0
  125. package/lib/typescript/src/components/SignIn.d.ts +1 -0
  126. package/lib/typescript/src/data/actions.d.ts +20 -0
  127. package/lib/typescript/src/hooks/api.d.ts +12 -0
  128. package/lib/typescript/src/hooks/debounce.d.ts +5 -0
  129. package/lib/typescript/src/hooks/fingerprint.d.ts +12 -0
  130. package/lib/typescript/src/hooks/index.d.ts +6 -0
  131. package/lib/typescript/src/hooks/interval.d.ts +2 -0
  132. package/lib/typescript/src/hooks/nav.d.ts +6 -0
  133. package/lib/typescript/src/hooks/rownd.d.ts +37 -0
  134. package/lib/typescript/src/index.d.ts +4 -0
  135. package/lib/typescript/src/types.d.ts +26 -0
  136. package/lib/typescript/src/utils/config.d.ts +18 -0
  137. package/lib/typescript/src/utils/events.d.ts +22 -0
  138. package/lib/typescript/src/utils/form.d.ts +17 -0
  139. package/lib/typescript/src/utils/queue.d.ts +21 -0
  140. package/lib/typescript/src/utils/storage.d.ts +3 -0
  141. package/lib/typescript/src/utils/tailwind.d.ts +2 -0
  142. package/lib/typescript/src/utils/tokens.d.ts +4 -0
  143. package/lib/typescript/src/utils/user-data.d.ts +3 -0
  144. package/lib/typescript/tailwind.config.d.ts +10 -0
  145. package/package.json +177 -0
  146. package/react-native.podspec +19 -0
  147. package/src/assets/images/checkmark--filled.svg +12 -0
  148. package/src/assets/images/email-verify-waiting.svg +36 -0
  149. package/src/assets/images/phone-verify-waiting.svg +26 -0
  150. package/src/components/AuthenticatedComponent.tsx +30 -0
  151. package/src/components/AutoSigninDialog.tsx +125 -0
  152. package/src/components/DefaultContext.tsx +278 -0
  153. package/src/components/GlobalContext.tsx +485 -0
  154. package/src/components/RowndComponents.tsx +21 -0
  155. package/src/components/RowndProvider.tsx +56 -0
  156. package/src/components/SignIn.tsx +770 -0
  157. package/src/data/actions.ts +21 -0
  158. package/src/hooks/api.ts +163 -0
  159. package/src/hooks/debounce.ts +36 -0
  160. package/src/hooks/fingerprint.ts +217 -0
  161. package/src/hooks/index.ts +7 -0
  162. package/src/hooks/interval.ts +25 -0
  163. package/src/hooks/nav.tsx +29 -0
  164. package/src/hooks/rownd.ts +184 -0
  165. package/src/index.tsx +6 -0
  166. package/src/index.tsx.bak +26 -0
  167. package/src/types.ts +27 -0
  168. package/src/utils/config.ts +36 -0
  169. package/src/utils/events.ts +54 -0
  170. package/src/utils/form.tsx +64 -0
  171. package/src/utils/queue.ts +75 -0
  172. package/src/utils/storage.ts +7 -0
  173. package/src/utils/tailwind.ts +6 -0
  174. package/src/utils/tokens.ts +26 -0
  175. package/src/utils/user-data.ts +15 -0
@@ -0,0 +1,21 @@
1
+ export enum ActionType {
2
+ SET_CONTAINER_VISIBLE = 'SET_CONTAINER_VISIBLE',
3
+ CHANGE_ROUTE = 'CHANGE_ROUTE',
4
+ LOGIN_SUCCESS = 'LOGIN_SUCCESS',
5
+ LOAD_USER = 'LOAD_USER',
6
+ REFRESH_TOKEN = 'REFRESH_TOKEN',
7
+ SIGN_OUT = 'SIGN_OUT',
8
+ UPDATE_LOCAL_ACLS = 'UPDATE_LOCAL_ACLS',
9
+ SET_USER_DATA_FIELD = 'SET_USER_DATA_FIELD',
10
+ SET_USER_DATA = 'SET_USER_DATA',
11
+ SET_REFRESH_USER_DATA = 'SET_REFRESH_USER_DATA',
12
+ LOAD_STATE = 'LOAD_STATE',
13
+ SET_SECTION = 'SET_SECTION',
14
+ SET_IS_SAVING_USER_DATA = 'SET_IS_SAVING_USER_DATA',
15
+ SET_APP_CONFIG = 'SET_APP_CONFIG',
16
+ }
17
+
18
+ export type TAction = {
19
+ type: ActionType;
20
+ payload?: any;
21
+ };
@@ -0,0 +1,163 @@
1
+ import ky from 'ky';
2
+ import jwt_decode, { JwtPayload } from 'jwt-decode';
3
+ import path from 'path';
4
+
5
+ import { useGlobalContext, GlobalState } from '../components/GlobalContext';
6
+ import AutoQueue from '../utils/queue';
7
+ import { useRef, useEffect } from 'react';
8
+ import { Platform } from 'react-native';
9
+ import { ActionType } from '../data/actions';
10
+
11
+ const packageJson = require(path.join(__dirname, '../../package.json'));
12
+
13
+ type RefreshTokenResp = {
14
+ access_token: string;
15
+ refresh_token: string;
16
+ };
17
+
18
+ const refreshQueue = new AutoQueue<RefreshTokenResp>();
19
+
20
+ export const DEFAULT_USER_AGENT = `Rownd SDK for React Native/${packageJson.version} (Language: TypeScript/JavaScript; Platform=${Platform.OS};)`;
21
+
22
+ export default function useApi() {
23
+ const { state, dispatch } = useGlobalContext();
24
+
25
+ const authRef = useRef({
26
+ access_token: state.auth.access_token,
27
+ refresh_token: state.auth.refresh_token,
28
+ });
29
+
30
+ useEffect(() => {
31
+ authRef.current = {
32
+ access_token: state.auth.access_token,
33
+ refresh_token: state.auth.refresh_token,
34
+ };
35
+ }, [state.auth.access_token, state.auth.refresh_token]);
36
+
37
+ function isNewAccessTokenNeeded(request?: Request) {
38
+ // stateCopy = stateCopy || state;
39
+ // Skip requests that don't need authentication
40
+ if (
41
+ (!!request && !request?.headers.get('authorization')) ||
42
+ !authRef.current?.access_token
43
+ ) {
44
+ return false;
45
+ }
46
+
47
+ const tokenPayload: JwtPayload = jwt_decode(authRef.current?.access_token);
48
+
49
+ // Shave 5 minutes off the token expiration to account for clock skew
50
+ const tokenExpiration = (tokenPayload.exp! - 5 * 60) * 1000;
51
+ if (tokenExpiration > Date.now()) {
52
+ return false; // shouldn't be expired
53
+ }
54
+
55
+ return true;
56
+ }
57
+
58
+ async function _newAccessTokenFromRefreshToken(
59
+ this: AutoQueue<RefreshTokenResp>,
60
+ stateCopy?: GlobalState
61
+ ) {
62
+ stateCopy = stateCopy || state;
63
+ if (this?._cache?.resp) {
64
+ // logger.log('using cached refresh response');
65
+ return this._cache.resp;
66
+ }
67
+
68
+ try {
69
+ // logger.log('requesting new refresh token');
70
+ const resp: RefreshTokenResp = await ky
71
+ .post(`${stateCopy.config?.apiUrl}/hub/auth/token`, {
72
+ json: {
73
+ refresh_token: stateCopy.auth?.refresh_token,
74
+ },
75
+ })
76
+ .json();
77
+
78
+ this._cache.resp = resp;
79
+
80
+ // Update local cache ref immediately to prevent stale auth checks
81
+ authRef.current = {
82
+ access_token: resp.access_token,
83
+ refresh_token: resp.refresh_token,
84
+ };
85
+
86
+ dispatch({
87
+ type: ActionType.REFRESH_TOKEN,
88
+ payload: resp,
89
+ });
90
+
91
+ return resp;
92
+ } catch (err) {
93
+ dispatch({
94
+ type: ActionType.SIGN_OUT,
95
+ });
96
+
97
+ throw err;
98
+ }
99
+ }
100
+
101
+ async function newAccessTokenFromRefreshToken(
102
+ stateCopy?: GlobalState
103
+ ): Promise<RefreshTokenResp> {
104
+ return await refreshQueue.enqueue(
105
+ _newAccessTokenFromRefreshToken.bind(refreshQueue, stateCopy)
106
+ );
107
+ }
108
+
109
+ const client = useRef(
110
+ ky.extend({
111
+ prefixUrl: state.config?.apiUrl,
112
+ headers: {
113
+ 'Content-Type': 'application/json',
114
+ 'User-Agent': DEFAULT_USER_AGENT,
115
+ },
116
+ retry: {
117
+ limit: 2,
118
+ statusCodes: [401, 408, 429, 500, 502, 503, 504],
119
+ },
120
+ hooks: {
121
+ beforeRequest: [
122
+ // Auto-refresh tokens
123
+ async (request) => {
124
+ // Skip requests that don't need authentication
125
+ if (!isNewAccessTokenNeeded(request)) {
126
+ return;
127
+ }
128
+
129
+ const tokenResp: RefreshTokenResp =
130
+ await newAccessTokenFromRefreshToken();
131
+
132
+ request.headers.set(
133
+ 'Authorization',
134
+ `Bearer ${tokenResp.access_token}`
135
+ );
136
+ },
137
+ ],
138
+ beforeRetry: [
139
+ async ({ request /*, options, error, retryCount*/ }) => {
140
+ // Skip requests that don't need authentication
141
+ if (!isNewAccessTokenNeeded(request)) {
142
+ return;
143
+ }
144
+
145
+ const tokenResp: RefreshTokenResp =
146
+ await newAccessTokenFromRefreshToken();
147
+
148
+ request.headers.set(
149
+ 'Authorization',
150
+ `Bearer ${tokenResp.access_token}`
151
+ );
152
+ },
153
+ ],
154
+ },
155
+ })
156
+ ).current;
157
+
158
+ return {
159
+ client,
160
+ newAccessTokenFromRefreshToken,
161
+ isNewAccessTokenNeeded,
162
+ };
163
+ }
@@ -0,0 +1,36 @@
1
+ import { debounce } from 'debounce';
2
+ import { useMemo, useEffect, useRef } from 'react';
3
+
4
+ export default function useDebounce(cb: any, delay: any) {
5
+ const immediate = false;
6
+ const inputsRef = useRef(cb);
7
+ const isMounted = useIsMounted();
8
+ useEffect(() => {
9
+ inputsRef.current = { cb, delay };
10
+ });
11
+
12
+ return useMemo(
13
+ () =>
14
+ debounce(
15
+ (...args: any) => {
16
+ // Don't execute callback, if (1) component in the meanwhile
17
+ // has been unmounted or (2) delay has changed
18
+ if (inputsRef.current.delay === delay && isMounted())
19
+ inputsRef.current.cb(...args);
20
+ },
21
+ delay,
22
+ immediate
23
+ ),
24
+ [delay, immediate, isMounted]
25
+ );
26
+ }
27
+
28
+ function useIsMounted() {
29
+ const isMountedRef = useRef(true);
30
+ useEffect(() => {
31
+ return () => {
32
+ isMountedRef.current = false;
33
+ };
34
+ }, []);
35
+ return () => isMountedRef.current;
36
+ }
@@ -0,0 +1,217 @@
1
+ import DeviceInfo from 'react-native-device-info';
2
+ import { useEffect, useCallback } from 'react';
3
+ import useApi from './api';
4
+ import { useGlobalContext } from '../components/GlobalContext';
5
+ import storage from '../utils/storage';
6
+ import { sha256 } from 'react-native-sha256';
7
+
8
+ interface IFingerprint {
9
+ message: string;
10
+ hash: string;
11
+ challenge: string;
12
+ }
13
+
14
+ interface IChallenge {
15
+ key: string;
16
+ value: string;
17
+ }
18
+
19
+ let isFingerprintingInProgress = false;
20
+
21
+ export default function () {
22
+ const { state } = useGlobalContext();
23
+ const { client: api } = useApi();
24
+
25
+ /**
26
+ * Computes hashes for all possible lookup fields values coupled with the app ID
27
+ * @param appId
28
+ * @param rawLookupValues
29
+ * @returns array of challenge hashes
30
+ */
31
+ async function computePossibleChallengeLookupValues(
32
+ appId: string,
33
+ rawLookupValues: string[]
34
+ ): Promise<string[]> {
35
+ const lookupHashes = [];
36
+ for (const value of rawLookupValues) {
37
+ lookupHashes.push(await sha256(`${appId}:${value}`));
38
+ }
39
+ return lookupHashes;
40
+ }
41
+
42
+ /**
43
+ * Looks for a challenge in storage based on presented lookup values
44
+ */
45
+ const getChallengeIfPresent = useCallback(
46
+ async (
47
+ appId: string | undefined,
48
+ challengeLookupValues: string[]
49
+ ): Promise<IChallenge | null> => {
50
+ if (!appId) {
51
+ return null;
52
+ }
53
+
54
+ const challenges: Record<string, string[]> = JSON.parse(
55
+ storage.getString('challenges') || '{}'
56
+ );
57
+
58
+ if (!Object.keys(challenges).length) {
59
+ return null;
60
+ }
61
+
62
+ let challengeKey = '';
63
+ let challengeValue = '';
64
+ for (const value of challengeLookupValues) {
65
+ const hash = await sha256(`${appId}:${value}`);
66
+ const challenge = Object.entries(challenges).find(([, hashes]) =>
67
+ hashes.includes(hash)
68
+ );
69
+
70
+ if (challenge) {
71
+ challengeKey = hash;
72
+ challengeValue = challenge[0];
73
+ break;
74
+ }
75
+ }
76
+
77
+ if (!challengeKey || !challengeValue) {
78
+ return null;
79
+ }
80
+
81
+ return {
82
+ key: challengeKey,
83
+ value: challengeValue,
84
+ };
85
+ },
86
+ []
87
+ );
88
+
89
+ const getFingerprint = useCallback(async () => {
90
+ const visitorId = DeviceInfo.getDeviceId();
91
+
92
+ return {
93
+ visitorId,
94
+ };
95
+ }, []);
96
+
97
+ const registerFingerprint = useCallback(async () => {
98
+ if (
99
+ !state.auth.access_token ||
100
+ !state.auth.is_verified_user ||
101
+ !state.app.id ||
102
+ isFingerprintingInProgress
103
+ ) {
104
+ return;
105
+ }
106
+
107
+ isFingerprintingInProgress = true;
108
+
109
+ try {
110
+ // Check for existing challenge
111
+ const challengeEntry = await getChallengeIfPresent(
112
+ state.app.id,
113
+ [state.user?.data?.email, state.user?.data?.phone_number].filter(
114
+ Boolean
115
+ )
116
+ );
117
+
118
+ const fingerprint = await getFingerprint();
119
+ const payload: IFingerprint = await api
120
+ .post('hub/auth/fingerprints', {
121
+ headers: {
122
+ Authorization: `Bearer ${state.auth.access_token}`,
123
+ },
124
+ json: {
125
+ hash: fingerprint.visitorId,
126
+ challenge: challengeEntry?.value || null, // Might exist from a previous run, in which case this request will be a no-op
127
+ },
128
+ })
129
+ .json();
130
+
131
+ if (payload.challenge === challengeEntry?.key) {
132
+ // This is a no-op
133
+ console.debug('Fingerprint already registered');
134
+ return;
135
+ }
136
+
137
+ // Save the challenge for future sign-ins if we don't have one already or we got a new one from the server
138
+ const challengeLookupHashes = await computePossibleChallengeLookupValues(
139
+ state.app.id,
140
+ [state.user?.data?.email, state.user?.data?.phone_number].filter(
141
+ Boolean
142
+ )
143
+ );
144
+ if (payload.challenge && challengeLookupHashes.length > 0) {
145
+ const challenges = JSON.parse(storage.getString('challenges') || '{}');
146
+ storage.set(
147
+ 'challenges',
148
+ JSON.stringify({
149
+ ...challenges,
150
+ [payload.challenge]: challengeLookupHashes,
151
+ })
152
+ );
153
+ }
154
+ } catch (err) {
155
+ console.warn('Failed to register fingerprint', err);
156
+ } finally {
157
+ isFingerprintingInProgress = false;
158
+ }
159
+ }, [
160
+ api,
161
+ state.app.id,
162
+ state.auth.access_token,
163
+ state.auth.is_verified_user,
164
+ state.user?.data?.email,
165
+ state.user?.data?.phone_number,
166
+ getChallengeIfPresent,
167
+ getFingerprint,
168
+ ]);
169
+
170
+ const clearFingerprint = useCallback((challenge: string) => {
171
+ const challenges: Record<string, string[]> = JSON.parse(
172
+ storage.getString('challenges') || '{}'
173
+ );
174
+ delete challenges[challenge];
175
+ storage.set('challenges', JSON.stringify(challenges));
176
+ }, []);
177
+
178
+ useEffect(() => {
179
+ if (!state.auth.access_token) {
180
+ return;
181
+ }
182
+
183
+ (async () => {
184
+ // If already fingerprinted, don't try again
185
+ const existingChallenge = await getChallengeIfPresent(
186
+ state.app.id,
187
+ [state.user?.data?.email, state.user?.data?.phone_number].filter(
188
+ Boolean
189
+ )
190
+ );
191
+
192
+ // Don't need to re-register a fingerprint if we already have one registered
193
+ if (existingChallenge) {
194
+ console.debug(
195
+ 'Found existing challenge, so not requesting fingerprint registration.'
196
+ );
197
+ return;
198
+ }
199
+
200
+ // We have an access token, so we can use it to get the fingerprint
201
+ registerFingerprint();
202
+ })();
203
+ }, [
204
+ getChallengeIfPresent,
205
+ registerFingerprint,
206
+ state.app.id,
207
+ state.auth.access_token,
208
+ state.user?.data?.email,
209
+ state.user?.data?.phone_number,
210
+ ]);
211
+
212
+ return {
213
+ getFingerprint,
214
+ getChallengeIfPresent,
215
+ clearFingerprint,
216
+ };
217
+ }
@@ -0,0 +1,7 @@
1
+ import useApi from './api';
2
+ import useDebounce from './debounce';
3
+ import useInterval from './interval';
4
+ import useNav from './nav';
5
+ import useDeviceFingerprint from './fingerprint';
6
+
7
+ export { useApi, useDebounce, useInterval, useNav, useDeviceFingerprint };
@@ -0,0 +1,25 @@
1
+ import { useEffect, useLayoutEffect, useRef } from 'react';
2
+
3
+ function useInterval(callback: () => void, delay: number | null) {
4
+ const savedCallback = useRef(callback);
5
+
6
+ // Remember the latest callback if it changes.
7
+ useLayoutEffect(() => {
8
+ savedCallback.current = callback;
9
+ }, [callback]);
10
+
11
+ // Set up the interval.
12
+ useEffect(() => {
13
+ // Don't schedule if no delay is specified.
14
+ // Note: 0 is a valid value for delay.
15
+ if (!delay && delay !== 0) {
16
+ return;
17
+ }
18
+
19
+ const id = setInterval(() => savedCallback.current(), delay);
20
+
21
+ return () => clearInterval(id);
22
+ }, [delay]);
23
+ }
24
+
25
+ export default useInterval;
@@ -0,0 +1,29 @@
1
+ import { useGlobalContext } from '../components/GlobalContext';
2
+ import { ActionType } from '../data/actions';
3
+
4
+ interface INavOpts {
5
+ route?: string;
6
+ hide?: boolean;
7
+ }
8
+
9
+ export default function useNav() {
10
+ const { dispatch } = useGlobalContext();
11
+
12
+ return ({ route, hide }: INavOpts) => {
13
+ if (hide) {
14
+ dispatch({
15
+ type: ActionType.SET_CONTAINER_VISIBLE,
16
+ payload: {
17
+ isVisible: false,
18
+ },
19
+ });
20
+ } else if (route) {
21
+ dispatch({
22
+ type: ActionType.CHANGE_ROUTE,
23
+ payload: {
24
+ current_route: route,
25
+ },
26
+ });
27
+ }
28
+ };
29
+ }
@@ -0,0 +1,184 @@
1
+ import { useGlobalContext } from '../components/GlobalContext';
2
+ import { ActionType } from '../data/actions';
3
+ import useApi from './api';
4
+ import { events, EventType } from '../utils/events';
5
+
6
+ export type TRowndContext = {
7
+ requestSignIn: (opts?: RequestSignInOpts) => void;
8
+ signOut: () => void;
9
+ getAccessToken: (opts?: GetAccessTokenOpts) => Promise<string | null>;
10
+ is_authenticated: boolean;
11
+ is_initializing: boolean;
12
+ access_token: string | null;
13
+ auth: AuthContext;
14
+ user: UserContext;
15
+ };
16
+
17
+ type AuthContext = {
18
+ access_token: string | null;
19
+ app_id: string | null;
20
+ is_verified_user?: boolean;
21
+ };
22
+
23
+ type UserContext = {
24
+ data: {
25
+ id?: string;
26
+ email?: string | null;
27
+ phone?: string | null;
28
+ [key: string]: any;
29
+ };
30
+ redacted_fields: string[];
31
+ set: (data: Record<string, any>) => Promise<Record<string, any>>;
32
+ setValue: (key: string, value: any) => Promise<Record<string, any>>;
33
+ };
34
+
35
+ type RequestSignInOpts = {
36
+ identifier?: string;
37
+ auto_sign_in?: boolean;
38
+ init_data?: Record<string, any>;
39
+ post_login_redirect?: string;
40
+ };
41
+
42
+ type GetAccessTokenOpts = {
43
+ waitForToken?: boolean;
44
+ };
45
+
46
+ // const RowndContext = createContext<TRowndContext | null>(null);
47
+
48
+ export function useRownd(): TRowndContext {
49
+ const { state, dispatch } = useGlobalContext();
50
+ const { isNewAccessTokenNeeded, newAccessTokenFromRefreshToken } = useApi();
51
+
52
+ const stateCopy = JSON.parse(JSON.stringify(state));
53
+
54
+ const requestSignIn = (opts?: RequestSignInOpts): void => {
55
+ dispatch({
56
+ type: ActionType.CHANGE_ROUTE,
57
+ payload: {
58
+ route: '/account/login',
59
+ options: opts,
60
+ },
61
+ });
62
+ };
63
+
64
+ const signOut = () => {
65
+ dispatch({
66
+ type: ActionType.SIGN_OUT,
67
+ });
68
+ };
69
+
70
+ const getAccessToken = async (
71
+ opts?: GetAccessTokenOpts
72
+ ): Promise<string | null> => {
73
+ const { waitForToken } = opts || {};
74
+ let accessToken = state?.auth.access_token;
75
+
76
+ // Wait for an access token to be available if none exists yet
77
+ if (!accessToken && waitForToken) {
78
+ return new Promise((resolve, _reject) => {
79
+ console.debug('auth_wait: waiting for access token');
80
+ const listener = (evt: any) => {
81
+ console.debug('auth_wait: received access token');
82
+ const data = evt.detail;
83
+ resolve(data.access_token);
84
+ };
85
+
86
+ events.addEventListener(EventType.AUTH, listener, { once: true });
87
+ });
88
+ }
89
+
90
+ if (isNewAccessTokenNeeded(undefined)) {
91
+ const resp = await newAccessTokenFromRefreshToken(stateCopy);
92
+ accessToken = resp.access_token;
93
+ }
94
+
95
+ return accessToken;
96
+ };
97
+
98
+ const setUserData = (
99
+ data: Record<string, any>
100
+ ): Promise<Record<string, any>> => {
101
+ return new Promise((resolve, reject) => {
102
+ console.debug('user_data_save_wait: waiting for data to be saved');
103
+ const listener = (evt: any) => {
104
+ console.debug('user_data_save_wait: received data saved event');
105
+ if (evt.error) {
106
+ return reject(evt.error);
107
+ }
108
+
109
+ resolve(evt.data);
110
+ };
111
+
112
+ events.addEventListener(EventType.USER_DATA_SAVED, listener, {
113
+ once: true,
114
+ });
115
+
116
+ dispatch({
117
+ type: ActionType.SET_USER_DATA,
118
+ payload: {
119
+ data,
120
+ },
121
+ });
122
+ });
123
+ };
124
+
125
+ const setUserDataValue = (
126
+ key: string,
127
+ value: any
128
+ ): Promise<Record<string, any>> => {
129
+ return new Promise((resolve, reject) => {
130
+ console.debug('user_data_save_wait: waiting for data to be saved');
131
+ const listener = (evt: any) => {
132
+ console.debug('user_data_save_wait: received data saved event');
133
+ if (evt.error) {
134
+ return reject(evt.error);
135
+ }
136
+
137
+ resolve(evt.data);
138
+ };
139
+
140
+ events.addEventListener(EventType.USER_DATA_SAVED, listener, {
141
+ once: true,
142
+ });
143
+
144
+ dispatch({
145
+ type: ActionType.SET_USER_DATA_FIELD,
146
+ payload: {
147
+ field: key,
148
+ value,
149
+ },
150
+ });
151
+
152
+ if (value) {
153
+ return;
154
+ }
155
+
156
+ const valueAcl = {
157
+ [key]: {
158
+ shared: true,
159
+ },
160
+ };
161
+
162
+ dispatch({
163
+ type: ActionType.UPDATE_LOCAL_ACLS,
164
+ payload: valueAcl,
165
+ });
166
+ });
167
+ };
168
+
169
+ return {
170
+ requestSignIn,
171
+ signOut,
172
+ getAccessToken,
173
+ is_authenticated: !!state.auth.access_token,
174
+ is_initializing: !!state.app.id,
175
+ access_token: state.auth?.access_token,
176
+ auth: state.auth,
177
+ user: {
178
+ data: state.user?.data,
179
+ redacted_fields: state.user?.redacted,
180
+ set: setUserData,
181
+ setValue: setUserDataValue,
182
+ },
183
+ };
184
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,6 @@
1
+ // import { requestSignIn } from './components/SignIn';
2
+ import { RowndProvider } from './components/RowndProvider';
3
+ import { useRownd } from './hooks/rownd';
4
+ import AuthenticatedComponent from './components/AuthenticatedComponent';
5
+
6
+ export { RowndProvider, useRownd, AuthenticatedComponent };