@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,26 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="62px" height="100px" viewBox="0 0 62 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <title>unverified-phone</title>
4
+ <defs>
5
+ <rect id="path-1" x="0" y="0" width="58" height="96" rx="8"></rect>
6
+ <filter x="-5.2%" y="-3.1%" width="110.3%" height="106.2%" filterUnits="objectBoundingBox" id="filter-2">
7
+ <feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
8
+ <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
9
+ <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
10
+ </filter>
11
+ </defs>
12
+ <g id="SMS-verify-from-web-stay-on-web-(BR)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
13
+ <g id="xlg-1312px-16-column-copy-51" transform="translate(-625.000000, -259.000000)">
14
+ <g id="unverified-phone" transform="translate(627.000000, 261.000000)">
15
+ <g id="Rectangle">
16
+ <use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
17
+ <use fill="#444444" fill-rule="evenodd" xlink:href="#path-1"></use>
18
+ </g>
19
+ <circle id="Oval" fill="#000000" cx="29" cy="89" r="5"></circle>
20
+ <rect id="Rectangle" fill="#FFFFFF" x="5" y="11" width="48" height="71" rx="2"></rect>
21
+ <rect id="Rectangle" fill-opacity="0.4" fill="#D8D8D8" x="10" y="20" width="38" height="5"></rect>
22
+ <rect id="Rectangle-Copy" fill-opacity="0.4" fill="#D8D8D8" x="10" y="31" width="38" height="36"></rect>
23
+ </g>
24
+ </g>
25
+ </g>
26
+ </svg>
@@ -0,0 +1,30 @@
1
+ import React, { useEffect } from 'react';
2
+
3
+ const { useRownd } = require('../hooks/rownd');
4
+
5
+ interface IAuthenticatedComponentProps {
6
+ shouldRequestSignIn?: boolean;
7
+ renderContentWhenUnauthenticated?: boolean;
8
+ }
9
+
10
+ export default function ({
11
+ shouldRequestSignIn,
12
+ renderContentWhenUnauthenticated,
13
+ children,
14
+ }: React.PropsWithChildren<IAuthenticatedComponentProps>) {
15
+ const { is_authenticated, requestSignIn } = useRownd();
16
+
17
+ useEffect(() => {
18
+ if (shouldRequestSignIn && !is_authenticated) {
19
+ requestSignIn();
20
+ }
21
+ }, [is_authenticated, requestSignIn, shouldRequestSignIn]);
22
+
23
+ return (
24
+ <>
25
+ {(is_authenticated || renderContentWhenUnauthenticated) && (
26
+ <>{children}</>
27
+ )}
28
+ </>
29
+ );
30
+ }
@@ -0,0 +1,125 @@
1
+ import React, { useMemo, useCallback, useEffect, useRef } from 'react';
2
+ import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
3
+ import {
4
+ BottomSheetBackdrop,
5
+ BottomSheetBackdropProps,
6
+ BottomSheetModal,
7
+ } from '@gorhom/bottom-sheet';
8
+ import { useTimeout } from 'usehooks-ts';
9
+ import { MaterialIcons } from '@expo/vector-icons';
10
+
11
+ import { useNav } from '../hooks';
12
+ import { useGlobalContext } from './GlobalContext';
13
+
14
+ export function AutoSigninDialog() {
15
+ const navTo = useNav();
16
+ const { state } = useGlobalContext();
17
+
18
+ // Let the user know they're auto signing-in, then close when done
19
+ useTimeout(() => {
20
+ if (state.nav.options.type === 'error') {
21
+ return;
22
+ }
23
+
24
+ handleClose();
25
+ }, 3000);
26
+
27
+ const bottomSheetModalRef = useRef<BottomSheetModal>(null);
28
+ useEffect(() => {
29
+ if (bottomSheetModalRef.current) {
30
+ bottomSheetModalRef.current.present();
31
+ }
32
+ }, []);
33
+
34
+ const handleClose = useCallback(() => {
35
+ setTimeout(() => {
36
+ navTo({ hide: true });
37
+ }, 150);
38
+ }, [navTo]);
39
+
40
+ const snapPoints = useMemo(() => ['30%', '60%'], []);
41
+
42
+ const renderBackdrop = useCallback(
43
+ (props: BottomSheetBackdropProps) => (
44
+ <BottomSheetBackdrop {...props} pressBehavior="none" />
45
+ ),
46
+ []
47
+ );
48
+
49
+ return (
50
+ <BottomSheetModal
51
+ snapPoints={snapPoints}
52
+ index={0}
53
+ backdropComponent={renderBackdrop}
54
+ keyboardBehavior="fillParent"
55
+ android_keyboardInputMode="adjustResize"
56
+ enablePanDownToClose={state.nav.options.type === 'error'}
57
+ onDismiss={handleClose}
58
+ style={styles.bottomSheet}
59
+ ref={bottomSheetModalRef}
60
+ >
61
+ <View style={styles.innerContainer}>
62
+ {state.nav.options.type === 'error' && (
63
+ <>
64
+ <MaterialIcons
65
+ name="error"
66
+ size={24}
67
+ color="#DA1E28"
68
+ style={styles.errorIcon}
69
+ />
70
+ <Text style={styles.errorMessage}>
71
+ {state.nav.options.message ||
72
+ 'An error occurred. Please try again.'}
73
+ </Text>
74
+ </>
75
+ )}
76
+
77
+ {state.nav.options.type === 'sign-in' && (
78
+ <>
79
+ <ActivityIndicator size="large" color="#5b0ae0" />
80
+ <Text style={styles.signInMessage}>
81
+ Automatically signing you in. Just a sec...
82
+ </Text>
83
+ </>
84
+ )}
85
+ </View>
86
+ </BottomSheetModal>
87
+ );
88
+ }
89
+
90
+ const styles = StyleSheet.create({
91
+ modal: {
92
+ // flex: 1,
93
+ },
94
+ bottomSheet: {
95
+ shadowColor: '#000',
96
+ shadowOffset: {
97
+ width: 0,
98
+ height: 12,
99
+ },
100
+ shadowOpacity: 0.58,
101
+ shadowRadius: 16.0,
102
+
103
+ elevation: 24,
104
+ },
105
+ innerContainer: {
106
+ borderRadius: 20,
107
+ borderColor: 'transparent',
108
+ borderWidth: 0,
109
+ padding: 25,
110
+ textAlign: 'center',
111
+ },
112
+ signInMessage: {
113
+ textAlign: 'center',
114
+ fontSize: 24,
115
+ padding: 20,
116
+ },
117
+ errorMessage: {
118
+ textAlign: 'center',
119
+ fontSize: 24,
120
+ padding: 20,
121
+ },
122
+ errorIcon: {
123
+ textAlign: 'center',
124
+ },
125
+ });
@@ -0,0 +1,278 @@
1
+ import { useEffect, useCallback, useRef } from 'react';
2
+ import isEqual from 'lodash-es/isEqual';
3
+ import { useDebounce, useApi, useDeviceFingerprint } from '../hooks';
4
+ import { DEFAULT_USER_AGENT } from '../hooks/api';
5
+ import type { IConfig } from '../utils/config';
6
+ import { useGlobalContext } from './GlobalContext';
7
+ import { ActionType } from '../data/actions';
8
+ import ky from 'ky';
9
+ import * as Clipboard from 'expo-clipboard';
10
+ import base64 from 'react-native-base64';
11
+ import { Linking } from 'react-native';
12
+ import { events, EventType } from '../utils/events';
13
+
14
+ export type UserInfoResp = {
15
+ data: {
16
+ [key: string]: any;
17
+ };
18
+ redacted: string[];
19
+ };
20
+
21
+ interface UserAclsResponse {
22
+ acls: Record<string, { shared: boolean }>;
23
+ }
24
+
25
+ type DefaultContextProps = {
26
+ config: IConfig;
27
+ };
28
+
29
+ export function DefaultContext({ config }: DefaultContextProps) {
30
+ const { state, dispatch } = useGlobalContext();
31
+ const { client: api } = useApi();
32
+ useDeviceFingerprint();
33
+
34
+ // Fetch app schema and config
35
+ useEffect(() => {
36
+ (async () => {
37
+ try {
38
+ const resp: any = await api
39
+ .get('hub/app-config', {
40
+ headers: {
41
+ 'x-rownd-app-key': config.appKey,
42
+ },
43
+ })
44
+ .json();
45
+
46
+ if (resp?.app?.icon) {
47
+ const iconMeta = await ky.head(resp.app.icon);
48
+ resp.app.icon_content_type = iconMeta.headers.get('content-type');
49
+ }
50
+
51
+ dispatch({
52
+ type: ActionType.SET_APP_CONFIG,
53
+ payload: resp.app,
54
+ });
55
+ } catch (err) {
56
+ console.error('Failed to fetch app config:', err);
57
+ }
58
+ })();
59
+ }, [api, config.appKey, dispatch]);
60
+
61
+ /**
62
+ * If not signed in, check the clipboard for an init hash or auth link we can use to auto-auth the user
63
+ */
64
+ useEffect(() => {
65
+ if (state.auth.access_token || !state.app.id) {
66
+ return;
67
+ }
68
+
69
+ (async () => {
70
+ try {
71
+ let authData = null;
72
+
73
+ let authLink =
74
+ (await Linking.getInitialURL()) || (await Clipboard.getStringAsync());
75
+ if (authLink.includes('rownd.link')) {
76
+ dispatch({
77
+ type: ActionType.CHANGE_ROUTE,
78
+ payload: {
79
+ route: '/account/auto-signin',
80
+ opts: {
81
+ type: 'sign-in',
82
+ },
83
+ },
84
+ });
85
+
86
+ authData = await ky
87
+ .get(authLink, {
88
+ headers: {
89
+ 'User-Agent': DEFAULT_USER_AGENT,
90
+ },
91
+ })
92
+ .json();
93
+ } else if (authLink.startsWith('rph_init=')) {
94
+ const authStr = authLink.split('rph_init=')[1];
95
+ authData = JSON.parse(base64.decode(authStr));
96
+ } else {
97
+ return;
98
+ }
99
+
100
+ // Clear the clipboard value so we don't leak any creds
101
+ await Clipboard.setStringAsync('');
102
+
103
+ dispatch({
104
+ type: ActionType.LOGIN_SUCCESS,
105
+ payload: authData,
106
+ });
107
+ } catch (err) {
108
+ console.error(
109
+ 'We found an auth link or string, but failed to authenticate with it because:',
110
+ err
111
+ );
112
+ }
113
+ })();
114
+ }, [state.auth.access_token, state.app.id, dispatch]);
115
+
116
+ const retrieveUserInfo = useCallback(() => {
117
+ if (!state.auth.access_token || !state.app.id) {
118
+ return;
119
+ }
120
+
121
+ (async () => {
122
+ const userInfo: UserInfoResp = await api
123
+ .get(`me/applications/${state.app.id}/data`, {
124
+ headers: {
125
+ Authorization: `Bearer ${state.auth.access_token}`,
126
+ },
127
+ })
128
+ .json();
129
+ dispatch({
130
+ type: ActionType.LOAD_USER,
131
+ payload: userInfo,
132
+ });
133
+
134
+ dispatch({
135
+ type: ActionType.UPDATE_LOCAL_ACLS,
136
+ payload: userInfo.redacted.reduce(
137
+ (acc: Record<string, any>, field: string) => {
138
+ acc[field] = { shared: false };
139
+ return acc;
140
+ },
141
+ {}
142
+ ),
143
+ });
144
+ })();
145
+ }, [api, dispatch, state.app.id, state.auth.access_token]);
146
+
147
+ const retrieveAcls = useCallback(() => {
148
+ if (!state.app.id || !state.auth.access_token) {
149
+ return;
150
+ }
151
+
152
+ (async () => {
153
+ try {
154
+ const appUserAcls: UserAclsResponse = await api
155
+ .get(`me/applications/${state.app.id}/acls`, {
156
+ headers: {
157
+ Authorization: `Bearer ${state.auth.access_token}`,
158
+ },
159
+ })
160
+ .json();
161
+ dispatch({
162
+ type: ActionType.UPDATE_LOCAL_ACLS,
163
+ payload: appUserAcls.acls,
164
+ });
165
+ } catch (err) {
166
+ if (err instanceof Error) {
167
+ // const unsharedAcls = reduceSchemaToUnsharedAcls();
168
+ dispatch({
169
+ type: ActionType.UPDATE_LOCAL_ACLS,
170
+ payload: {},
171
+ });
172
+ }
173
+ }
174
+ })();
175
+ }, [api, dispatch, state.app.id, state.auth.access_token]);
176
+
177
+ useEffect(retrieveUserInfo, [retrieveUserInfo]);
178
+ useEffect(retrieveAcls, [retrieveAcls]);
179
+
180
+ useEffect(() => {
181
+ if (!state.user.needs_refresh) {
182
+ return;
183
+ }
184
+ retrieveUserInfo();
185
+ dispatch({
186
+ type: ActionType.SET_REFRESH_USER_DATA,
187
+ payload: { needs_refresh: false },
188
+ });
189
+ }, [dispatch, retrieveUserInfo, state.user.needs_refresh]);
190
+
191
+ const saveAclsDebounced = useDebounce(saveAcls, 2000); // 2s
192
+
193
+ async function saveAcls() {
194
+ if (state.app.id && state.local_acls) {
195
+ await api
196
+ .put(`me/applications/${state.app.id}/acls`, {
197
+ headers: {
198
+ Authorization: `Bearer ${state.auth.access_token}`,
199
+ },
200
+ json: {
201
+ acls: state.local_acls,
202
+ },
203
+ })
204
+ .json();
205
+ }
206
+ }
207
+
208
+ const localAclsRef = useRef(state.local_acls);
209
+ useEffect(() => {
210
+ // Don't save acls if they were just loaded for the first time or they haven't changed
211
+ if (
212
+ !localAclsRef.current ||
213
+ isEqual(localAclsRef.current, state.local_acls)
214
+ ) {
215
+ return;
216
+ }
217
+ saveAclsDebounced();
218
+ }, [saveAclsDebounced, state.local_acls]);
219
+
220
+ const saveUserDataDebounced = useDebounce(saveUserData, 2000); // 2s
221
+
222
+ // Save user data in the application state to the API server
223
+ async function saveUserData() {
224
+ if (state.app.id && state.user.data && state.auth.access_token) {
225
+ dispatch({
226
+ type: ActionType.SET_IS_SAVING_USER_DATA,
227
+ payload: {
228
+ saving: true,
229
+ },
230
+ });
231
+
232
+ const eventDetails: Record<string, any> = {};
233
+ try {
234
+ eventDetails.data = await api
235
+ .put(`me/applications/${state.app.id}/data`, {
236
+ headers: {
237
+ Authorization: `Bearer ${state.auth.access_token}`,
238
+ },
239
+ json: {
240
+ data: state.user.data,
241
+ },
242
+ })
243
+ .json();
244
+ } catch (err) {
245
+ eventDetails.error = err;
246
+ // Get the latest user info from Rownd. Something was probably bad
247
+ // with the data we just tried to save.
248
+ // This is a bit hacky. We should be able to reset the state without
249
+ // calling rownd again by rolling back to a previously good state...
250
+ retrieveUserInfo();
251
+ // TODO: Set some error state
252
+ } finally {
253
+ events.dispatch(EventType.USER_DATA_SAVED, eventDetails);
254
+ dispatch({
255
+ type: ActionType.SET_IS_SAVING_USER_DATA,
256
+ payload: {
257
+ saving: false,
258
+ },
259
+ });
260
+ }
261
+ }
262
+ }
263
+
264
+ const userDataRef = useRef(state.user.data);
265
+ useEffect(() => {
266
+ // Don't update the user data in the API server if the email is unknown or the data hasn't changed
267
+ if (
268
+ (!state.user.data?.email && !state.user.data.phone_number) ||
269
+ isEqual(userDataRef.current, state.user.data)
270
+ ) {
271
+ return;
272
+ }
273
+ saveUserDataDebounced();
274
+ userDataRef.current = state.user.data;
275
+ }, [dispatch, saveUserDataDebounced, state.user.data]);
276
+
277
+ return null;
278
+ }