@rownd/react-native 0.2.4 → 2.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.
Files changed (253) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +15 -52
  3. package/android/build.gradle +127 -37
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/AndroidManifest.xml +1 -1
  6. package/android/src/main/java/com/reactnativerowndplugin/RowndPluginModule.kt +98 -0
  7. package/android/src/main/java/com/reactnativerowndplugin/RowndPluginPackage.kt +16 -0
  8. package/ios/{ReactNative.xcodeproj → Rownd.xcodeproj}/project.pbxproj +13 -12
  9. package/ios/Rownd.xcodeproj/project.xcworkspace/contents.xcworkspacedata +4 -0
  10. package/ios/RowndPlugin-Bridging-Header.h +3 -0
  11. package/ios/RowndPlugin.m +26 -0
  12. package/ios/RowndPlugin.swift +83 -0
  13. package/ios/RowndPluginEventEmitter.m +12 -0
  14. package/ios/RowndPluginEventEmitter.swift +19 -0
  15. package/lib/commonjs/components/GlobalContext.js +30 -298
  16. package/lib/commonjs/components/GlobalContext.js.map +1 -1
  17. package/lib/commonjs/components/{BottomSheetTextInput/types.js → GlobalContext.types.js} +1 -1
  18. package/lib/commonjs/components/GlobalContext.types.js.map +1 -0
  19. package/lib/commonjs/constants/action.js +13 -0
  20. package/lib/commonjs/constants/action.js.map +1 -0
  21. package/lib/commonjs/hooks/rownd.js +15 -146
  22. package/lib/commonjs/hooks/rownd.js.map +1 -1
  23. package/lib/commonjs/index.js +2 -12
  24. package/lib/commonjs/index.js.map +1 -1
  25. package/lib/commonjs/reducer/rowndReducer.js +89 -0
  26. package/lib/commonjs/reducer/rowndReducer.js.map +1 -0
  27. package/lib/commonjs/utils/config.js +0 -26
  28. package/lib/commonjs/utils/config.js.map +1 -1
  29. package/lib/commonjs/utils/nativeModule.js +72 -0
  30. package/lib/commonjs/utils/nativeModule.js.map +1 -0
  31. package/lib/module/components/GlobalContext.js +29 -293
  32. package/lib/module/components/GlobalContext.js.map +1 -1
  33. package/lib/module/components/GlobalContext.types.js +2 -0
  34. package/lib/module/components/GlobalContext.types.js.map +1 -0
  35. package/lib/module/constants/action.js +6 -0
  36. package/lib/module/constants/action.js.map +1 -0
  37. package/lib/module/hooks/rownd.js +12 -137
  38. package/lib/module/hooks/rownd.js.map +1 -1
  39. package/lib/module/index.js +2 -4
  40. package/lib/module/index.js.map +1 -1
  41. package/lib/module/reducer/rowndReducer.js +78 -0
  42. package/lib/module/reducer/rowndReducer.js.map +1 -0
  43. package/lib/module/utils/config.js +1 -16
  44. package/lib/module/utils/config.js.map +1 -1
  45. package/lib/module/utils/nativeModule.js +48 -0
  46. package/lib/module/utils/nativeModule.js.map +1 -0
  47. package/lib/typescript/components/GlobalContext.d.ts +13 -0
  48. package/lib/typescript/{src/components/GlobalContext.d.ts → components/GlobalContext.types.d.ts} +9 -35
  49. package/lib/typescript/constants/action.d.ts +7 -0
  50. package/lib/typescript/{src/hooks → hooks}/rownd.d.ts +10 -13
  51. package/lib/typescript/index.d.ts +3 -0
  52. package/lib/typescript/reducer/rowndReducer.d.ts +4 -0
  53. package/lib/typescript/utils/config.d.ts +10 -0
  54. package/lib/typescript/utils/nativeModule.d.ts +10 -0
  55. package/package.json +36 -56
  56. package/rownd-react-native.podspec +36 -0
  57. package/src/components/GlobalContext.tsx +44 -457
  58. package/src/components/GlobalContext.types.ts +105 -0
  59. package/src/constants/action.ts +8 -0
  60. package/src/hooks/rownd.ts +29 -147
  61. package/src/index.tsx +2 -4
  62. package/src/reducer/rowndReducer.ts +78 -0
  63. package/src/utils/config.ts +0 -27
  64. package/src/utils/nativeModule.ts +69 -0
  65. package/android/src/main/java/com/reactnative/ReactNativePackage.java +0 -22
  66. package/android/src/main/java/com/reactnative/ReactNativeViewManager.java +0 -31
  67. package/ios/ReactNativeViewManager.m +0 -34
  68. package/lib/commonjs/assets/images/checkmark--filled.svg +0 -12
  69. package/lib/commonjs/assets/images/email-verify-waiting.svg +0 -36
  70. package/lib/commonjs/assets/images/error-icon-material.svg +0 -1
  71. package/lib/commonjs/assets/images/phone-verify-waiting.svg +0 -26
  72. package/lib/commonjs/components/AuthenticatedComponent.js +0 -35
  73. package/lib/commonjs/components/AuthenticatedComponent.js.map +0 -1
  74. package/lib/commonjs/components/AutoSigninDialog.js +0 -132
  75. package/lib/commonjs/components/AutoSigninDialog.js.map +0 -1
  76. package/lib/commonjs/components/BottomSheetTextInput/BottomSheetTextInput.js +0 -65
  77. package/lib/commonjs/components/BottomSheetTextInput/BottomSheetTextInput.js.map +0 -1
  78. package/lib/commonjs/components/BottomSheetTextInput/index.js +0 -24
  79. package/lib/commonjs/components/BottomSheetTextInput/index.js.map +0 -1
  80. package/lib/commonjs/components/BottomSheetTextInput/types.js.map +0 -1
  81. package/lib/commonjs/components/DarkText.js +0 -35
  82. package/lib/commonjs/components/DarkText.js.map +0 -1
  83. package/lib/commonjs/components/DefaultContext.js +0 -266
  84. package/lib/commonjs/components/DefaultContext.js.map +0 -1
  85. package/lib/commonjs/components/RowndComponents.js +0 -25
  86. package/lib/commonjs/components/RowndComponents.js.map +0 -1
  87. package/lib/commonjs/components/RowndProvider.js +0 -55
  88. package/lib/commonjs/components/RowndProvider.js.map +0 -1
  89. package/lib/commonjs/components/SignIn.js +0 -638
  90. package/lib/commonjs/components/SignIn.js.map +0 -1
  91. package/lib/commonjs/components/images/CheckmarkFilled.js +0 -40
  92. package/lib/commonjs/components/images/CheckmarkFilled.js.map +0 -1
  93. package/lib/commonjs/components/images/EmailVerifyWaiting.js +0 -100
  94. package/lib/commonjs/components/images/EmailVerifyWaiting.js.map +0 -1
  95. package/lib/commonjs/components/images/ErrorIcon.js +0 -31
  96. package/lib/commonjs/components/images/ErrorIcon.js.map +0 -1
  97. package/lib/commonjs/components/images/PhoneVerifyWaiting.js +0 -91
  98. package/lib/commonjs/components/images/PhoneVerifyWaiting.js.map +0 -1
  99. package/lib/commonjs/data/actions.js +0 -26
  100. package/lib/commonjs/data/actions.js.map +0 -1
  101. package/lib/commonjs/hooks/api.js +0 -155
  102. package/lib/commonjs/hooks/api.js.map +0 -1
  103. package/lib/commonjs/hooks/debounce.js +0 -38
  104. package/lib/commonjs/hooks/debounce.js.map +0 -1
  105. package/lib/commonjs/hooks/fingerprint.js +0 -176
  106. package/lib/commonjs/hooks/fingerprint.js.map +0 -1
  107. package/lib/commonjs/hooks/index.js +0 -48
  108. package/lib/commonjs/hooks/index.js.map +0 -1
  109. package/lib/commonjs/hooks/interval.js +0 -31
  110. package/lib/commonjs/hooks/interval.js.map +0 -1
  111. package/lib/commonjs/hooks/nav.js +0 -39
  112. package/lib/commonjs/hooks/nav.js.map +0 -1
  113. package/lib/commonjs/index.tsx.bak +0 -26
  114. package/lib/commonjs/types.js +0 -2
  115. package/lib/commonjs/types.js.map +0 -1
  116. package/lib/commonjs/utils/events.js +0 -57
  117. package/lib/commonjs/utils/events.js.map +0 -1
  118. package/lib/commonjs/utils/form.js +0 -46
  119. package/lib/commonjs/utils/form.js.map +0 -1
  120. package/lib/commonjs/utils/queue.js +0 -117
  121. package/lib/commonjs/utils/queue.js.map +0 -1
  122. package/lib/commonjs/utils/storage.js +0 -15
  123. package/lib/commonjs/utils/storage.js.map +0 -1
  124. package/lib/commonjs/utils/tokens.js +0 -35
  125. package/lib/commonjs/utils/tokens.js.map +0 -1
  126. package/lib/commonjs/utils/user-data.js +0 -21
  127. package/lib/commonjs/utils/user-data.js.map +0 -1
  128. package/lib/module/assets/images/checkmark--filled.svg +0 -12
  129. package/lib/module/assets/images/email-verify-waiting.svg +0 -36
  130. package/lib/module/assets/images/error-icon-material.svg +0 -1
  131. package/lib/module/assets/images/phone-verify-waiting.svg +0 -26
  132. package/lib/module/components/AuthenticatedComponent.js +0 -24
  133. package/lib/module/components/AuthenticatedComponent.js.map +0 -1
  134. package/lib/module/components/AutoSigninDialog.js +0 -110
  135. package/lib/module/components/AutoSigninDialog.js.map +0 -1
  136. package/lib/module/components/BottomSheetTextInput/BottomSheetTextInput.js +0 -52
  137. package/lib/module/components/BottomSheetTextInput/BottomSheetTextInput.js.map +0 -1
  138. package/lib/module/components/BottomSheetTextInput/index.js +0 -5
  139. package/lib/module/components/BottomSheetTextInput/index.js.map +0 -1
  140. package/lib/module/components/BottomSheetTextInput/types.js +0 -2
  141. package/lib/module/components/BottomSheetTextInput/types.js.map +0 -1
  142. package/lib/module/components/DarkText.js +0 -24
  143. package/lib/module/components/DarkText.js.map +0 -1
  144. package/lib/module/components/DefaultContext.js +0 -244
  145. package/lib/module/components/DefaultContext.js.map +0 -1
  146. package/lib/module/components/RowndComponents.js +0 -12
  147. package/lib/module/components/RowndComponents.js.map +0 -1
  148. package/lib/module/components/RowndProvider.js +0 -39
  149. package/lib/module/components/RowndProvider.js.map +0 -1
  150. package/lib/module/components/SignIn.js +0 -610
  151. package/lib/module/components/SignIn.js.map +0 -1
  152. package/lib/module/components/images/CheckmarkFilled.js +0 -27
  153. package/lib/module/components/images/CheckmarkFilled.js.map +0 -1
  154. package/lib/module/components/images/EmailVerifyWaiting.js +0 -87
  155. package/lib/module/components/images/EmailVerifyWaiting.js.map +0 -1
  156. package/lib/module/components/images/ErrorIcon.js +0 -18
  157. package/lib/module/components/images/ErrorIcon.js.map +0 -1
  158. package/lib/module/components/images/PhoneVerifyWaiting.js +0 -78
  159. package/lib/module/components/images/PhoneVerifyWaiting.js.map +0 -1
  160. package/lib/module/data/actions.js +0 -19
  161. package/lib/module/data/actions.js.map +0 -1
  162. package/lib/module/hooks/api.js +0 -135
  163. package/lib/module/hooks/api.js.map +0 -1
  164. package/lib/module/hooks/debounce.js +0 -29
  165. package/lib/module/hooks/debounce.js.map +0 -1
  166. package/lib/module/hooks/fingerprint.js +0 -157
  167. package/lib/module/hooks/fingerprint.js.map +0 -1
  168. package/lib/module/hooks/index.js +0 -7
  169. package/lib/module/hooks/index.js.map +0 -1
  170. package/lib/module/hooks/interval.js +0 -23
  171. package/lib/module/hooks/interval.js.map +0 -1
  172. package/lib/module/hooks/nav.js +0 -30
  173. package/lib/module/hooks/nav.js.map +0 -1
  174. package/lib/module/index.tsx.bak +0 -26
  175. package/lib/module/types.js +0 -2
  176. package/lib/module/types.js.map +0 -1
  177. package/lib/module/utils/events.js +0 -45
  178. package/lib/module/utils/events.js.map +0 -1
  179. package/lib/module/utils/form.js +0 -34
  180. package/lib/module/utils/form.js.map +0 -1
  181. package/lib/module/utils/queue.js +0 -109
  182. package/lib/module/utils/queue.js.map +0 -1
  183. package/lib/module/utils/storage.js +0 -6
  184. package/lib/module/utils/storage.js.map +0 -1
  185. package/lib/module/utils/tokens.js +0 -24
  186. package/lib/module/utils/tokens.js.map +0 -1
  187. package/lib/module/utils/user-data.js +0 -14
  188. package/lib/module/utils/user-data.js.map +0 -1
  189. package/lib/package.json +0 -173
  190. package/lib/typescript/src/components/AuthenticatedComponent.d.ts +0 -7
  191. package/lib/typescript/src/components/AutoSigninDialog.d.ts +0 -2
  192. package/lib/typescript/src/components/BottomSheetTextInput/BottomSheetTextInput.d.ts +0 -4
  193. package/lib/typescript/src/components/BottomSheetTextInput/index.d.ts +0 -2
  194. package/lib/typescript/src/components/BottomSheetTextInput/types.d.ts +0 -3
  195. package/lib/typescript/src/components/DarkText.d.ts +0 -3
  196. package/lib/typescript/src/components/DefaultContext.d.ts +0 -12
  197. package/lib/typescript/src/components/RowndComponents.d.ts +0 -2
  198. package/lib/typescript/src/components/RowndProvider.d.ts +0 -8
  199. package/lib/typescript/src/components/SignIn.d.ts +0 -2
  200. package/lib/typescript/src/components/images/CheckmarkFilled.d.ts +0 -4
  201. package/lib/typescript/src/components/images/EmailVerifyWaiting.d.ts +0 -4
  202. package/lib/typescript/src/components/images/ErrorIcon.d.ts +0 -4
  203. package/lib/typescript/src/components/images/PhoneVerifyWaiting.d.ts +0 -4
  204. package/lib/typescript/src/data/actions.d.ts +0 -20
  205. package/lib/typescript/src/hooks/api.d.ts +0 -12
  206. package/lib/typescript/src/hooks/debounce.d.ts +0 -5
  207. package/lib/typescript/src/hooks/fingerprint.d.ts +0 -12
  208. package/lib/typescript/src/hooks/index.d.ts +0 -6
  209. package/lib/typescript/src/hooks/interval.d.ts +0 -2
  210. package/lib/typescript/src/hooks/nav.d.ts +0 -6
  211. package/lib/typescript/src/index.d.ts +0 -4
  212. package/lib/typescript/src/types.d.ts +0 -26
  213. package/lib/typescript/src/utils/config.d.ts +0 -18
  214. package/lib/typescript/src/utils/events.d.ts +0 -22
  215. package/lib/typescript/src/utils/form.d.ts +0 -18
  216. package/lib/typescript/src/utils/queue.d.ts +0 -21
  217. package/lib/typescript/src/utils/storage.d.ts +0 -3
  218. package/lib/typescript/src/utils/tokens.d.ts +0 -4
  219. package/lib/typescript/src/utils/user-data.d.ts +0 -3
  220. package/react-native.podspec +0 -19
  221. package/src/assets/images/checkmark--filled.svg +0 -12
  222. package/src/assets/images/email-verify-waiting.svg +0 -36
  223. package/src/assets/images/error-icon-material.svg +0 -1
  224. package/src/assets/images/phone-verify-waiting.svg +0 -26
  225. package/src/components/AuthenticatedComponent.tsx +0 -30
  226. package/src/components/AutoSigninDialog.tsx +0 -131
  227. package/src/components/BottomSheetTextInput/BottomSheetTextInput.tsx +0 -57
  228. package/src/components/BottomSheetTextInput/index.ts +0 -5
  229. package/src/components/BottomSheetTextInput/types.ts +0 -3
  230. package/src/components/DarkText.tsx +0 -16
  231. package/src/components/DefaultContext.tsx +0 -278
  232. package/src/components/RowndComponents.tsx +0 -18
  233. package/src/components/RowndProvider.tsx +0 -56
  234. package/src/components/SignIn.tsx +0 -793
  235. package/src/components/images/CheckmarkFilled.tsx +0 -30
  236. package/src/components/images/EmailVerifyWaiting.tsx +0 -95
  237. package/src/components/images/ErrorIcon.tsx +0 -11
  238. package/src/components/images/PhoneVerifyWaiting.tsx +0 -74
  239. package/src/data/actions.ts +0 -21
  240. package/src/hooks/api.ts +0 -162
  241. package/src/hooks/debounce.ts +0 -36
  242. package/src/hooks/fingerprint.ts +0 -217
  243. package/src/hooks/index.ts +0 -7
  244. package/src/hooks/interval.ts +0 -25
  245. package/src/hooks/nav.tsx +0 -29
  246. package/src/index.tsx.bak +0 -26
  247. package/src/types.ts +0 -27
  248. package/src/utils/events.ts +0 -54
  249. package/src/utils/form.tsx +0 -64
  250. package/src/utils/queue.ts +0 -75
  251. package/src/utils/storage.ts +0 -7
  252. package/src/utils/tokens.ts +0 -26
  253. package/src/utils/user-data.ts +0 -15
