@rownd/react-native 0.2.5 → 2.2.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 (252) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +34 -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/RowndPlugin-Bridging-Header.h +3 -0
  10. package/ios/RowndPlugin.m +26 -0
  11. package/ios/RowndPlugin.swift +83 -0
  12. package/ios/RowndPluginEventEmitter.m +12 -0
  13. package/ios/RowndPluginEventEmitter.swift +19 -0
  14. package/lib/commonjs/components/GlobalContext.js +32 -298
  15. package/lib/commonjs/components/GlobalContext.js.map +1 -1
  16. package/lib/commonjs/components/{BottomSheetTextInput/types.js → GlobalContext.types.js} +1 -1
  17. package/lib/commonjs/components/GlobalContext.types.js.map +1 -0
  18. package/lib/commonjs/constants/action.js +13 -0
  19. package/lib/commonjs/constants/action.js.map +1 -0
  20. package/lib/commonjs/hooks/rownd.js +15 -146
  21. package/lib/commonjs/hooks/rownd.js.map +1 -1
  22. package/lib/commonjs/index.js +2 -12
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/commonjs/reducer/rowndReducer.js +89 -0
  25. package/lib/commonjs/reducer/rowndReducer.js.map +1 -0
  26. package/lib/commonjs/utils/config.js +0 -26
  27. package/lib/commonjs/utils/config.js.map +1 -1
  28. package/lib/commonjs/utils/nativeModule.js +72 -0
  29. package/lib/commonjs/utils/nativeModule.js.map +1 -0
  30. package/lib/module/components/GlobalContext.js +30 -293
  31. package/lib/module/components/GlobalContext.js.map +1 -1
  32. package/lib/module/components/GlobalContext.types.js +2 -0
  33. package/lib/module/components/GlobalContext.types.js.map +1 -0
  34. package/lib/module/constants/action.js +6 -0
  35. package/lib/module/constants/action.js.map +1 -0
  36. package/lib/module/hooks/rownd.js +12 -137
  37. package/lib/module/hooks/rownd.js.map +1 -1
  38. package/lib/module/index.js +2 -4
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/reducer/rowndReducer.js +78 -0
  41. package/lib/module/reducer/rowndReducer.js.map +1 -0
  42. package/lib/module/utils/config.js +1 -16
  43. package/lib/module/utils/config.js.map +1 -1
  44. package/lib/module/utils/nativeModule.js +48 -0
  45. package/lib/module/utils/nativeModule.js.map +1 -0
  46. package/lib/typescript/components/GlobalContext.d.ts +13 -0
  47. package/lib/typescript/{src/components/GlobalContext.d.ts → components/GlobalContext.types.d.ts} +9 -35
  48. package/lib/typescript/constants/action.d.ts +7 -0
  49. package/lib/typescript/{src/hooks → hooks}/rownd.d.ts +10 -13
  50. package/lib/typescript/index.d.ts +3 -0
  51. package/lib/typescript/reducer/rowndReducer.d.ts +4 -0
  52. package/lib/typescript/utils/config.d.ts +10 -0
  53. package/lib/typescript/utils/nativeModule.d.ts +10 -0
  54. package/package.json +36 -56
  55. package/rownd-react-native.podspec +36 -0
  56. package/src/components/GlobalContext.tsx +47 -457
  57. package/src/components/GlobalContext.types.ts +105 -0
  58. package/src/constants/action.ts +8 -0
  59. package/src/hooks/rownd.ts +29 -147
  60. package/src/index.tsx +2 -4
  61. package/src/reducer/rowndReducer.ts +78 -0
  62. package/src/utils/config.ts +0 -27
  63. package/src/utils/nativeModule.ts +69 -0
  64. package/android/src/main/java/com/reactnative/ReactNativePackage.java +0 -22
  65. package/android/src/main/java/com/reactnative/ReactNativeViewManager.java +0 -31
  66. package/ios/ReactNativeViewManager.m +0 -34
  67. package/lib/commonjs/assets/images/checkmark--filled.svg +0 -12
  68. package/lib/commonjs/assets/images/email-verify-waiting.svg +0 -36
  69. package/lib/commonjs/assets/images/error-icon-material.svg +0 -1
  70. package/lib/commonjs/assets/images/phone-verify-waiting.svg +0 -26
  71. package/lib/commonjs/components/AuthenticatedComponent.js +0 -35
  72. package/lib/commonjs/components/AuthenticatedComponent.js.map +0 -1
  73. package/lib/commonjs/components/AutoSigninDialog.js +0 -132
  74. package/lib/commonjs/components/AutoSigninDialog.js.map +0 -1
  75. package/lib/commonjs/components/BottomSheetTextInput/BottomSheetTextInput.js +0 -65
  76. package/lib/commonjs/components/BottomSheetTextInput/BottomSheetTextInput.js.map +0 -1
  77. package/lib/commonjs/components/BottomSheetTextInput/index.js +0 -24
  78. package/lib/commonjs/components/BottomSheetTextInput/index.js.map +0 -1
  79. package/lib/commonjs/components/BottomSheetTextInput/types.js.map +0 -1
  80. package/lib/commonjs/components/DarkText.js +0 -35
  81. package/lib/commonjs/components/DarkText.js.map +0 -1
  82. package/lib/commonjs/components/DefaultContext.js +0 -266
  83. package/lib/commonjs/components/DefaultContext.js.map +0 -1
  84. package/lib/commonjs/components/RowndComponents.js +0 -25
  85. package/lib/commonjs/components/RowndComponents.js.map +0 -1
  86. package/lib/commonjs/components/RowndProvider.js +0 -55
  87. package/lib/commonjs/components/RowndProvider.js.map +0 -1
  88. package/lib/commonjs/components/SignIn.js +0 -644
  89. package/lib/commonjs/components/SignIn.js.map +0 -1
  90. package/lib/commonjs/components/images/CheckmarkFilled.js +0 -40
  91. package/lib/commonjs/components/images/CheckmarkFilled.js.map +0 -1
  92. package/lib/commonjs/components/images/EmailVerifyWaiting.js +0 -100
  93. package/lib/commonjs/components/images/EmailVerifyWaiting.js.map +0 -1
  94. package/lib/commonjs/components/images/ErrorIcon.js +0 -31
  95. package/lib/commonjs/components/images/ErrorIcon.js.map +0 -1
  96. package/lib/commonjs/components/images/PhoneVerifyWaiting.js +0 -91
  97. package/lib/commonjs/components/images/PhoneVerifyWaiting.js.map +0 -1
  98. package/lib/commonjs/data/actions.js +0 -26
  99. package/lib/commonjs/data/actions.js.map +0 -1
  100. package/lib/commonjs/hooks/api.js +0 -155
  101. package/lib/commonjs/hooks/api.js.map +0 -1
  102. package/lib/commonjs/hooks/debounce.js +0 -38
  103. package/lib/commonjs/hooks/debounce.js.map +0 -1
  104. package/lib/commonjs/hooks/fingerprint.js +0 -176
  105. package/lib/commonjs/hooks/fingerprint.js.map +0 -1
  106. package/lib/commonjs/hooks/index.js +0 -48
  107. package/lib/commonjs/hooks/index.js.map +0 -1
  108. package/lib/commonjs/hooks/interval.js +0 -31
  109. package/lib/commonjs/hooks/interval.js.map +0 -1
  110. package/lib/commonjs/hooks/nav.js +0 -39
  111. package/lib/commonjs/hooks/nav.js.map +0 -1
  112. package/lib/commonjs/index.tsx.bak +0 -26
  113. package/lib/commonjs/types.js +0 -2
  114. package/lib/commonjs/types.js.map +0 -1
  115. package/lib/commonjs/utils/events.js +0 -57
  116. package/lib/commonjs/utils/events.js.map +0 -1
  117. package/lib/commonjs/utils/form.js +0 -46
  118. package/lib/commonjs/utils/form.js.map +0 -1
  119. package/lib/commonjs/utils/queue.js +0 -117
  120. package/lib/commonjs/utils/queue.js.map +0 -1
  121. package/lib/commonjs/utils/storage.js +0 -15
  122. package/lib/commonjs/utils/storage.js.map +0 -1
  123. package/lib/commonjs/utils/tokens.js +0 -35
  124. package/lib/commonjs/utils/tokens.js.map +0 -1
  125. package/lib/commonjs/utils/user-data.js +0 -21
  126. package/lib/commonjs/utils/user-data.js.map +0 -1
  127. package/lib/module/assets/images/checkmark--filled.svg +0 -12
  128. package/lib/module/assets/images/email-verify-waiting.svg +0 -36
  129. package/lib/module/assets/images/error-icon-material.svg +0 -1
  130. package/lib/module/assets/images/phone-verify-waiting.svg +0 -26
  131. package/lib/module/components/AuthenticatedComponent.js +0 -24
  132. package/lib/module/components/AuthenticatedComponent.js.map +0 -1
  133. package/lib/module/components/AutoSigninDialog.js +0 -110
  134. package/lib/module/components/AutoSigninDialog.js.map +0 -1
  135. package/lib/module/components/BottomSheetTextInput/BottomSheetTextInput.js +0 -52
  136. package/lib/module/components/BottomSheetTextInput/BottomSheetTextInput.js.map +0 -1
  137. package/lib/module/components/BottomSheetTextInput/index.js +0 -5
  138. package/lib/module/components/BottomSheetTextInput/index.js.map +0 -1
  139. package/lib/module/components/BottomSheetTextInput/types.js +0 -2
  140. package/lib/module/components/BottomSheetTextInput/types.js.map +0 -1
  141. package/lib/module/components/DarkText.js +0 -24
  142. package/lib/module/components/DarkText.js.map +0 -1
  143. package/lib/module/components/DefaultContext.js +0 -244
  144. package/lib/module/components/DefaultContext.js.map +0 -1
  145. package/lib/module/components/RowndComponents.js +0 -12
  146. package/lib/module/components/RowndComponents.js.map +0 -1
  147. package/lib/module/components/RowndProvider.js +0 -39
  148. package/lib/module/components/RowndProvider.js.map +0 -1
  149. package/lib/module/components/SignIn.js +0 -616
  150. package/lib/module/components/SignIn.js.map +0 -1
  151. package/lib/module/components/images/CheckmarkFilled.js +0 -27
  152. package/lib/module/components/images/CheckmarkFilled.js.map +0 -1
  153. package/lib/module/components/images/EmailVerifyWaiting.js +0 -87
  154. package/lib/module/components/images/EmailVerifyWaiting.js.map +0 -1
  155. package/lib/module/components/images/ErrorIcon.js +0 -18
  156. package/lib/module/components/images/ErrorIcon.js.map +0 -1
  157. package/lib/module/components/images/PhoneVerifyWaiting.js +0 -78
  158. package/lib/module/components/images/PhoneVerifyWaiting.js.map +0 -1
  159. package/lib/module/data/actions.js +0 -19
  160. package/lib/module/data/actions.js.map +0 -1
  161. package/lib/module/hooks/api.js +0 -135
  162. package/lib/module/hooks/api.js.map +0 -1
  163. package/lib/module/hooks/debounce.js +0 -29
  164. package/lib/module/hooks/debounce.js.map +0 -1
  165. package/lib/module/hooks/fingerprint.js +0 -157
  166. package/lib/module/hooks/fingerprint.js.map +0 -1
  167. package/lib/module/hooks/index.js +0 -7
  168. package/lib/module/hooks/index.js.map +0 -1
  169. package/lib/module/hooks/interval.js +0 -23
  170. package/lib/module/hooks/interval.js.map +0 -1
  171. package/lib/module/hooks/nav.js +0 -30
  172. package/lib/module/hooks/nav.js.map +0 -1
  173. package/lib/module/index.tsx.bak +0 -26
  174. package/lib/module/types.js +0 -2
  175. package/lib/module/types.js.map +0 -1
  176. package/lib/module/utils/events.js +0 -45
  177. package/lib/module/utils/events.js.map +0 -1
  178. package/lib/module/utils/form.js +0 -34
  179. package/lib/module/utils/form.js.map +0 -1
  180. package/lib/module/utils/queue.js +0 -109
  181. package/lib/module/utils/queue.js.map +0 -1
  182. package/lib/module/utils/storage.js +0 -6
  183. package/lib/module/utils/storage.js.map +0 -1
  184. package/lib/module/utils/tokens.js +0 -24
  185. package/lib/module/utils/tokens.js.map +0 -1
  186. package/lib/module/utils/user-data.js +0 -14
  187. package/lib/module/utils/user-data.js.map +0 -1
  188. package/lib/package.json +0 -173
  189. package/lib/typescript/src/components/AuthenticatedComponent.d.ts +0 -7
  190. package/lib/typescript/src/components/AutoSigninDialog.d.ts +0 -2
  191. package/lib/typescript/src/components/BottomSheetTextInput/BottomSheetTextInput.d.ts +0 -4
  192. package/lib/typescript/src/components/BottomSheetTextInput/index.d.ts +0 -2
  193. package/lib/typescript/src/components/BottomSheetTextInput/types.d.ts +0 -3
  194. package/lib/typescript/src/components/DarkText.d.ts +0 -3
  195. package/lib/typescript/src/components/DefaultContext.d.ts +0 -12
  196. package/lib/typescript/src/components/RowndComponents.d.ts +0 -2
  197. package/lib/typescript/src/components/RowndProvider.d.ts +0 -8
  198. package/lib/typescript/src/components/SignIn.d.ts +0 -2
  199. package/lib/typescript/src/components/images/CheckmarkFilled.d.ts +0 -4
  200. package/lib/typescript/src/components/images/EmailVerifyWaiting.d.ts +0 -4
  201. package/lib/typescript/src/components/images/ErrorIcon.d.ts +0 -4
  202. package/lib/typescript/src/components/images/PhoneVerifyWaiting.d.ts +0 -4
  203. package/lib/typescript/src/data/actions.d.ts +0 -20
  204. package/lib/typescript/src/hooks/api.d.ts +0 -12
  205. package/lib/typescript/src/hooks/debounce.d.ts +0 -5
  206. package/lib/typescript/src/hooks/fingerprint.d.ts +0 -12
  207. package/lib/typescript/src/hooks/index.d.ts +0 -6
  208. package/lib/typescript/src/hooks/interval.d.ts +0 -2
  209. package/lib/typescript/src/hooks/nav.d.ts +0 -6
  210. package/lib/typescript/src/index.d.ts +0 -4
  211. package/lib/typescript/src/types.d.ts +0 -26
  212. package/lib/typescript/src/utils/config.d.ts +0 -18
  213. package/lib/typescript/src/utils/events.d.ts +0 -22
  214. package/lib/typescript/src/utils/form.d.ts +0 -18
  215. package/lib/typescript/src/utils/queue.d.ts +0 -21
  216. package/lib/typescript/src/utils/storage.d.ts +0 -3
  217. package/lib/typescript/src/utils/tokens.d.ts +0 -4
  218. package/lib/typescript/src/utils/user-data.d.ts +0 -3
  219. package/react-native.podspec +0 -19
  220. package/src/assets/images/checkmark--filled.svg +0 -12
  221. package/src/assets/images/email-verify-waiting.svg +0 -36
  222. package/src/assets/images/error-icon-material.svg +0 -1
  223. package/src/assets/images/phone-verify-waiting.svg +0 -26
  224. package/src/components/AuthenticatedComponent.tsx +0 -30
  225. package/src/components/AutoSigninDialog.tsx +0 -131
  226. package/src/components/BottomSheetTextInput/BottomSheetTextInput.tsx +0 -57
  227. package/src/components/BottomSheetTextInput/index.ts +0 -5
  228. package/src/components/BottomSheetTextInput/types.ts +0 -3
  229. package/src/components/DarkText.tsx +0 -16
  230. package/src/components/DefaultContext.tsx +0 -278
  231. package/src/components/RowndComponents.tsx +0 -18
  232. package/src/components/RowndProvider.tsx +0 -56
  233. package/src/components/SignIn.tsx +0 -797
  234. package/src/components/images/CheckmarkFilled.tsx +0 -30
  235. package/src/components/images/EmailVerifyWaiting.tsx +0 -95
  236. package/src/components/images/ErrorIcon.tsx +0 -11
  237. package/src/components/images/PhoneVerifyWaiting.tsx +0 -74
  238. package/src/data/actions.ts +0 -21
  239. package/src/hooks/api.ts +0 -162
  240. package/src/hooks/debounce.ts +0 -36
  241. package/src/hooks/fingerprint.ts +0 -217
  242. package/src/hooks/index.ts +0 -7
  243. package/src/hooks/interval.ts +0 -25
  244. package/src/hooks/nav.tsx +0 -29
  245. package/src/index.tsx.bak +0 -26
  246. package/src/types.ts +0 -27
  247. package/src/utils/events.ts +0 -54
  248. package/src/utils/form.tsx +0 -64
  249. package/src/utils/queue.ts +0 -75
  250. package/src/utils/storage.ts +0 -7
  251. package/src/utils/tokens.ts +0 -26
  252. package/src/utils/user-data.ts +0 -15
