@onairos/react-native 1.0.2 → 2.0.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 (37) hide show
  1. package/lib/commonjs/api/index.js +34 -0
  2. package/lib/commonjs/api/index.js.map +1 -0
  3. package/lib/commonjs/components/OnairosButton.js +69 -122
  4. package/lib/commonjs/components/OnairosButton.js.map +1 -1
  5. package/lib/commonjs/components/Overlay.js +173 -197
  6. package/lib/commonjs/components/Overlay.js.map +1 -1
  7. package/lib/commonjs/components/UniversalOnboarding.js +8 -5
  8. package/lib/commonjs/components/UniversalOnboarding.js.map +1 -1
  9. package/lib/commonjs/constants/index.js +13 -9
  10. package/lib/commonjs/constants/index.js.map +1 -1
  11. package/lib/commonjs/types.js +2 -0
  12. package/lib/commonjs/types.js.map +1 -0
  13. package/lib/commonjs/utils/encryption.js +32 -0
  14. package/lib/commonjs/utils/encryption.js.map +1 -0
  15. package/lib/module/api/index.js +27 -0
  16. package/lib/module/api/index.js.map +1 -0
  17. package/lib/module/components/OnairosButton.js +71 -123
  18. package/lib/module/components/OnairosButton.js.map +1 -1
  19. package/lib/module/components/Overlay.js +175 -198
  20. package/lib/module/components/Overlay.js.map +1 -1
  21. package/lib/module/components/UniversalOnboarding.js +8 -5
  22. package/lib/module/components/UniversalOnboarding.js.map +1 -1
  23. package/lib/module/constants/index.js +13 -9
  24. package/lib/module/constants/index.js.map +1 -1
  25. package/lib/module/types.js +2 -0
  26. package/lib/module/types.js.map +1 -0
  27. package/lib/module/utils/encryption.js +24 -0
  28. package/lib/module/utils/encryption.js.map +1 -0
  29. package/package.json +1 -1
  30. package/src/api/index.ts +33 -0
  31. package/src/components/OnairosButton.tsx +74 -131
  32. package/src/components/Overlay.tsx +190 -185
  33. package/src/components/UniversalOnboarding.tsx +7 -4
  34. package/src/constants/index.ts +12 -8
  35. package/src/types/index.ts +34 -16
  36. package/src/types.ts +29 -0
  37. package/src/utils/encryption.ts +25 -0
@@ -1,272 +1,277 @@
1
- import React, { useCallback, useEffect, useMemo, useRef } from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
5
5
  StyleSheet,
6
6
  TouchableOpacity,
7
7
  ScrollView,
8
+ Alert,
8
9
  Platform,
9
- Dimensions,
10
10
  } from 'react-native';
