@transfergratis/react-native-sdk 0.1.22 → 0.1.24

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 (142) hide show
  1. package/android/src/main/AndroidManifest.xml +9 -4
  2. package/build/components/EnhancedCameraView.d.ts.map +1 -1
  3. package/build/components/EnhancedCameraView.js +26 -3
  4. package/build/components/EnhancedCameraView.js.map +1 -1
  5. package/build/components/EnhancedCameraView.web.d.ts.map +1 -1
  6. package/build/components/EnhancedCameraView.web.js +21 -0
  7. package/build/components/EnhancedCameraView.web.js.map +1 -1
  8. package/build/components/KYCElements/CameraCapture.d.ts.map +1 -1
  9. package/build/components/KYCElements/CameraCapture.js +4 -3
  10. package/build/components/KYCElements/CameraCapture.js.map +1 -1
  11. package/build/components/KYCElements/CountrySelectionTemplate.d.ts +5 -2
  12. package/build/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  13. package/build/components/KYCElements/CountrySelectionTemplate.js +360 -101
  14. package/build/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  15. package/build/components/KYCElements/FileUpload.d.ts.map +1 -1
  16. package/build/components/KYCElements/FileUpload.js +5 -4
  17. package/build/components/KYCElements/FileUpload.js.map +1 -1
  18. package/build/components/KYCElements/FileUploadTemplate.d.ts.map +1 -1
  19. package/build/components/KYCElements/FileUploadTemplate.js +5 -4
  20. package/build/components/KYCElements/FileUploadTemplate.js.map +1 -1
  21. package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  22. package/build/components/KYCElements/IDCardCapture.js +193 -237
  23. package/build/components/KYCElements/IDCardCapture.js.map +1 -1
  24. package/build/components/KYCElements/LocationCaptureTemplate.d.ts.map +1 -1
  25. package/build/components/KYCElements/LocationCaptureTemplate.js +78 -37
  26. package/build/components/KYCElements/LocationCaptureTemplate.js.map +1 -1
  27. package/build/components/KYCElements/OrientationVideoCapture.js +3 -2
  28. package/build/components/KYCElements/OrientationVideoCapture.js.map +1 -1
  29. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js +3 -2
  30. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js.map +1 -1
  31. package/build/components/KYCElements/OrientationVideoCaptureFinal.js +3 -2
  32. package/build/components/KYCElements/OrientationVideoCaptureFinal.js.map +1 -1
  33. package/build/components/KYCElements/SelfieCapture.d.ts.map +1 -1
  34. package/build/components/KYCElements/SelfieCapture.js +4 -3
  35. package/build/components/KYCElements/SelfieCapture.js.map +1 -1
  36. package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
  37. package/build/components/KYCElements/SelfieCaptureTemplate.js +182 -39
  38. package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
  39. package/build/components/KYCElements/WelcomeTemplate.d.ts +12 -0
  40. package/build/components/KYCElements/WelcomeTemplate.d.ts.map +1 -0
  41. package/build/components/KYCElements/WelcomeTemplate.js +243 -0
  42. package/build/components/KYCElements/WelcomeTemplate.js.map +1 -0
  43. package/build/components/TemplateKYCExample.d.ts +4 -2
  44. package/build/components/TemplateKYCExample.d.ts.map +1 -1
  45. package/build/components/TemplateKYCExample.js +5 -68
  46. package/build/components/TemplateKYCExample.js.map +1 -1
  47. package/build/components/TemplateKYCFlowRefactored.d.ts +2 -1
  48. package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  49. package/build/components/TemplateKYCFlowRefactored.js +95 -9
  50. package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
  51. package/build/components/example/DynamicTemplateExample.d.ts +10 -0
  52. package/build/components/example/DynamicTemplateExample.d.ts.map +1 -0
  53. package/build/components/example/DynamicTemplateExample.js +241 -0
  54. package/build/components/example/DynamicTemplateExample.js.map +1 -0
  55. package/build/config/allowedDomains.d.ts +30 -0
  56. package/build/config/allowedDomains.d.ts.map +1 -0
  57. package/build/config/allowedDomains.js +127 -0
  58. package/build/config/allowedDomains.js.map +1 -0
  59. package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  60. package/build/hooks/useTemplateKYCFlow.js +68 -43
  61. package/build/hooks/useTemplateKYCFlow.js.map +1 -1
  62. package/build/hooks/useTemplateLoader.d.ts +14 -0
  63. package/build/hooks/useTemplateLoader.d.ts.map +1 -0
  64. package/build/hooks/useTemplateLoader.js +85 -0
  65. package/build/hooks/useTemplateLoader.js.map +1 -0
  66. package/build/i18n/en/index.d.ts +9 -0
  67. package/build/i18n/en/index.d.ts.map +1 -1
  68. package/build/i18n/en/index.js +9 -0
  69. package/build/i18n/en/index.js.map +1 -1
  70. package/build/i18n/fr/index.d.ts +9 -0
  71. package/build/i18n/fr/index.d.ts.map +1 -1
  72. package/build/i18n/fr/index.js +9 -0
  73. package/build/i18n/fr/index.js.map +1 -1
  74. package/build/index.d.ts +5 -0
  75. package/build/index.d.ts.map +1 -1
  76. package/build/index.js +8 -0
  77. package/build/index.js.map +1 -1
  78. package/build/modules/api/CardAuthentification.js +1 -0
  79. package/build/modules/api/CardAuthentification.js.map +1 -1
  80. package/build/modules/api/KYCService.d.ts +4 -1
  81. package/build/modules/api/KYCService.d.ts.map +1 -1
  82. package/build/modules/api/KYCService.js +17 -5
  83. package/build/modules/api/KYCService.js.map +1 -1
  84. package/build/modules/api/TemplateService.d.ts +45 -0
  85. package/build/modules/api/TemplateService.d.ts.map +1 -0
  86. package/build/modules/api/TemplateService.js +145 -0
  87. package/build/modules/api/TemplateService.js.map +1 -0
  88. package/build/modules/api/types.d.ts +1 -0
  89. package/build/modules/api/types.d.ts.map +1 -1
  90. package/build/modules/api/types.js.map +1 -1
  91. package/build/types/KYC.types.d.ts +144 -4
  92. package/build/types/KYC.types.d.ts.map +1 -1
  93. package/build/types/KYC.types.js +15 -0
  94. package/build/types/KYC.types.js.map +1 -1
  95. package/build/utils/cropByObb.d.ts +1 -0
  96. package/build/utils/cropByObb.d.ts.map +1 -1
  97. package/build/utils/cropByObb.js +70 -0
  98. package/build/utils/cropByObb.js.map +1 -1
  99. package/build/utils/platformAlert.d.ts +20 -0
  100. package/build/utils/platformAlert.d.ts.map +1 -0
  101. package/build/utils/platformAlert.js +67 -0
  102. package/build/utils/platformAlert.js.map +1 -0
  103. package/build/utils/template-transformer.d.ts +10 -0
  104. package/build/utils/template-transformer.d.ts.map +1 -0
  105. package/build/utils/template-transformer.js +353 -0
  106. package/build/utils/template-transformer.js.map +1 -0
  107. package/build/web/WebKYCEntry.d.ts.map +1 -1
  108. package/build/web/WebKYCEntry.js +102 -20
  109. package/build/web/WebKYCEntry.js.map +1 -1
  110. package/package.json +1 -1
  111. package/src/components/EnhancedCameraView.tsx +31 -2
  112. package/src/components/EnhancedCameraView.web.tsx +24 -0
  113. package/src/components/KYCElements/CameraCapture.tsx +4 -3
  114. package/src/components/KYCElements/CountrySelectionTemplate.tsx +410 -113
  115. package/src/components/KYCElements/FileUpload.tsx +5 -4
  116. package/src/components/KYCElements/FileUploadTemplate.tsx +5 -4
  117. package/src/components/KYCElements/IDCardCapture.tsx +196 -254
  118. package/src/components/KYCElements/LocationCaptureTemplate.tsx +95 -44
  119. package/src/components/KYCElements/OrientationVideoCapture.tsx +2 -2
  120. package/src/components/KYCElements/OrientationVideoCaptureEnhanced.tsx +2 -2
  121. package/src/components/KYCElements/OrientationVideoCaptureFinal.tsx +2 -2
  122. package/src/components/KYCElements/SelfieCapture.tsx +4 -3
  123. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +195 -41
  124. package/src/components/KYCElements/WelcomeTemplate.tsx +289 -0
  125. package/src/components/TemplateKYCExample.tsx +16 -71
  126. package/src/components/TemplateKYCFlowRefactored.tsx +121 -11
  127. package/src/components/example/DynamicTemplateExample.tsx +289 -0
  128. package/src/config/allowedDomains.ts +152 -0
  129. package/src/hooks/useTemplateKYCFlow.tsx +71 -46
  130. package/src/hooks/useTemplateLoader.ts +102 -0
  131. package/src/i18n/en/index.ts +10 -0
  132. package/src/i18n/fr/index.ts +9 -0
  133. package/src/index.ts +11 -0
  134. package/src/modules/api/CardAuthentification.ts +1 -1
  135. package/src/modules/api/KYCService.ts +18 -8
  136. package/src/modules/api/TemplateService.ts +167 -0
  137. package/src/modules/api/types.ts +1 -0
  138. package/src/types/KYC.types.ts +188 -3
  139. package/src/utils/cropByObb.ts +83 -3
  140. package/src/utils/platformAlert.ts +85 -0
  141. package/src/utils/template-transformer.ts +433 -0
  142. package/src/web/WebKYCEntry.tsx +122 -24