@@ -1,793 +0,0 @@
1
- import React, {
2
- useCallback,
3
- useEffect,
4
- useMemo,
5
- useReducer,
6
- useRef,
7
- useState,
8
- } from 'react';
9
- import { differenceInMinutes } from 'date-fns';
10
- import {
11
- View,
12
- StyleSheet,
13
- TouchableOpacity,
14
- Image,
15
- ActivityIndicator,
16
- Linking,
17
- Text,
18
- } from 'react-native';
19
- // import Text from './DarkText';
20
- import { SvgCssUri } from 'react-native-svg';
21
- import phone, { PhoneResult } from 'phone';
22
- import jwt_decode from 'jwt-decode';
23
- import {
24
- BottomSheetBackdrop,
25
- BottomSheetBackdropProps,
26
- BottomSheetModal,
27
- } from '@gorhom/bottom-sheet';
28
- import BottomSheetTextInput from './BottomSheetTextInput';
29
- import bottomSheetMeta from '@gorhom/bottom-sheet/package.json';
30
-
31
- import { useApi, useInterval, useNav, useDeviceFingerprint } from '../hooks';
32
- import { useGlobalContext } from './GlobalContext';
33
- import { ActionType } from '../data/actions';
34
- import { renderField } from '../utils/form';
35
-
36
- // Image imports
37
- import ImageEmailVerifyWaiting from './images/EmailVerifyWaiting';
38
- import ImagePhoneVerifyWaiting from './images/PhoneVerifyWaiting';
39
- import ImageCheckmarkFilled from './images/CheckmarkFilled';
40
-
41
- enum LoginStep {
42
- INIT = 'init',
43
- WAITING = 'waiting',
44
- SUCCESS = 'success',
45
- FAILURE = 'failure',
46
- ERROR = 'error',
47
- }
48
-
49
- type LoginInitBody = {
50
- challenge_id: string;
51
- message: string;
52
- auth_tokens?: {
53
- access_token: string;
54
- refresh_token: string;
55
- };
56
- registration_status: string;
57
- init_data?: Record<string, any>;
58
- };
59
-
60
- type LoginSuccessBody = {
61
- access_token: string;
62
- refresh_token: string;
63
- app_user_id: string;
64
- app_id: string;
65
- status: string;
66
- };
67
-
68
- enum LoginVerificationStatus {
69
- PENDING = 'pending',
70
- EXPIRED = 'expired',
71
- VERIFIED = 'verified',
72
- }
73
-
74
- export function SignIn() {
75
- const navTo = useNav();
76
- const { getFingerprint, getChallengeIfPresent, clearFingerprint } =
77
- useDeviceFingerprint();
78
- const { state, dispatch } = useGlobalContext();
79
- const { config, nav, app, user } = state;
80
-
81
- let decodedAccessToken: any;
82
- if (state.auth.access_token) {
83
- decodedAccessToken = jwt_decode(state.auth.access_token);
84
- }
85
-
86
- const [userIdentifier, setUserIdentifier] = useState('');
87
- const [fieldError, setFieldError] = useState<string | null>(null);
88
- const [step, setStep] = useState(
89
- state.auth.access_token &&
90
- decodedAccessToken?.['https://auth.rownd.io/is_verified_user'] !== false
91
- ? LoginStep.SUCCESS
92
- : LoginStep.INIT
93
- );
94
- const [error, setError] = useState('');
95
- const allowedIdentifiers = useMemo(() => ['email', 'phone'], []);
96
- const [requestId, setRequestId] = useState<string | null>(null);
97
- const [loginPollStart, setLoginPollStart] = useState<number | null>(null);
98
- const [isSubmitting, setIsSubmitting] = useState(false);
99
- const [loginType, setLoginType] = useState<'email' | 'phone' | null>(null);
100
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
101
- const [_phoneDetails, setPhoneDetails] = useState<PhoneResult | null>(null); // TODO: For parity with web, need to use `phoneDetails` to update the input visuals
102
- const [isValidUserIdentifier, setIsValidUserIdentifier] = useState(false);
103
- const [requiresAdditionalFields, setRequiresAdditionalFields] = useState(
104
- nav?.options?.init_data
105
- );
106
-
107
- const bottomSheetModalRef = useRef<BottomSheetModal>(null);
108
- useEffect(() => {
109
- if (bottomSheetModalRef.current) {
110
- bottomSheetModalRef.current.present();
111
- }
112
- }, []);
113
-
114
- const addlFieldInit = useCallback(
115
- (currentState: Record<string, string>) => {
116
- const addlFields = app?.config?.hub?.auth?.additional_fields;
117
- const addlInputs =
118
- nav?.options?.init_data || nav?.options?.default_values || {};
119
-
120
- const newState: Record<string, string> = {};
121
- if (addlFields?.length) {
122
- for (const field of addlFields) {
123
- if (field?.options) {
124
- newState[field.name] =
125
- addlInputs?.[field.name] || field.options[0].value;
126
- }
127
- }
128
- }
129
-
130
- return {
131
- ...currentState,
132
- ...newState,
133
- };
134
- },
135
- [
136
- app?.config?.hub?.auth?.additional_fields,
137
- nav?.options?.default_values,
138
- nav?.options?.init_data,
139
- ]
140
- );
141
-
142
- const fieldReducer = useCallback(
143
- (
144
- currentState: any,
145
- action: { type: string; payload?: Record<string, string> }
146
- ) => {
147
- console.log('fieldReducer', action);
148
- switch (action.type) {
149
- case 'reset':
150
- return addlFieldInit(currentState);
151
-
152
- default:
153
- return {
154
- ...currentState,
155
- ...action.payload,
156
- };
157
- }
158
- },
159
- [addlFieldInit]
160
- );
161
-
162
- const [addlFieldValues, addlFieldDispatch] = useReducer(
163
- fieldReducer,
164
- {},
165
- addlFieldInit
166
- );
167
-
168
- const { client: api } = useApi();
169
-
170
- function validateEmail(email: string): boolean {
171
- const re =
172
- /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
173
- const isValid = re.test(String(email).toLowerCase());
174
-
175
- if (!isValid) {
176
- setFieldError('Invalid email address');
177
- return false;
178
- }
179
-
180
- setFieldError(null);
181
- return true;
182
- }
183
-
184
- const isValidPhone = useCallback((): boolean => {
185
- const phoneResult = phone(userIdentifier);
186
- if (!phoneResult.isValid) {
187
- setLoginType(null);
188
- setPhoneDetails(null);
189
- return false;
190
- }
191
-
192
- setLoginType('phone');
193
- setPhoneDetails(phoneResult);
194
- setUserIdentifier(phoneResult.phoneNumber);
195
- return true;
196
- }, [userIdentifier]);
197
-
198
- const isValidEmail = useCallback((): boolean => {
199
- const emailAtIdx = userIdentifier?.indexOf('@');
200
- const emailSuffixIdx = userIdentifier?.substring(emailAtIdx).indexOf('.');
201
- if (
202
- emailAtIdx > 0 &&
203
- emailSuffixIdx > 0 &&
204
- userIdentifier?.substring(emailAtIdx + emailSuffixIdx).length >= 3
205
- ) {
206
- return validateEmail(userIdentifier);
207
- }
208
-
209
- return false;
210
- }, [userIdentifier]);
211
-
212
- const validateInput = useCallback(() => {
213
- const validations = [];
214
- if (allowedIdentifiers.includes('phone')) {
215
- validations.push(isValidPhone);
216
- }
217
-
218
- if (allowedIdentifiers.includes('email')) {
219
- validations.push(isValidEmail);
220
- }
221
-
222
- if (!validations.some((fn) => fn())) {
223
- setIsValidUserIdentifier(false);
224
- } else {
225
- setIsValidUserIdentifier(true);
226
- }
227
- }, [allowedIdentifiers, isValidEmail, isValidPhone]);
228
-
229
- // Fire validation as data changes in field
230
- useEffect(validateInput, [validateInput]);
231
-
232
- const pollLoginStatus = useCallback(async () => {
233
- try {
234
- const resp: LoginSuccessBody = await api
235
- .post(`hub/auth/challenge_status`, {
236
- headers: {
237
- 'x-rownd-app-key': config?.appKey,
238
- },
239
- json: {
240
- challenge_id: requestId,
241
- [loginType === 'phone' ? 'phone' : 'email']: userIdentifier,
242
- },
243
- })
244
- .json();
245
-
246
- let err: any;
247
- switch (resp.status) {
248
- case 'pending':
249
- err = new Error('Login challenge is still pending');
250
- err.code = LoginVerificationStatus.PENDING;
251
- throw err;
252
-
253
- case 'expired':
254
- err = new Error('Login challenge is still pending');
255
- err.code = LoginVerificationStatus.PENDING;
256
- throw err;
257
-
258
- case 'verified':
259
- break;
260
-
261
- default:
262
- err = new Error('Unknown login challenge status');
263
- throw err;
264
- }
265
-
266
- dispatch({
267
- type: ActionType.LOGIN_SUCCESS,
268
- payload: resp,
269
- });
270
-
271
- setStep(LoginStep.SUCCESS);
272
- } catch (err: any) {
273
- // logger.log('login poll error', err);
274
-
275
- // If network error, try again up to 1 minute, else fail
276
- if (!err.code && differenceInMinutes(Date.now(), loginPollStart!) > 0) {
277
- setStep(LoginStep.ERROR);
278
- setError('Network error. Please try again later.');
279
- return;
280
- }
281
-
282
- // If request expires, then fail (assume > 6 mins is a failure/expiration)
283
- if (
284
- (err.status || err.code) &&
285
- differenceInMinutes(Date.now(), loginPollStart!) > 6
286
- ) {
287
- setStep(LoginStep.ERROR);
288
- setError('The sign in request expired.');
289
- return;
290
- }
291
-
292
- if (err.status && err.status >= 400) {
293
- setStep(LoginStep.FAILURE);
294
- setError('Sign in unsuccessful.');
295
- }
296
- }
297
- }, [
298
- api,
299
- config?.appKey,
300
- dispatch,
301
- loginPollStart,
302
- loginType,
303
- requestId,
304
- userIdentifier,
305
- ]);
306
-
307
- // Polling when a login flow is in progress
308
- useInterval(pollLoginStatus, step === LoginStep.WAITING ? 5000 : null);
309
-
310
- // login polling manager
311
- useEffect(() => {
312
- if (step === LoginStep.SUCCESS) {
313
- if (nav?.options?.post_login_redirect || state.config?.postLoginUrl) {
314
- Linking.openURL(
315
- nav?.options?.post_login_redirect || state.config?.postLoginUrl
316
- );
317
- }
318
-
319
- dispatch({
320
- type: ActionType.CHANGE_ROUTE,
321
- payload: {
322
- route: '/',
323
- },
324
- });
325
-
326
- // Reset modal state if this user is unverified, since we'll need to re-submit at some point
327
- if (!state.auth.is_verified_user) {
328
- setStep(LoginStep.INIT);
329
- }
330
- }
331
- }, [
332
- dispatch,
333
- nav?.options?.post_login_redirect,
334
- pollLoginStatus,
335
- state.auth.is_verified_user,
336
- state.config?.postLoginUrl,
337
- step,
338
- ]);
339
-
340
- const initSignIn = useCallback(async () => {
341
- if (step === LoginStep.WAITING) {
342
- return;
343
- }
344
-
345
- // Validation
346
- if (fieldError) {
347
- return;
348
- }
349
-
350
- const payload = {
351
- [loginType === 'phone' ? 'phone' : 'email']: userIdentifier,
352
- return_url:
353
- nav?.options?.post_login_redirect || state.config?.postLoginUrl,
354
- user_data: Object.values(user.data).some(
355
- (f) => f !== null && f !== undefined
356
- )
357
- ? user.data
358
- : {}, // Include user.data if at least one field is defined
359
- };
360
-
361
- // Set the user_id to the application's default user id format if it is defined
362
- if (app.config?.default_user_id_format) {
363
- payload.user_id = app.config?.default_user_id_format;
364
- }
365
-
366
- if (requiresAdditionalFields) {
367
- payload.user_data = {
368
- ...payload.user_data,
369
- ...addlFieldValues,
370
- };
371
- }
372
-
373
- // Submission
374
- try {
375
- setIsSubmitting(true);
376
-
377
- // Get the browser fingerprint for future sign-ins that don't require re-verification
378
- const fingerprint = await getFingerprint();
379
- const challengeEntry = await getChallengeIfPresent(app.id, [
380
- userIdentifier,
381
- ]);
382
- payload.fingerprint = {
383
- hash: fingerprint.visitorId,
384
- challenge: challengeEntry?.value,
385
- };
386
-
387
- const resp: LoginInitBody = await api
388
- .post('hub/auth/init', {
389
- headers: {
390
- 'x-rownd-app-key': config?.appKey,
391
- },
392
- json: payload,
393
- })
394
- .json();
395
-
396
- // This will only be true when a user is signing in for the very first time and
397
- // the app allows unverified users OR the user was successfully fingerprinted.
398
- if (resp.auth_tokens) {
399
- dispatch({
400
- type: ActionType.LOGIN_SUCCESS,
401
- payload: {
402
- ...resp.auth_tokens,
403
- app_id: app.id,
404
- },
405
- });
406
-
407
- setStep(LoginStep.SUCCESS);
408
- return;
409
- } else if (payload?.fingerprint?.challenge) {
410
- // The fingerprint is probably expired, so delete the challenge for re-registration
411
- try {
412
- clearFingerprint(payload.fingerprint.challenge);
413
- } catch (err) {
414
- // no-op, but not likely to throw anyway
415
- }
416
- }
417
-
418
- setRequestId(resp.challenge_id);
419
- setStep(LoginStep.WAITING);
420
- setLoginPollStart(Date.now());
421
- } catch (err: any) {
422
- // logger.error(err);
423
-
424
- if (err.response.status === 400) {
425
- setStep(LoginStep.INIT);
426
- setRequiresAdditionalFields(true);
427
- return;
428
- }
429
-
430
- setStep(LoginStep.ERROR);
431
- setError(err.message);
432
- } finally {
433
- setIsSubmitting(false);
434
- }
435
- }, [
436
- step,
437
- fieldError,
438
- loginType,
439
- userIdentifier,
440
- nav?.options?.post_login_redirect,
441
- state.config?.postLoginUrl,
442
- user.data,
443
- app.config?.default_user_id_format,
444
- app.id,
445
- requiresAdditionalFields,
446
- addlFieldValues,
447
- getFingerprint,
448
- getChallengeIfPresent,
449
- api,
450
- config?.appKey,
451
- dispatch,
452
- clearFingerprint,
453
- ]);
454
-
455
- const launchRowndWebsite = () => {
456
- Linking.openURL('https://rownd.io');
457
- };
458
-
459
- const handleAddlFieldChange = (evt: Event) => {
460
- const target = evt.target as HTMLInputElement;
461
- const { value, name } = target;
462
-
463
- addlFieldDispatch({
464
- type: 'default',
465
- payload: {
466
- [name]: value,
467
- },
468
- });
469
- };
470
-
471
- const handleClose = useCallback(() => {
472
- setTimeout(() => {
473
- navTo({ hide: true });
474
- }, 150);
475
- }, [navTo]);
476
-
477
- const snapPoints = useMemo(() => ['25%', '70%'], []);
478
-
479
- const renderBackdrop = useCallback(
480
- (cbProps: BottomSheetBackdropProps) => (
481
- <BottomSheetBackdrop {...cbProps} pressBehavior="close" />
482
- ),
483
- []
484
- );
485
-
486
- let extraBottomSheetProps: any = {};
487
- if (bottomSheetMeta.version.startsWith('4')) {
488
- extraBottomSheetProps.keyboardBehavior = 'fillParent';
489
- extraBottomSheetProps.android_keyboardInputMode = 'adjustResize';
490
- extraBottomSheetProps.enablePanDownToClose =
491
- state?.nav?.options?.type === 'error';
492
- }
493
-
494
- return (
495
- <BottomSheetModal
496
- snapPoints={snapPoints}
497
- index={1}
498
- backdropComponent={renderBackdrop}
499
- onDismiss={handleClose}
500
- style={styles.bottomSheet}
501
- ref={bottomSheetModalRef}
502
- {...extraBottomSheetProps}
503
- >
504
- <View style={styles.innerContainer}>
505
- {step === LoginStep.INIT && (
506
- <>
507
- {app?.config?.hub?.auth?.show_app_icon &&
508
- app?.icon &&
509
- (app.icon_content_type === 'image/svg+xml' ? (
510
- <SvgCssUri uri={app.icon} style={styles.appLogo} />
511
- ) : (
512
- <Image style={styles.appLogo} source={{ uri: app.icon }} />
513
- ))}
514
- <Text style={styles.dialogHeading}>Sign in or sign up</Text>
515
- <Text style={styles.inputLabel}>Email or phone number</Text>
516
- <BottomSheetTextInput
517
- style={styles.identifierInput}
518
- placeholder="Enter here"
519
- keyboardType="email-address"
520
- textContentType="emailAddress"
521
- returnKeyLabel="Sign in"
522
- returnKeyType="go"
523
- enablesReturnKeyAutomatically={true}
524
- autoCapitalize="none"
525
- onChangeText={(text: string) => setUserIdentifier(text.trim())}
526
- onBlur={validateInput}
527
- // value={userIdentifier}
528
- onSubmitEditing={initSignIn}
529
- autoCorrect={false}
530
- />
531
- {requiresAdditionalFields &&
532
- app?.config?.hub?.auth?.additional_fields.map((field) => {
533
- return renderField({
534
- ...field,
535
- value: addlFieldValues[field.name] || '',
536
- [['input', 'text', 'tel', 'email'].includes(field.type)
537
- ? 'onInput'
538
- : 'onChange']: handleAddlFieldChange,
539
- });
540
- })}
541
- <TouchableOpacity
542
- disabled={!isValidUserIdentifier || isSubmitting}
543
- onPress={initSignIn}
544
- >
545
- <View
546
- style={{
547
- ...styles.button,
548
- ...(!isValidUserIdentifier && styles.buttonDisabled),
549
- ...(isSubmitting && styles.buttonSubmitting),
550
- }}
551
- >
552
- <View style={styles.buttonText}>
553
- <Text
554
- style={
555
- isValidUserIdentifier
556
- ? styles.buttonContent
557
- : {
558
- ...styles.buttonContent,
559
- ...styles.buttonDisabledText,
560
- }
561
- }
562
- >
563
- {isSubmitting && (
564
- <ActivityIndicator
565
- size="small"
566
- color="#efefef"
567
- style={styles.loadingIndicator}
568
- />
569
- )}
570
- {isSubmitting ? 'Just a sec...' : 'Continue'}
571
- </Text>
572
- </View>
573
- </View>
574
- </TouchableOpacity>
575
- <Text style={styles.signInNoticeText}>
576
- By continuing, you're agreeing to the terms of service that govern
577
- this app and to receive email or text messages for verification
578
- purposes.
579
- </Text>
580
- </>
581
- )}
582
-
583
- {step === LoginStep.WAITING && (
584
- <>
585
- <Text style={styles.dialogHeading}>
586
- Thanks! Verify your{' '}
587
- {loginType === 'phone' ? 'phone number' : 'email'} to finish
588
- </Text>
589
- <Text style={styles.py6}>
590
- Click the link in the message we just sent to{' '}
591
- <Text style={styles.italic}>{userIdentifier}</Text> to verify and
592
- finish.
593
- <Text
594
- style={[styles.link]}
595
- onPress={() => setStep(LoginStep.INIT)}
596
- >
597
- &nbsp;Re-send message
598
- </Text>
599
- </Text>
600
-
601
- <View style={styles.signInVerificationImage}>
602
- {loginType === 'phone' ? (
603
- <ImagePhoneVerifyWaiting />
604
- ) : (
605
- <ImageEmailVerifyWaiting />
606
- )}
607
- </View>
608
-
609
- <TouchableOpacity
610
- style={[styles.button]}
611
- onPress={() => setStep(LoginStep.INIT)}
612
- >
613
- <Text style={styles.buttonContent}>
614
- Try a different{' '}
615
- {loginType === 'phone' ? 'phone number' : 'email'}
616
- </Text>
617
- </TouchableOpacity>
618
- </>
619
- )}
620
-
621
- {step === LoginStep.SUCCESS && (
622
- <>
623
- <View style={styles.signInVerificationImage}>
624
- <ImageCheckmarkFilled />
625
- </View>
626
- </>
627
- )}
628
-
629
- {step === LoginStep.FAILURE && (
630
- <>
631
- <Text style={styles.textBase}>Whoops, that didn't work!</Text>
632
- <TouchableOpacity
633
- style={styles.button}
634
- onPress={() => setStep(LoginStep.INIT)}
635
- >
636
- <Text style={styles.buttonContent}>Try again</Text>
637
- </TouchableOpacity>
638
- </>
639
- )}
640
-
641
- {step === LoginStep.ERROR && (
642
- <>
643
- <Text style={styles.textBase}>
644
- An error occurred while signing you in.
645
- </Text>
646
- {!!error && <Text style={styles.textRose800}>{error}</Text>}
647
- <TouchableOpacity
648
- style={styles.button}
649
- onPress={() => setStep(LoginStep.INIT)}
650
- >
651
- <Text style={styles.buttonContent}>Try again</Text>
652
- </TouchableOpacity>
653
- </>
654
- )}
655
- <Text>
656
- Powered by{' '}
657
- <Text style={styles.link} onPress={launchRowndWebsite}>
658
- Rownd
659
- </Text>
660
- </Text>
661
- </View>
662
- </BottomSheetModal>
663
- );
664
- }
665
-
666
- const styles = StyleSheet.create({
667
- modal: {
668
- // flex: 1,
669
- },
670
- bottomSheet: {
671
- shadowColor: '#000',
672
- shadowOffset: {
673
- width: 0,
674
- height: 12,
675
- },
676
- shadowOpacity: 0.58,
677
- shadowRadius: 16.0,
678
-
679
- elevation: 24,
680
- },
681
- innerContainer: {
682
- borderRadius: 20,
683
- borderColor: 'transparent',
684
- borderWidth: 0,
685
- padding: 25,
686
- },
687
- container: {
688
- flex: 1,
689
- padding: 24,
690
- },
691
- header: {
692
- alignItems: 'center',
693
- backgroundColor: 'white',
694
- paddingVertical: 20,
695
- borderTopLeftRadius: 20,
696
- borderTopRightRadius: 20,
697
- },
698
- panelHandle: {
699
- width: 40,
700
- height: 2,
701
- backgroundColor: 'rgba(0,0,0,0.3)',
702
- borderRadius: 4,
703
- },
704
- appLogo: {
705
- padding: 50,
706
- width: 75,
707
- height: 75,
708
- resizeMode: 'center',
709
- textAlign: 'center',
710
- marginLeft: 'auto',
711
- marginRight: 'auto',
712
- marginBottom: 20,
713
- },
714
- dialogHeading: {
715
- fontSize: 24,
716
- },
717
- inputLabel: {
718
- marginTop: 20,
719
- marginBottom: 5,
720
- },
721
- identifierInput: {
722
- backgroundColor: '#eee',
723
- borderRadius: 8,
724
- padding: 10,
725
- fontSize: 18,
726
- },
727
- button: {
728
- borderRadius: 10,
729
- padding: 10,
730
- marginTop: 20,
731
- marginBottom: 30,
732
- elevation: 0,
733
- backgroundColor: '#5b0ae0',
734
- color: '#fff',
735
- display: 'flex',
736
- alignItems: 'center',
737
- justifyContent: 'center',
738
- // height: 45,
739
- },
740
- buttonDisabled: {
741
- backgroundColor: '#eee',
742
- },
743
- buttonPressed: {
744
- opacity: 0.5,
745
- },
746
- buttonContent: {
747
- textAlign: 'center',
748
- fontSize: 18,
749
- color: '#fff',
750
- },
751
- buttonText: {
752
- // marginHorizontal: 10,
753
- // paddingHorizontal: 10,
754
- // fontSize: 18,
755
- // color: '#fff',
756
- },
757
- buttonTextInner: {
758
- fontSize: 18,
759
- },
760
- buttonDisabledText: {
761
- color: '#8e8e8e',
762
- },
763
- buttonSubmitting: {
764
- backgroundColor: '#2f0492',
765
- color: '#c7c7c7',
766
- },
767
- loadingIndicator: {
768
- marginRight: 10,
769
- },
770
- signInNoticeText: {
771
- fontSize: 12,
772
- marginBottom: 20,
773
- },
774
- signInVerificationImage: {
775
- alignItems: 'center',
776
- },
777
- link: {
778
- color: '#6114e1',
779
- },
780
- py6: {
781
- paddingVertical: 6,
782
- },
783
- italic: {
784
- fontStyle: 'italic',
785
- },
786
- textBase: {
787
- fontSize: 16,
788
- lineHeight: 24,
789
- },
790
- textRose800: {
791
- color: 'rgb(159, 18, 57)',
792
- },
793
- });