11
- import BottomSheet from '@gorhom/bottom-sheet';
12
- import Icon from 'react-native-vector-icons/MaterialIcons';
11
+ import { BottomSheetModal, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
13
12
  import { COLORS } from '../constants';
13
+ import { onairosApi } from '../api';
14
+ import { encryptModelKey, getServerPublicKey } from '../utils/encryption';
15
+ import * as PackageInfo from 'react-native-package-info';
14
16
 
15
17
  interface OverlayProps {
16
- visible: boolean;
17
- onClose: () => void;
18
- onAccept: () => void;
19
- AppName: string;
20
- requestData: Record<string, Record<string, string>>;
21
- biometricType?: 'FaceID' | 'TouchID' | 'BiometricID';
18
+ data: {
19
+ [key: string]: {
20
+ type: string;
21
+ descriptions: string;
22
+ reward: string;
23
+ };
24
+ };
25
+ username: string;
26
+ modelKey: string;
27
+ onResolved: (apiUrl: string, accessToken: string, loginDetails: any) => void;
22
28
  }
23
29
 
24
- const { height } = Dimensions.get('window');
25
-
26
30
  export const Overlay: React.FC<OverlayProps> = ({
27
- visible,
28
- onClose,
29
- onAccept,
30
- AppName,
31
- requestData,
32
- biometricType = Platform.OS === 'ios' ? 'FaceID' : 'BiometricID',
31
+ data,
32
+ username,
33
+ modelKey,
34
+ onResolved,
33
35
  }) => {
34
- const bottomSheetRef = useRef<BottomSheet>(null);
35
- const snapPoints = useMemo(() => ['85%'], []);
36
+ const [selections, setSelections] = useState<{ [key: string]: boolean }>({});
37
+ const [details, setDetails] = useState<string>('');
38
+ const bottomSheetRef = React.useRef<BottomSheetModal>(null);
36
39
 
37
- // Expand or collapse the bottom sheet based on visibility
38
40
  useEffect(() => {
39
- if (visible) {
40
- bottomSheetRef.current?.expand();
41
- } else {
42
- bottomSheetRef.current?.close();
41
+ // Initialize selection state
42
+ const initialSelections: { [key: string]: boolean } = {};
43
+ Object.keys(data).forEach((key) => {
44
+ initialSelections[key] = false;
45
+ });
46
+ setSelections(initialSelections);
47
+ getDetails();
48
+ }, []);
49
+
50
+ const getDetails = async () => {
51
+ try {
52
+ const response = await onairosApi.post('getAccountInfo', {
53
+ Info: {
54
+ username: username,
55
+ },
56
+ });
57
+ setDetails(response.AccountInfo);
58
+ } catch (e) {
59
+ console.error('Error getting account info:', e);
43
60
  }
44
- }, [visible]);
61
+ };
62
+
63
+ const closeOverlay = () => {
64
+ bottomSheetRef.current?.dismiss();
65
+ };
66
+
67
+ const confirmSelection = async () => {
68
+ try {
69
+ const packageInfo = await PackageInfo.getPackageInfo();
70
+ const serverPublicKey = await getServerPublicKey();
71
+ const encryptedModelKey = encryptModelKey(serverPublicKey, modelKey);
72
+
73
+ const response = await onairosApi.post('getAPIUrlMobile', {
74
+ Info: {
75
+ storage: 'local',
76
+ appId: packageInfo.packageName,
77
+ confirmations: selections,
78
+ developerURL: 'devURL',
79
+ EncryptedUserPin: encryptedModelKey,
80
+ account: username,
81
+ proofMode: false,
82
+ },
83
+ });
45
84
 
46
- // Get the icon for the biometric type
47
- const getBiometricIcon = useCallback(() => {
48
- switch (biometricType) {
49
- case 'FaceID':
50
- return 'face';
51
- case 'TouchID':
52
- return 'fingerprint';
53
- default:
54
- return 'security';
85
+ if (response.apiUrl && response.token) {
86
+ onResolved(response.apiUrl, response.token, { username });
87
+ closeOverlay();
88
+ }
89
+ } catch (e) {
90
+ console.error('Error in confirmSelection:', e);
91
+ showErrorModal('Failed to confirm selection. Please try again.');
55
92
  }
56
- }, [biometricType]);
93
+ };
57
94
 
58
- // Render the requested data sections
59
- const renderDataCategories = () => {
60
- return Object.entries(requestData).map(([category, items]) => (
61
- <View key={category} style={styles.categoryContainer}>
62
- <Text style={styles.categoryTitle}>{category}</Text>
63
-
64
- {Object.entries(items).map(([item, description]) => (
65
- <View key={item} style={styles.itemContainer}>
66
- <Icon name="check-circle" size={20} color={COLORS.primary} />
67
- <View style={styles.itemContent}>
68
- <Text style={styles.itemTitle}>{item}</Text>
69
- <Text style={styles.itemDescription}>{description}</Text>
70
- </View>
71
- </View>
72
- ))}
73
- </View>
74
- ));
95
+ const showErrorModal = (errorMessage: string) => {
96
+ Alert.alert('Notice', errorMessage, [{ text: 'OK' }]);
75
97
  };
76
98
 
99
+ const selectedCount = Object.values(selections).filter(Boolean).length;
100
+
77
101
  return (
78
- <BottomSheet
102
+ <BottomSheetModal
79
103
  ref={bottomSheetRef}
80
- index={visible ? 0 : -1}
81
- snapPoints={snapPoints}
82
- enablePanDownToClose
83
- onClose={onClose}
84
- backdropComponent={({ animatedIndex }) => (
85
- <View
86
- style={[
87
- styles.backdrop,
88
- {
89
- opacity: animatedIndex.interpolate({
90
- inputRange: [0, 1],
91
- outputRange: [0.5, 0],
92
- }),
93
- },
94
- ]}
104
+ snapPoints={['80%']}
105
+ backdropComponent={(props) => (
106
+ <BottomSheetBackdrop
107
+ {...props}
108
+ appearsOnIndex={0}
109
+ disappearsOnIndex={-1}
95
110
  />
96
111
  )}
97
112
  >
98
113
  <View style={styles.container}>
99
114
  <View style={styles.header}>
100
- <Icon name="shield" size={24} color={COLORS.primary} />
101
- <Text style={styles.headerTitle}>Data Request</Text>
102
- <TouchableOpacity onPress={onClose} style={styles.closeButton}>
103
- <Icon name="close" size={24} color="#000" />
115
+ <Text style={styles.headerTitle}>Onairos</Text>
116
+ <TouchableOpacity onPress={closeOverlay}>
117
+ <Text style={styles.closeButton}>×</Text>
104
118
  </TouchableOpacity>
119
+ <Text style={styles.username}>{username}</Text>
105
120
  </View>
106
-
121
+
107
122
  <ScrollView style={styles.content}>
108
- <View style={styles.appInfoContainer}>
109
- <Text style={styles.appName}>{AppName}</Text>
110
- <Text style={styles.requestText}>
111
- is requesting access to the following data:
112
- </Text>
113
- </View>
114
-
115
- {renderDataCategories()}
116
-
117
- <View style={styles.securityNotice}>
118
- <Icon name="security" size={20} color="#666" />
119
- <Text style={styles.securityText}>
120
- Your data is securely processed and never shared with third parties.
121
- </Text>
122
- </View>
123
+ {Object.entries(data).map(([key, value]) => (
124
+ <View key={key} style={styles.card}>
125
+ <TouchableOpacity
126
+ style={styles.checkboxContainer}
127
+ onPress={() =>
128
+ setSelections((prev) => ({
129
+ ...prev,
130
+ [key]: !prev[key],
131
+ }))
132
+ }
133
+ >
134
+ <View
135
+ style={[
136
+ styles.checkbox,
137
+ selections[key] && styles.checkboxSelected,
138
+ ]}
139
+ />
140
+ <View style={styles.cardContent}>
141
+ <Text style={styles.cardTitle}>{value.type} Insight</Text>
142
+ <Text style={styles.cardDescription}>
143
+ Description: {value.descriptions}
144
+ </Text>
145
+ {value.reward && (
146
+ <Text style={styles.cardReward}>
147
+ Reward: {value.reward}
148
+ </Text>
149
+ )}
150
+ </View>
151
+ </TouchableOpacity>
152
+ </View>
153
+ ))}
123
154
  </ScrollView>
124
-
155
+
125
156
  <View style={styles.footer}>
126
157
  <TouchableOpacity
127
- style={styles.cancelButton}
128
- onPress={onClose}
158
+ style={styles.footerButton}
159
+ onPress={closeOverlay}
129
160
  >
130
- <Text style={styles.cancelButtonText}>Deny</Text>
161
+ <Text style={styles.footerButtonText}>Cancel</Text>
131
162
  </TouchableOpacity>
132
-
163
+ <Text style={styles.selectedCount}>Selected: {selectedCount}</Text>
133
164
  <TouchableOpacity
134
- style={styles.acceptButton}
135
- onPress={onAccept}
165
+ style={styles.footerButton}
166
+ onPress={confirmSelection}
136
167
  >
137
- <Text style={styles.acceptButtonText}>Accept</Text>
138
- <Icon name={getBiometricIcon()} size={20} color="#fff" style={styles.buttonIcon} />
168
+ <Text style={styles.footerButtonText}>Confirm</Text>
139
169
  </TouchableOpacity>
140
170
  </View>
141
171
  </View>
142
- </BottomSheet>
172
+ </BottomSheetModal>
143
173
  );
144
174
  };
145
175
 
146
176
  const styles = StyleSheet.create({
147
177
  container: {
148
178
  flex: 1,
149
- backgroundColor: '#fff',
150
- },
151
- backdrop: {
152
- ...StyleSheet.absoluteFillObject,
153
- backgroundColor: '#000',
179
+ backgroundColor: COLORS.white,
180
+ borderTopLeftRadius: 10,
181
+ borderTopRightRadius: 10,
154
182
  },
155
183
  header: {
156
184
  flexDirection: 'row',
157
185
  alignItems: 'center',
186
+ justifyContent: 'space-between',
158
187
  padding: 16,
159
- backgroundColor: COLORS.headerBg,
160
- borderTopLeftRadius: 16,
161
- borderTopRightRadius: 16,
188
+ backgroundColor: COLORS.primary,
189
+ borderTopLeftRadius: 10,
190
+ borderTopRightRadius: 10,
162
191
  },
163
192
  headerTitle: {
164
- flex: 1,
165
- fontSize: 18,
166
- fontWeight: '600',
167
- marginLeft: 12,
168
- color: '#000',
193
+ fontSize: 24,
194
+ color: COLORS.white,
195
+ fontWeight: 'bold',
169
196
  },
170
197
  closeButton: {
198
+ fontSize: 24,
199
+ color: COLORS.white,
171
200
  padding: 8,
172
201
  },
202
+ username: {
203
+ fontSize: 18,
204
+ color: COLORS.primary,
205
+ },
173
206
  content: {
174
207
  flex: 1,
175
208
  padding: 16,
176
209
  },
177
- appInfoContainer: {
178
- marginBottom: 24,
179
- },
180
- appName: {
181
- fontSize: 20,
182
- fontWeight: 'bold',
183
- color: '#000',
184
- marginBottom: 4,
185
- },
186
- requestText: {
187
- fontSize: 16,
188
- color: '#666',
189
- },
190
- categoryContainer: {
191
- marginBottom: 20,
192
- },
193
- categoryTitle: {
194
- fontSize: 18,
195
- fontWeight: '600',
196
- color: '#000',
210
+ card: {
211
+ backgroundColor: COLORS.white,
212
+ borderRadius: 8,
197
213
  marginBottom: 12,
214
+ shadowColor: COLORS.black,
215
+ shadowOffset: { width: 0, height: 2 },
216
+ shadowOpacity: 0.1,
217
+ shadowRadius: 4,
218
+ elevation: 3,
198
219
  },
199
- itemContainer: {
220
+ checkboxContainer: {
200
221
  flexDirection: 'row',
201
- marginBottom: 12,
202
- paddingLeft: 8,
222
+ padding: 16,
223
+ alignItems: 'center',
203
224
  },
204
- itemContent: {
225
+ checkbox: {
226
+ width: 24,
227
+ height: 24,
228
+ borderRadius: 4,
229
+ borderWidth: 2,
230
+ borderColor: COLORS.primary,
231
+ marginRight: 12,
232
+ },
233
+ checkboxSelected: {
234
+ backgroundColor: COLORS.primary,
235
+ },
236
+ cardContent: {
205
237
  flex: 1,
206
- marginLeft: 12,
207
238
  },
208
- itemTitle: {
209
- fontSize: 16,
210
- fontWeight: '500',
211
- color: '#000',
212
- marginBottom: 2,
239
+ cardTitle: {
240
+ fontSize: 18,
241
+ fontWeight: 'bold',
242
+ marginBottom: 4,
213
243
  },
214
- itemDescription: {
244
+ cardDescription: {
215
245
  fontSize: 14,
216
- color: '#666',
217
- },
218
- securityNotice: {
219
- flexDirection: 'row',
220
- alignItems: 'flex-start',
221
- backgroundColor: '#f5f5f5',
222
- padding: 12,
223
- borderRadius: 8,
224
- marginBottom: 24,
246
+ color: COLORS.gray,
247
+ marginBottom: 4,
225
248
  },
226
- securityText: {
227
- flex: 1,
249
+ cardReward: {
228
250
  fontSize: 14,
229
- color: '#666',
230
- marginLeft: 8,
251
+ color: COLORS.success,
252
+ fontWeight: 'bold',
231
253
  },
232
254
  footer: {
233
255
  flexDirection: 'row',
256
+ justifyContent: 'space-between',
257
+ alignItems: 'center',
234
258
  padding: 16,
235
259
  borderTopWidth: 1,
236
- borderTopColor: '#eee',
237
- },
238
- cancelButton: {
239
- flex: 1,
240
- justifyContent: 'center',
241
- alignItems: 'center',
242
- paddingVertical: 12,
243
- borderWidth: 1,
244
- borderColor: '#ddd',
245
- borderRadius: 25,
246
- marginRight: 8,
260
+ borderTopColor: COLORS.lightGray,
247
261
  },
248
- cancelButtonText: {
249
- fontSize: 16,
250
- fontWeight: '600',
251
- color: '#666',
252
- },
253
- acceptButton: {
254
- flex: 2,
255
- flexDirection: 'row',
256
- justifyContent: 'center',
257
- alignItems: 'center',
258
- paddingVertical: 12,
262
+ footerButton: {
263
+ paddingVertical: 8,
264
+ paddingHorizontal: 16,
265
+ borderRadius: 8,
259
266
  backgroundColor: COLORS.primary,
260
- borderRadius: 25,
261
- marginLeft: 8,
262
267
  },
263
- acceptButtonText: {
268
+ footerButtonText: {
269
+ color: COLORS.white,
264
270
  fontSize: 16,
265
271
  fontWeight: '600',
266
- color: '#fff',
267
- marginRight: 8,
268
272
  },
269
- buttonIcon: {
270
- marginLeft: 4,
273
+ selectedCount: {
274
+ fontSize: 16,
275
+ color: COLORS.gray,
271
276
  },
272
277
  });
@@ -25,14 +25,15 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
25
25
  requestData,
26
26
  returnLink,
27
27
  onComplete,
28
- embedd,
29
- debug,
30
- test,
28
+ embedd = false,
29
+ debug = false,
30
+ test = false,
31
31
  }) => {
32
32
  const bottomSheetRef = useRef<BottomSheet>(null);
33
33
  const [step, setStep] = useState<'connect' | 'pin' | 'training'>('connect');
34
34
  const [connections, setConnections] = useState<ConnectionStatus>({});
35
35
  const [pin, setPin] = useState<string>('');
36
+ const [selectedTier, setSelectedTier] = useState<keyof typeof requestData>('Medium');
36
37
  const [training, setTraining] = useState<{
37
38
  progress: number;
38
39
  eta: string;
@@ -90,10 +91,12 @@ export const UniversalOnboarding: React.FC<UniversalOnboardingProps> = ({
90
91
  onComplete('https://api2.onairos.uk', 'dummy-token', {
91
92
  pin: userPin,
92
93
  connections,
94
+ selectedTier,
95
+ tierData: requestData[selectedTier],
93
96
  });
94
97
  }
95
98
  }, 1000);
96
- }, [connections, onComplete]);
99
+ }, [connections, onComplete, selectedTier, requestData]);
97
100
 
98
101
  const canProceedToPin = useMemo(() => {
99
102
  const connectedPlatforms = Object.values(connections).filter(Boolean).length;
@@ -1,19 +1,23 @@
1
1
  import type { PlatformConfig } from '../types';
2
2
 
3
3
  export const COLORS = {
4
- primary: '#00BFA5',
5
- headerBg: '#E0F2F1',
4
+ primary: '#1BA9D4',
5
+ headerBg: '#F8F9FA',
6
6
  text: {
7
7
  primary: '#000000',
8
- secondary: '#757575',
8
+ secondary: '#666666',
9
9
  },
10
- border: '#EEEEEE',
11
- success: '#4CAF50',
12
- error: '#F44336',
13
- instagram: '#E4405F',
14
- pinterest: '#BD081C',
10
+ border: '#E5E5E5',
11
+ success: '#34C759',
12
+ error: '#FF3B30',
13
+ instagram: '#E1306C',
14
+ pinterest: '#E60023',
15
15
  reddit: '#FF4500',
16
16
  youtube: '#FF0000',
17
+ white: '#FFFFFF',
18
+ black: '#000000',
19
+ gray: '#666666',
20
+ lightGray: '#E5E5E5',
17
21
  };
18
22
 
19
23
  export const PLATFORMS: Record<string, PlatformConfig> = {
@@ -1,34 +1,52 @@
1
+ export interface DataTier {
2
+ type: string;
3
+ descriptions: string;
4
+ reward: string;
5
+ }
6
+
1
7
  export interface OnairosButtonProps {
8
+ returnLink?: string;
9
+ prefillUrl?: string;
2
10
  AppName: string;
3
- requestData: Record<string, Record<string, string>>;
4
- returnLink: string;
5
- embedd?: boolean;
11
+ buttonType?: 'normal' | 'pill';
12
+ requestData?: {
13
+ Small: DataTier;
14
+ Medium: DataTier;
15
+ Large: DataTier;
16
+ };
17
+ buttonWidth?: number;
18
+ buttonHeight?: number;
19
+ hasStroke?: boolean;
20
+ enabled?: boolean;
21
+ buttonForm?: 'default' | 'login' | 'signup';
22
+ onRejection?: () => void;
23
+ onResolved?: (apiUrl: string, token: string, userData: any) => void;
24
+ preCheck?: () => Promise<boolean>;
6
25
  color?: string;
7
- icon?: string;
8
- onResolved: (apiUrl: string, accessToken: string, loginDetails?: Record<string, any>) => void;
9
- login?: boolean;
10
- buttonType?: 'circle' | 'pill';
26
+ swerv?: boolean;
11
27
  debug?: boolean;
12
- test?: boolean;
28
+ preferredPlatform?: string;
29
+ testMode?: boolean;
13
30
  }
14
31
 
15
32
  export interface UniversalOnboardingProps {
16
33
  visible: boolean;
17
34
  onClose: () => void;
18
35
  AppName: string;
19
- requestData: Record<string, Record<string, string>>;
20
- returnLink: string;
21
- onComplete: (apiUrl: string, accessToken: string, data: any) => void;
22
- embedd?: boolean;
36
+ requestData?: {
37
+ Small: DataTier;
38
+ Medium: DataTier;
39
+ Large: DataTier;
40
+ };
41
+ returnLink?: string;
42
+ onComplete: (apiUrl: string, token: string, userData: any) => void;
43
+ preferredPlatform?: string;
23
44
  debug?: boolean;
24
45
  test?: boolean;
25
46
  }
26
47
 
27
48
  export interface ConnectionStatus {
28
- instagram?: { userName: string };
29
- pinterest?: { userName: string };
30
- reddit?: { userName: string };
31
- youtube?: { userName: string };
49
+ [key: string]: boolean;
32
50
  }
33
51
 
34
52
  export interface PlatformListProps {
package/src/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ export interface DataTier {
2
+ type: string;
3
+ descriptions: string;
4
+ reward: string;
5
+ }
6
+
7
+ export interface UniversalOnboardingProps {
8
+ visible: boolean;
9
+ onClose: () => void;
10
+ AppName: string;
11
+ requestData: {
12
+ Small: DataTier;
13
+ Medium: DataTier;
14
+ Large: DataTier;
15
+ };
16
+ returnLink: string;
17
+ onComplete: (apiUrl: string, token: string, data: any) => void;
18
+ embedd?: boolean;
19
+ debug?: boolean;
20
+ test?: boolean;
21
+ buttonType?: 'default' | 'pill';
22
+ buttonForm?: 'signup' | 'login';
23
+ }
24
+
25
+ export interface ConnectionStatus {
26
+ [key: string]: boolean;
27
+ }
28
+
29
+ // ... rest of the existing types ...
@@ -0,0 +1,25 @@
1
+ import { RSA } from 'react-native-rsa-native';
2
+ import { onairosApi } from '../api';
3
+
4
+ export const encryptModelKey = async (publicKey: string, modelKey: string): Promise<string> => {
5
+ try {
6
+ const encrypted = await RSA.encrypt(modelKey, publicKey);
7
+ return encrypted;
8
+ } catch (error) {
9
+ console.error('Error encrypting model key:', error);
10
+ throw error;
11
+ }
12
+ };
13
+
14
+ export const getServerPublicKey = async (): Promise<string> => {
15
+ try {
16
+ const response = await onairosApi.get('public/getPublicKey');
17
+ if (response && response.publicKey) {
18
+ return response.publicKey;
19
+ }
20
+ throw new Error('Server response does not contain publicKey field');
21
+ } catch (error) {
22
+ console.error('Error getting server public key:', error);
23
+ throw error;
24
+ }
25
+ };