@@ -1,797 +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} // managed inputs that modify values cause strange issues on Android :-(
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
- // <View width={25} height={25}>
565
- <ActivityIndicator
566
- size="small"
567
- color="#efefef"
568
- style={styles.loadingIndicator}
569
- />
570
- // </View>
571
- )}
572
- {isSubmitting ? 'Just a sec...' : 'Continue'}
573
- </Text>
574
- </View>
575
- </View>
576
- </TouchableOpacity>
577
- <Text style={styles.signInNoticeText}>
578
- By continuing, you're agreeing to the terms of service that govern
579
- this app and to receive email or text messages for verification
580
- purposes.
581
- </Text>
582
- </>
583
- )}
584
-
585
- {step === LoginStep.WAITING && (
586
- <>
587
- <Text style={styles.dialogHeading}>
588
- Thanks! Verify your{' '}
589
- {loginType === 'phone' ? 'phone number' : 'email'} to finish
590
- </Text>
591
- <Text style={styles.py6}>
592
- Click the link in the message we just sent to{' '}
593
- <Text style={styles.italic}>{userIdentifier}</Text> to verify and
594
- finish.
595
- <Text
596
- style={[styles.link]}
597
- onPress={() => setStep(LoginStep.INIT)}
598
- >
599
- &nbsp;Re-send message
600
- </Text>
601
- </Text>
602
-
603
- <View style={styles.signInVerificationImage}>
604
- {loginType === 'phone' ? (
605
- <ImagePhoneVerifyWaiting />
606
- ) : (
607
- <ImageEmailVerifyWaiting />
608
- )}
609
- </View>
610
-
611
- <TouchableOpacity
612
- style={[styles.button]}
613
- onPress={() => setStep(LoginStep.INIT)}
614
- >
615
- <Text style={styles.buttonContent}>
616
- Try a different{' '}
617
- {loginType === 'phone' ? 'phone number' : 'email'}
618
- </Text>
619
- </TouchableOpacity>
620
- </>
621
- )}
622
-
623
- {step === LoginStep.SUCCESS && (
624
- <>
625
- <View style={styles.signInVerificationImage}>
626
- <ImageCheckmarkFilled />
627
- </View>
628
- </>
629
- )}
630
-
631
- {step === LoginStep.FAILURE && (
632
- <>
633
- <Text style={styles.textBase}>Whoops, that didn't work!</Text>
634
- <TouchableOpacity
635
- style={styles.button}
636
- onPress={() => setStep(LoginStep.INIT)}
637
- >
638
- <Text style={styles.buttonContent}>Try again</Text>
639
- </TouchableOpacity>
640
- </>
641
- )}
642
-
643
- {step === LoginStep.ERROR && (
644
- <>
645
- <Text style={styles.textBase}>
646
- An error occurred while signing you in.
647
- </Text>
648
- {!!error && <Text style={styles.textRose800}>{error}</Text>}
649
- <TouchableOpacity
650
- style={styles.button}
651
- onPress={() => setStep(LoginStep.INIT)}
652
- >
653
- <Text style={styles.buttonContent}>Try again</Text>
654
- </TouchableOpacity>
655
- </>
656
- )}
657
- <Text>
658
- Powered by{' '}
659
- <Text style={styles.link} onPress={launchRowndWebsite}>
660
- Rownd
661
- </Text>
662
- </Text>
663
- </View>
664
- </BottomSheetModal>
665
- );
666
- }
667
-
668
- const styles = StyleSheet.create({
669
- modal: {
670
- // flex: 1,
671
- },
672
- bottomSheet: {
673
- shadowColor: '#000',
674
- shadowOffset: {
675
- width: 0,
676
- height: 12,
677
- },
678
- shadowOpacity: 0.58,
679
- shadowRadius: 16.0,
680
-
681
- elevation: 24,
682
- },
683
- innerContainer: {
684
- borderRadius: 20,
685
- borderColor: 'transparent',
686
- borderWidth: 0,
687
- padding: 25,
688
- },
689
- container: {
690
- flex: 1,
691
- padding: 24,
692
- },
693
- header: {
694
- alignItems: 'center',
695
- backgroundColor: 'white',
696
- paddingVertical: 20,
697
- borderTopLeftRadius: 20,
698
- borderTopRightRadius: 20,
699
- },
700
- panelHandle: {
701
- width: 40,
702
- height: 2,
703
- backgroundColor: 'rgba(0,0,0,0.3)',
704
- borderRadius: 4,
705
- },
706
- appLogo: {
707
- padding: 50,
708
- width: 75,
709
- height: 75,
710
- resizeMode: 'center',
711
- textAlign: 'center',
712
- marginLeft: 'auto',
713
- marginRight: 'auto',
714
- marginBottom: 20,
715
- },
716
- dialogHeading: {
717
- fontSize: 24,
718
- },
719
- inputLabel: {
720
- marginTop: 20,
721
- marginBottom: 5,
722
- },
723
- identifierInput: {
724
- backgroundColor: '#eee',
725
- borderRadius: 8,
726
- padding: 10,
727
- fontSize: 18,
728
- },
729
- button: {
730
- borderRadius: 10,
731
- padding: 10,
732
- marginTop: 20,
733
- marginBottom: 30,
734
- elevation: 0,
735
- backgroundColor: '#5b0ae0',
736
- color: '#fff',
737
- display: 'flex',
738
- alignItems: 'center',
739
- justifyContent: 'center',
740
- // height: 45,
741
- },
742
- buttonDisabled: {
743
- backgroundColor: '#eee',
744
- },
745
- buttonPressed: {
746
- opacity: 0.5,
747
- },
748
- buttonContent: {
749
- textAlign: 'center',
750
- fontSize: 18,
751
- color: '#fff',
752
- },
753
- buttonText: {
754
- // marginHorizontal: 10,
755
- // paddingHorizontal: 10,
756
- // fontSize: 18,
757
- // color: '#fff',
758
- },
759
- buttonTextInner: {
760
- fontSize: 18,
761
- },
762
- buttonDisabledText: {
763
- color: '#8e8e8e',
764
- },
765
- buttonSubmitting: {
766
- backgroundColor: '#2f0492',
767
- color: '#c7c7c7',
768
- },
769
- loadingIndicator: {
770
- marginRight: 10,
771
- width: 25,
772
- height: 25,
773
- },
774
- signInNoticeText: {
775
- fontSize: 12,
776
- marginBottom: 20,
777
- },
778
- signInVerificationImage: {
779
- alignItems: 'center',
780
- },
781
- link: {
782
- color: '#6114e1',
783
- },
784
- py6: {
785
- paddingVertical: 6,
786
- },
787
- italic: {
788
- fontStyle: 'italic',
789
- },
790
- textBase: {
791
- fontSize: 16,
792
- lineHeight: 24,
793
- },
794
- textRose800: {
795
- color: 'rgb(159, 18, 57)',
796
- },
797
- });