@@ -0,0 +1,289 @@
1
+ import React, { useState } from 'react';
2
+ import { View, Text, StyleSheet, SafeAreaView, TextInput, TouchableOpacity, ScrollView } from 'react-native';
3
+ import { TemplateKYCFlow } from '../TemplateKYCFlowRefactored';
4
+ import { VerificationState } from '../../types/KYC.types';
5
+
6
+ /**
7
+ * Example component demonstrating how to use dynamic template loading
8
+ *
9
+ * This example shows two ways to use templates:
10
+ * 1. With templateId - loads template dynamically from backend API
11
+ * 2. With direct template object - uses hardcoded template (backward compatibility)
12
+ */
13
+ export const DynamicTemplateExample: React.FC = () => {
14
+ const [templateId, setTemplateId] = useState<string>('');
15
+ const [apiKey, setApiKey] = useState<string>('');
16
+ const [showFlow, setShowFlow] = useState<boolean>(false);
17
+ const [language, setLanguage] = useState<'en' | 'fr'>('en');
18
+
19
+ const handleComplete = (data: VerificationState) => {
20
+ console.log('KYC Verification completed:', data);
21
+ setShowFlow(false);
22
+ alert('Verification completed successfully!');
23
+ };
24
+
25
+ const handleError = (error: string) => {
26
+ console.error('KYC Error:', error);
27
+ alert(`Error: ${error}`);
28
+ setShowFlow(false);
29
+ };
30
+
31
+ const handleCancel = () => {
32
+ console.log('KYC Flow cancelled');
33
+ setShowFlow(false);
34
+ };
35
+
36
+ const startFlow = () => {
37
+ if (!templateId.trim()) {
38
+ alert('Please enter a template ID');
39
+ return;
40
+ }
41
+ setShowFlow(true);
42
+ };
43
+
44
+ if (showFlow) {
45
+ return (
46
+ <SafeAreaView style={styles.container}>
47
+ <TemplateKYCFlow
48
+ templateId={templateId}
49
+ API_KEY={apiKey || undefined}
50
+ language={language}
51
+ onComplete={handleComplete}
52
+ onError={handleError}
53
+ onCancel={handleCancel}
54
+ />
55
+ </SafeAreaView>
56
+ );
57
+ }
58
+
59
+ return (
60
+ <SafeAreaView style={styles.container}>
61
+ <ScrollView style={styles.scrollView} contentContainerStyle={styles.content}>
62
+ <Text style={styles.title}>Dynamic Template KYC Example</Text>
63
+ <Text style={styles.subtitle}>
64
+ Load a KYC template dynamically from the backend API
65
+ </Text>
66
+
67
+ <View style={styles.section}>
68
+ <Text style={styles.label}>Template ID *</Text>
69
+ <TextInput
70
+ style={styles.input}
71
+ placeholder="e.g., free-style"
72
+ value={templateId}
73
+ onChangeText={setTemplateId}
74
+ autoCapitalize="none"
75
+ autoCorrect={false}
76
+ />
77
+ <Text style={styles.hint}>
78
+ Enter the template ID to load from the backend API
79
+ </Text>
80
+ </View>
81
+
82
+ <View style={styles.section}>
83
+ <Text style={styles.label}>API Key (Optional)</Text>
84
+ <TextInput
85
+ style={styles.input}
86
+ placeholder="Your API key"
87
+ value={apiKey}
88
+ onChangeText={setApiKey}
89
+ autoCapitalize="none"
90
+ autoCorrect={false}
91
+ secureTextEntry
92
+ />
93
+ <Text style={styles.hint}>
94
+ If not provided, the SDK will use token-based authentication
95
+ </Text>
96
+ </View>
97
+
98
+ <View style={styles.section}>
99
+ <Text style={styles.label}>Language</Text>
100
+ <View style={styles.languageContainer}>
101
+ <TouchableOpacity
102
+ style={[
103
+ styles.languageButton,
104
+ language === 'en' && styles.languageButtonActive,
105
+ ]}
106
+ onPress={() => setLanguage('en')}
107
+ >
108
+ <Text
109
+ style={[
110
+ styles.languageButtonText,
111
+ language === 'en' && styles.languageButtonTextActive,
112
+ ]}
113
+ >
114
+ English
115
+ </Text>
116
+ </TouchableOpacity>
117
+ <TouchableOpacity
118
+ style={[
119
+ styles.languageButton,
120
+ language === 'fr' && styles.languageButtonActive,
121
+ ]}
122
+ onPress={() => setLanguage('fr')}
123
+ >
124
+ <Text
125
+ style={[
126
+ styles.languageButtonText,
127
+ language === 'fr' && styles.languageButtonTextActive,
128
+ ]}
129
+ >
130
+ Français
131
+ </Text>
132
+ </TouchableOpacity>
133
+ </View>
134
+ </View>
135
+
136
+ <TouchableOpacity
137
+ style={[styles.button, !templateId.trim() && styles.buttonDisabled]}
138
+ onPress={startFlow}
139
+ disabled={!templateId.trim()}
140
+ >
141
+ <Text style={styles.buttonText}>Start KYC Verification</Text>
142
+ </TouchableOpacity>
143
+
144
+ <View style={styles.infoSection}>
145
+ <Text style={styles.infoTitle}>Usage Example:</Text>
146
+ <Text style={styles.code}>
147
+ {`<TemplateKYCFlow
148
+ templateId="free-style"
149
+ API_KEY="your-api-key"
150
+ language="fr"
151
+ onComplete={handleComplete}
152
+ onError={handleError}
153
+ onCancel={handleCancel}
154
+ />`}
155
+ </Text>
156
+ </View>
157
+
158
+ <View style={styles.infoSection}>
159
+ <Text style={styles.infoTitle}>Notes:</Text>
160
+ <Text style={styles.infoText}>
161
+ • Template ID is required to load from backend{'\n'}
162
+ • API Key is optional but recommended for production{'\n'}
163
+ • The SDK will automatically transform the backend template format to SDK format{'\n'}
164
+ • Templates are cached for 5 minutes to improve performance{'\n'}
165
+ • You can also use a direct template object for backward compatibility
166
+ </Text>
167
+ </View>
168
+ </ScrollView>
169
+ </SafeAreaView>
170
+ );
171
+ };
172
+
173
+ const styles = StyleSheet.create({
174
+ container: {
175
+ flex: 1,
176
+ backgroundColor: '#f5f5f5',
177
+ },
178
+ scrollView: {
179
+ flex: 1,
180
+ },
181
+ content: {
182
+ padding: 20,
183
+ paddingBottom: 40,
184
+ },
185
+ title: {
186
+ fontSize: 24,
187
+ fontWeight: 'bold',
188
+ color: '#333',
189
+ marginBottom: 8,
190
+ textAlign: 'center',
191
+ },
192
+ subtitle: {
193
+ fontSize: 16,
194
+ color: '#666',
195
+ marginBottom: 32,
196
+ textAlign: 'center',
197
+ },
198
+ section: {
199
+ marginBottom: 24,
200
+ },
201
+ label: {
202
+ fontSize: 16,
203
+ fontWeight: '600',
204
+ color: '#333',
205
+ marginBottom: 8,
206
+ },
207
+ input: {
208
+ backgroundColor: 'white',
209
+ borderWidth: 1,
210
+ borderColor: '#ddd',
211
+ borderRadius: 8,
212
+ padding: 12,
213
+ fontSize: 16,
214
+ color: '#333',
215
+ },
216
+ hint: {
217
+ fontSize: 12,
218
+ color: '#999',
219
+ marginTop: 4,
220
+ },
221
+ languageContainer: {
222
+ flexDirection: 'row',
223
+ gap: 12,
224
+ },
225
+ languageButton: {
226
+ flex: 1,
227
+ padding: 12,
228
+ backgroundColor: 'white',
229
+ borderWidth: 1,
230
+ borderColor: '#ddd',
231
+ borderRadius: 8,
232
+ alignItems: 'center',
233
+ },
234
+ languageButtonActive: {
235
+ backgroundColor: '#2DBD60',
236
+ borderColor: '#2DBD60',
237
+ },
238
+ languageButtonText: {
239
+ fontSize: 16,
240
+ color: '#666',
241
+ fontWeight: '500',
242
+ },
243
+ languageButtonTextActive: {
244
+ color: 'white',
245
+ fontWeight: '600',
246
+ },
247
+ button: {
248
+ backgroundColor: '#2DBD60',
249
+ padding: 16,
250
+ borderRadius: 8,
251
+ alignItems: 'center',
252
+ marginTop: 8,
253
+ marginBottom: 32,
254
+ },
255
+ buttonDisabled: {
256
+ backgroundColor: '#ccc',
257
+ },
258
+ buttonText: {
259
+ color: 'white',
260
+ fontSize: 16,
261
+ fontWeight: '600',
262
+ },
263
+ infoSection: {
264
+ backgroundColor: 'white',
265
+ padding: 16,
266
+ borderRadius: 8,
267
+ marginBottom: 16,
268
+ },
269
+ infoTitle: {
270
+ fontSize: 16,
271
+ fontWeight: '600',
272
+ color: '#333',
273
+ marginBottom: 8,
274
+ },
275
+ code: {
276
+ fontFamily: 'monospace',
277
+ fontSize: 12,
278
+ color: '#555',
279
+ backgroundColor: '#f5f5f5',
280
+ padding: 12,
281
+ borderRadius: 4,
282
+ overflow: 'hidden',
283
+ },
284
+ infoText: {
285
+ fontSize: 14,
286
+ color: '#666',
287
+ lineHeight: 20,
288
+ },
289
+ });
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Allowed domains configuration for callback URL validation
3
+ * These domains are permitted to receive KYC completion callbacks
4
+ */
5
+
6
+ export interface AllowedDomainConfig {
7
+ domains: string[];
8
+ enforceHttps: boolean;
9
+ allowLocalhost: boolean;
10
+ }
11
+
12
+ // Default configuration - can be overridden via environment variables
13
+ const DEFAULT_CONFIG: AllowedDomainConfig = {
14
+ domains: [
15
+ 'transfergratis.com',
16
+ 'www.transfergratis.com',
17
+ 'admin.transfergratis.com',
18
+ 'dashboard.transfergratis.com',
19
+ // Add other trusted domains here
20
+ ],
21
+ enforceHttps: true,
22
+ allowLocalhost: true, // Allow localhost for development
23
+ };
24
+
25
+ /**
26
+ * Get allowed domains from environment or use defaults
27
+ */
28
+ export const getAllowedDomainsConfig = (): AllowedDomainConfig => {
29
+ // Check for environment variable override
30
+ if (typeof window !== 'undefined' && (window as any).KYC_ALLOWED_DOMAINS) {
31
+ const envDomains = (window as any).KYC_ALLOWED_DOMAINS;
32
+ if (Array.isArray(envDomains)) {
33
+ return {
34
+ ...DEFAULT_CONFIG,
35
+ domains: envDomains,
36
+ };
37
+ }
38
+ }
39
+
40
+ return DEFAULT_CONFIG;
41
+ };
42
+
43
+ /**
44
+ * Check if a domain is in the allowed list
45
+ */
46
+ export const isDomainAllowed = (domain: string): boolean => {
47
+ const config = getAllowedDomainsConfig();
48
+
49
+ // Allow localhost in development
50
+ if (config.allowLocalhost && (domain === 'localhost' || domain.startsWith('127.0.0.1'))) {
51
+ return true;
52
+ }
53
+
54
+ // Check if domain matches any in the allowed list
55
+ return config.domains.some(allowedDomain => {
56
+ // Exact match
57
+ if (domain === allowedDomain) {
58
+ return true;
59
+ }
60
+
61
+ // Subdomain match (e.g., app.transfergratis.com matches transfergratis.com)
62
+ if (domain.endsWith('.' + allowedDomain)) {
63
+ return true;
64
+ }
65
+
66
+ return false;
67
+ });
68
+ };
69
+
70
+ /**
71
+ * Validate if a URL is allowed for callback
72
+ */
73
+ export const isCallbackUrlAllowed = (url: string): { allowed: boolean; reason?: string } => {
74
+ const config = getAllowedDomainsConfig();
75
+
76
+ try {
77
+ const urlObj = new URL(url);
78
+
79
+ // Check protocol
80
+ if (config.enforceHttps && urlObj.protocol !== 'https:') {
81
+ // Allow http for localhost in development
82
+ if (!(config.allowLocalhost && (urlObj.hostname === 'localhost' || urlObj.hostname.startsWith('127.0.0.1')))) {
83
+ return {
84
+ allowed: false,
85
+ reason: 'HTTPS required for callback URLs',
86
+ };
87
+ }
88
+ }
89
+
90
+ // Check domain
91
+ if (!isDomainAllowed(urlObj.hostname)) {
92
+ return {
93
+ allowed: false,
94
+ reason: `Domain '${urlObj.hostname}' is not in the allowed list`,
95
+ };
96
+ }
97
+
98
+ return { allowed: true };
99
+ } catch (error) {
100
+ return {
101
+ allowed: false,
102
+ reason: 'Invalid URL format',
103
+ };
104
+ }
105
+ };
106
+
107
+ /**
108
+ * Generate a signature for the callback URL parameters
109
+ * This can be used to verify the integrity of the callback
110
+ */
111
+ export const generateCallbackSignature = async (
112
+ params: Record<string, string>,
113
+ secret?: string
114
+ ): Promise<string> => {
115
+ // Only generate signature if secret is provided
116
+ if (!secret) {
117
+ return '';
118
+ }
119
+
120
+ // Sort params for consistent signature
121
+ const sortedParams = Object.keys(params)
122
+ .sort()
123
+ .map(key => `${key}=${params[key]}`)
124
+ .join('&');
125
+
126
+ const data = `${sortedParams}:${secret}`;
127
+
128
+ // Use Web Crypto API for signature generation
129
+ if (typeof window !== 'undefined' && window.crypto && window.crypto.subtle) {
130
+ try {
131
+ const encoder = new TextEncoder();
132
+ const dataBuffer = encoder.encode(data);
133
+ const hashBuffer = await window.crypto.subtle.digest('SHA-256', dataBuffer);
134
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
135
+ const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
136
+ return hashHex;
137
+ } catch (error) {
138
+ console.error('Error generating signature:', error);
139
+ return '';
140
+ }
141
+ }
142
+
143
+ // Fallback: simple hash for environments without Web Crypto API
144
+ let hash = 0;
145
+ for (let i = 0; i < data.length; i++) {
146
+ const char = data.charCodeAt(i);
147
+ hash = ((hash << 5) - hash) + char;
148
+ hash = hash & hash;
149
+ }
150
+ return Math.abs(hash).toString(16);
151
+ };
152
+