@tagadapay/plugin-sdk 3.1.12 → 3.1.22

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 (106) hide show
  1. package/build-cdn.js +129 -11
  2. package/dist/data/iso3166.d.ts +23 -33
  3. package/dist/data/iso3166.js +134 -198
  4. package/dist/data/languages.d.ts +5 -64
  5. package/dist/data/languages.js +23 -143
  6. package/dist/external-tracker.js +968 -101
  7. package/dist/external-tracker.min.js +2 -2
  8. package/dist/external-tracker.min.js.map +4 -4
  9. package/dist/react/hooks/useISOData.js +1 -1
  10. package/dist/react/hooks/usePaymentPolling.d.ts +3 -3
  11. package/dist/tagada-sdk.js +12066 -0
  12. package/dist/tagada-sdk.min.js +50 -0
  13. package/dist/tagada-sdk.min.js.map +7 -0
  14. package/dist/v2/core/client.d.ts +4 -2
  15. package/dist/v2/core/client.js +4 -3
  16. package/dist/v2/core/errors.d.ts +75 -0
  17. package/dist/v2/core/errors.js +104 -0
  18. package/dist/v2/core/funnelClient.d.ts +2 -0
  19. package/dist/v2/core/index.d.ts +1 -0
  20. package/dist/v2/core/index.js +2 -0
  21. package/dist/v2/core/pixelMapping.d.ts +49 -0
  22. package/dist/v2/core/pixelMapping.js +325 -0
  23. package/dist/v2/core/resources/apiClient.d.ts +2 -0
  24. package/dist/v2/core/resources/apiClient.js +52 -9
  25. package/dist/v2/core/resources/checkout.d.ts +89 -30
  26. package/dist/v2/core/resources/checkout.js +8 -0
  27. package/dist/v2/core/resources/customer.d.ts +20 -19
  28. package/dist/v2/core/resources/funnel.d.ts +17 -17
  29. package/dist/v2/core/resources/payments.d.ts +84 -13
  30. package/dist/v2/core/resources/payments.js +26 -9
  31. package/dist/v2/core/types.d.ts +50 -12
  32. package/dist/v2/core/types.js +0 -3
  33. package/dist/v2/core/utils/checkout.d.ts +2 -2
  34. package/dist/v2/core/utils/checkout.js +7 -2
  35. package/dist/v2/core/utils/currency.d.ts +14 -0
  36. package/dist/v2/core/utils/currency.js +40 -0
  37. package/dist/v2/core/utils/index.d.ts +1 -0
  38. package/dist/v2/core/utils/index.js +2 -0
  39. package/dist/v2/core/utils/order.d.ts +11 -9
  40. package/dist/v2/core/utils/pluginConfig.d.ts +8 -0
  41. package/dist/v2/core/utils/pluginConfig.js +28 -0
  42. package/dist/v2/index.d.ts +3 -1
  43. package/dist/v2/index.js +1 -1
  44. package/dist/v2/react/components/FunnelScriptInjector.js +21 -0
  45. package/dist/v2/react/components/WhopCheckout.d.ts +24 -0
  46. package/dist/v2/react/components/WhopCheckout.js +231 -0
  47. package/dist/v2/react/hooks/__examples__/FunnelContextExample.js +1 -1
  48. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.d.ts +14 -0
  49. package/dist/v2/react/hooks/payment-actions/useAirwallexRadarAction.js +181 -0
  50. package/dist/v2/react/hooks/payment-actions/useErrorAction.d.ts +9 -0
  51. package/dist/v2/react/hooks/payment-actions/useErrorAction.js +21 -0
  52. package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.d.ts +14 -0
  53. package/dist/v2/react/hooks/payment-actions/useFinixRadarAction.js +187 -0
  54. package/dist/v2/react/hooks/payment-actions/useKessPayAction.d.ts +11 -0
  55. package/dist/v2/react/hooks/payment-actions/useKessPayAction.js +91 -0
  56. package/dist/v2/react/hooks/payment-actions/useMasterCardAction.d.ts +24 -0
  57. package/dist/v2/react/hooks/payment-actions/useMasterCardAction.js +221 -0
  58. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.d.ts +15 -0
  59. package/dist/v2/react/hooks/payment-actions/usePaymentActionHandler.js +142 -0
  60. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.d.ts +3 -0
  61. package/dist/v2/react/hooks/payment-actions/useProcessorAuthAction.js +13 -0
  62. package/dist/v2/react/hooks/payment-actions/useRedirectAction.d.ts +10 -0
  63. package/dist/v2/react/hooks/payment-actions/useRedirectAction.js +35 -0
  64. package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.d.ts +14 -0
  65. package/dist/v2/react/hooks/payment-actions/useStripeRadarAction.js +192 -0
  66. package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.d.ts +14 -0
  67. package/dist/v2/react/hooks/payment-actions/useThreedsAuthAction.js +81 -0
  68. package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.d.ts +11 -0
  69. package/dist/v2/react/hooks/payment-actions/useTrustFlowAction.js +84 -0
  70. package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.d.ts +14 -0
  71. package/dist/v2/react/hooks/payment-processing/usePaymentInstruments.js +36 -0
  72. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.d.ts +31 -0
  73. package/dist/v2/react/hooks/payment-processing/usePaymentProcessors.js +212 -0
  74. package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.d.ts +14 -0
  75. package/dist/v2/react/hooks/payment-redirect/useAirwallex3dsReturn.js +207 -0
  76. package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.d.ts +12 -0
  77. package/dist/v2/react/hooks/payment-redirect/useGenericPaymentReturn.js +101 -0
  78. package/dist/v2/react/hooks/useCheckoutQuery.d.ts +6 -0
  79. package/dist/v2/react/hooks/useCheckoutQuery.js +45 -0
  80. package/dist/v2/react/hooks/useGeoLocation.d.ts +2 -1
  81. package/dist/v2/react/hooks/useGeoLocation.js +4 -2
  82. package/dist/v2/react/hooks/useISOData.js +1 -1
  83. package/dist/v2/react/hooks/usePaymentPolling.d.ts +3 -3
  84. package/dist/v2/react/hooks/usePaymentQuery.d.ts +18 -5
  85. package/dist/v2/react/hooks/usePaymentQuery.js +63 -1015
  86. package/dist/v2/react/hooks/usePaymentRetrieve.d.ts +3 -2
  87. package/dist/v2/react/hooks/usePaymentRetrieve.js +3 -1
  88. package/dist/v2/react/hooks/usePixelTracking.d.ts +5 -48
  89. package/dist/v2/react/hooks/usePixelTracking.js +212 -514
  90. package/dist/v2/react/hooks/useShippingRatesQuery.js +13 -5
  91. package/dist/v2/react/hooks/useWhopPaymentPolling.d.ts +30 -0
  92. package/dist/v2/react/hooks/useWhopPaymentPolling.js +61 -0
  93. package/dist/v2/react/index.d.ts +7 -0
  94. package/dist/v2/react/index.js +4 -0
  95. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.d.ts +2 -1
  96. package/dist/v2/react/providers/ExpressPaymentMethodsProvider.js +3 -1
  97. package/dist/v2/react/providers/TagadaProvider.js +71 -2
  98. package/dist/v2/standalone/external-tracker.d.ts +52 -46
  99. package/dist/v2/standalone/external-tracker.js +205 -98
  100. package/dist/v2/standalone/index.d.ts +22 -0
  101. package/dist/v2/standalone/index.js +126 -1
  102. package/package.json +3 -3
  103. package/dist/react/utils/__tests__/urlUtils.test.d.ts +0 -1
  104. package/dist/react/utils/__tests__/urlUtils.test.js +0 -189
  105. package/dist/v2/core/__tests__/pathRemapping.test.d.ts +0 -11
  106. package/dist/v2/core/__tests__/pathRemapping.test.js +0 -776
package/build-cdn.js CHANGED
@@ -4,8 +4,10 @@
4
4
  * Build script for creating standalone CDN bundles
5
5
  *
6
6
  * This creates:
7
- * - dist/external-tracker.min.js - Minified UMD bundle for CDN
7
+ * - dist/external-tracker.min.js - Minified UMD bundle for CDN (tracking only)
8
8
  * - dist/external-tracker.js - Non-minified for debugging
9
+ * - dist/tagada-sdk.min.js - Full standalone SDK bundle for CDN
10
+ * - dist/tagada-sdk.js - Non-minified for debugging
9
11
  *
10
12
  * Usage:
11
13
  * node build-cdn.js
@@ -14,16 +16,23 @@
14
16
 
15
17
  const esbuild = require('esbuild');
16
18
  const path = require('path');
19
+ const fs = require('fs');
17
20
 
18
- const ENTRY_POINT = path.join(__dirname, 'src/v2/standalone/external-tracker.ts');
21
+ const TRACKER_ENTRY = path.join(__dirname, 'src/v2/standalone/external-tracker.ts');
22
+ const SDK_ENTRY = path.join(__dirname, 'src/v2/standalone/index.ts');
19
23
  const OUT_DIR = path.join(__dirname, 'dist');
20
24
 
21
25
  async function build() {
22
- console.log('🔨 Building external-tracker CDN bundles...\n');
26
+ console.log('🔨 Building CDN bundles...\n');
23
27
 
28
+ // ==========================================================================
29
+ // EXTERNAL TRACKER (lightweight, tracking only)
30
+ // ==========================================================================
31
+ console.log('📦 Building external-tracker...');
32
+
24
33
  // Build minified UMD bundle
25
34
  await esbuild.build({
26
- entryPoints: [ENTRY_POINT],
35
+ entryPoints: [TRACKER_ENTRY],
27
36
  bundle: true,
28
37
  minify: true,
29
38
  sourcemap: true,
@@ -59,7 +68,7 @@ if (typeof window !== 'undefined' && TagadaTrackerBundle && TagadaTrackerBundle.
59
68
 
60
69
  // Build non-minified version for debugging
61
70
  await esbuild.build({
62
- entryPoints: [ENTRY_POINT],
71
+ entryPoints: [TRACKER_ENTRY],
63
72
  bundle: true,
64
73
  minify: false,
65
74
  sourcemap: false,
@@ -90,19 +99,128 @@ if (typeof window !== 'undefined' && TagadaTrackerBundle && TagadaTrackerBundle.
90
99
 
91
100
  console.log(' ✅ dist/external-tracker.js (non-minified for debugging)');
92
101
 
102
+ // ==========================================================================
103
+ // FULL STANDALONE SDK (createTagadaClient, config loading, hot reload, etc.)
104
+ // ==========================================================================
105
+ console.log('\n📦 Building tagada-sdk (full standalone SDK)...');
106
+
107
+ // Build minified SDK bundle
108
+ await esbuild.build({
109
+ entryPoints: [SDK_ENTRY],
110
+ bundle: true,
111
+ minify: true,
112
+ sourcemap: true,
113
+ target: ['es2018', 'chrome58', 'firefox57', 'safari11', 'edge79'],
114
+ format: 'iife',
115
+ globalName: 'TagadaSDKBundle',
116
+ outfile: path.join(OUT_DIR, 'tagada-sdk.min.js'),
117
+ external: [],
118
+ define: {
119
+ 'process.env.NODE_ENV': '"production"',
120
+ },
121
+ footer: {
122
+ js: `
123
+ // Expose SDK under window.tgd namespace (clean, single global)
124
+ if (typeof window !== 'undefined' && TagadaSDKBundle) {
125
+ window.tgd = window.tgd || {};
126
+ // Core
127
+ window.tgd.createTagadaClient = TagadaSDKBundle.createTagadaClient;
128
+ // Config utilities
129
+ window.tgd.getPluginConfig = TagadaSDKBundle.getPluginConfig;
130
+ window.tgd.loadPluginConfig = TagadaSDKBundle.loadPluginConfig;
131
+ // Hot reload
132
+ window.tgd.onConfigUpdate = TagadaSDKBundle.onConfigUpdate;
133
+ window.tgd.sendConfigUpdate = TagadaSDKBundle.sendConfigUpdate;
134
+ window.tgd.broadcastConfigUpdate = TagadaSDKBundle.broadcastConfigUpdate;
135
+ // Currency
136
+ window.tgd.formatMoney = TagadaSDKBundle.formatMoney;
137
+ // Script injection (auto-runs on load, but can be manually called)
138
+ window.tgd.injectStepConfigScripts = TagadaSDKBundle.injectStepConfigScripts;
139
+ // Full bundle reference
140
+ window.tgd.sdk = TagadaSDKBundle;
141
+ }
142
+ `.trim(),
143
+ },
144
+ banner: {
145
+ js: `/**
146
+ * TagadaPay SDK v${require('./package.json').version}
147
+ * CDN Bundle - Full standalone SDK for external/vanilla pages
148
+ * Usage: window.tgd.createTagadaClient(), window.tgd.formatMoney(), etc.
149
+ * @license MIT
150
+ */`,
151
+ },
152
+ });
153
+
154
+ console.log(' ✅ dist/tagada-sdk.min.js (minified + sourcemap)');
155
+
156
+ // Build non-minified SDK version for debugging
157
+ await esbuild.build({
158
+ entryPoints: [SDK_ENTRY],
159
+ bundle: true,
160
+ minify: false,
161
+ sourcemap: false,
162
+ target: ['es2018', 'chrome58', 'firefox57', 'safari11', 'edge79'],
163
+ format: 'iife',
164
+ globalName: 'TagadaSDKBundle',
165
+ outfile: path.join(OUT_DIR, 'tagada-sdk.js'),
166
+ external: [],
167
+ define: {
168
+ 'process.env.NODE_ENV': '"development"',
169
+ },
170
+ footer: {
171
+ js: `
172
+ // Expose SDK under window.tgd namespace (clean, single global)
173
+ if (typeof window !== 'undefined' && TagadaSDKBundle) {
174
+ window.tgd = window.tgd || {};
175
+ window.tgd.createTagadaClient = TagadaSDKBundle.createTagadaClient;
176
+ window.tgd.getPluginConfig = TagadaSDKBundle.getPluginConfig;
177
+ window.tgd.loadPluginConfig = TagadaSDKBundle.loadPluginConfig;
178
+ window.tgd.onConfigUpdate = TagadaSDKBundle.onConfigUpdate;
179
+ window.tgd.sendConfigUpdate = TagadaSDKBundle.sendConfigUpdate;
180
+ window.tgd.broadcastConfigUpdate = TagadaSDKBundle.broadcastConfigUpdate;
181
+ window.tgd.formatMoney = TagadaSDKBundle.formatMoney;
182
+ window.tgd.injectStepConfigScripts = TagadaSDKBundle.injectStepConfigScripts;
183
+ window.tgd.sdk = TagadaSDKBundle;
184
+ }
185
+ `.trim(),
186
+ },
187
+ banner: {
188
+ js: `/**
189
+ * TagadaPay SDK v${require('./package.json').version}
190
+ * CDN Bundle - Full standalone SDK (Debug Build)
191
+ * Usage: window.tgd.createTagadaClient(), window.tgd.formatMoney(), etc.
192
+ * @license MIT
193
+ */`,
194
+ },
195
+ });
196
+
197
+ console.log(' ✅ dist/tagada-sdk.js (non-minified for debugging)');
198
+
93
199
  // Get file sizes
94
- const fs = require('fs');
95
- const minSize = fs.statSync(path.join(OUT_DIR, 'external-tracker.min.js')).size;
96
- const fullSize = fs.statSync(path.join(OUT_DIR, 'external-tracker.js')).size;
200
+ const trackerMinSize = fs.statSync(path.join(OUT_DIR, 'external-tracker.min.js')).size;
201
+ const trackerFullSize = fs.statSync(path.join(OUT_DIR, 'external-tracker.js')).size;
202
+ const sdkMinSize = fs.statSync(path.join(OUT_DIR, 'tagada-sdk.min.js')).size;
203
+ const sdkFullSize = fs.statSync(path.join(OUT_DIR, 'tagada-sdk.js')).size;
97
204
 
98
205
  console.log('\n📦 Bundle sizes:');
99
- console.log(` external-tracker.min.js: ${(minSize / 1024).toFixed(2)} KB`);
100
- console.log(` external-tracker.js: ${(fullSize / 1024).toFixed(2)} KB`);
206
+ console.log(` external-tracker.min.js: ${(trackerMinSize / 1024).toFixed(2)} KB`);
207
+ console.log(` external-tracker.js: ${(trackerFullSize / 1024).toFixed(2)} KB`);
208
+ console.log(` tagada-sdk.min.js: ${(sdkMinSize / 1024).toFixed(2)} KB`);
209
+ console.log(` tagada-sdk.js: ${(sdkFullSize / 1024).toFixed(2)} KB`);
101
210
 
102
211
  console.log('\n✨ CDN build complete!\n');
103
212
  console.log('Usage via CDN:');
213
+ console.log('');
214
+ console.log(' // External Tracker (lightweight, tracking only):');
104
215
  console.log(' <script src="https://cdn.jsdelivr.net/npm/@tagadapay/plugin-sdk/dist/external-tracker.min.js"></script>');
105
- console.log(' <script>TagadaTracker.init({ storeId: "store_xxx", ... });</script>\n');
216
+ console.log(' <script>TagadaTracker.init({ storeId: "store_xxx", ... });</script>');
217
+ console.log('');
218
+ console.log(' // Full SDK (createTagadaClient, config, hot reload):');
219
+ console.log(' <script src="https://cdn.jsdelivr.net/npm/@tagadapay/plugin-sdk/dist/tagada-sdk.min.js"></script>');
220
+ console.log(' <script>');
221
+ console.log(' const client = createTagadaClient({ features: { funnel: true } });');
222
+ console.log(' client.subscribe((state) => console.log(state));');
223
+ console.log(' </script>\n');
106
224
  }
107
225
 
108
226
  build().catch((err) => {
@@ -1,33 +1,16 @@
1
- export declare const AVAILABLE_LANGUAGES: readonly ["en", "ru", "de", "fr", "es", "zh", "hi", "pt", "ja", "ar", "it", "he"];
2
- export type SupportedLanguage = typeof AVAILABLE_LANGUAGES[number];
3
- /**
4
- * Register a language database for use
5
- * @param language - Language code
6
- * @param database - The language database from iso3166-2-db
7
- * @example
8
- * import ruDatabase from 'iso3166-2-db/i18n/dispute/UN/ru';
9
- * registerLanguage('ru', ruDatabase);
10
- */
11
- export declare function registerLanguage(language: SupportedLanguage, database: any): void;
12
- /**
13
- * Check if a language is registered
14
- * @param language - Language code to check
15
- * @returns true if the language is registered
16
- */
17
- export declare function isLanguageRegistered(language: SupportedLanguage): boolean;
18
- /**
19
- * Get list of registered languages
20
- * @returns Array of registered language codes
21
- */
22
- export declare function getRegisteredLanguages(): SupportedLanguage[];
23
1
  /**
24
- * Dynamically import and register a language
25
- * @param language - Language code to import
26
- * @returns Promise that resolves when language is registered
27
- * @example
28
- * await importLanguage('ru');
2
+ * Geodata CDN-backed country/region data.
3
+ *
4
+ * Instead of bundling the 775KB iso3166-2-db package, we fetch slim JSON
5
+ * from Vercel Blob CDN on demand:
6
+ * - countries.json (~10KB) — fetched once on first getCountries() call
7
+ * - regions.json (~95KB) — fetched lazily on first getStatesForCountry() call
8
+ *
9
+ * Both are cached in memory after first fetch. The API surface is identical
10
+ * to the previous static-import version so all consumers work unchanged.
29
11
  */
30
- export declare function importLanguage(language: SupportedLanguage): Promise<void>;
12
+ export declare const AVAILABLE_LANGUAGES: readonly ["en", "ru", "de", "da", "fr", "es", "zh", "hi", "pt", "ja", "ar", "it", "he"];
13
+ export type SupportedLanguage = typeof AVAILABLE_LANGUAGES[number];
31
14
  export interface Country {
32
15
  code: string;
33
16
  name: string;
@@ -41,20 +24,27 @@ export interface State {
41
24
  countryCode: string;
42
25
  uniqueKey?: string;
43
26
  }
27
+ export declare function ensureCountriesLoaded(language?: SupportedLanguage): Promise<void>;
28
+ export declare function ensureRegionsLoaded(language?: SupportedLanguage): Promise<void>;
29
+ export declare function ensureGeoDataLoaded(language?: SupportedLanguage): Promise<void>;
44
30
  export declare const getCountries: (language?: SupportedLanguage) => Country[];
45
- export declare const getAllStates: (language?: SupportedLanguage) => State[];
46
31
  export declare const getStatesForCountry: (countryCode: string, language?: SupportedLanguage) => State[];
32
+ export declare const getAllStates: (language?: SupportedLanguage) => State[];
47
33
  export declare const findCountryByName: (countryName: string, language?: SupportedLanguage) => Country | null;
48
34
  export declare const findRegionByCode: (regionCode: string, language?: SupportedLanguage) => State | null;
49
35
  export declare const isValidCountryCode: (countryCode: string, language?: SupportedLanguage) => boolean;
50
36
  export declare const isValidStateCode: (countryCode: string, stateCode: string, language?: SupportedLanguage) => boolean;
51
37
  export declare const getCountryWithRegions: (countryCode: string, language?: SupportedLanguage) => {
52
38
  country: {
53
- code: any;
54
- name: any;
55
- iso3: any;
56
- numeric: any;
39
+ code: string;
40
+ name: string;
41
+ iso3: string | undefined;
42
+ numeric: number | undefined;
57
43
  uniqueKey: string;
58
44
  };
59
45
  regions: State[];
60
46
  } | null;
47
+ export declare function registerLanguage(_language: SupportedLanguage, _database: any): void;
48
+ export declare function isLanguageRegistered(language: SupportedLanguage): boolean;
49
+ export declare function getRegisteredLanguages(): SupportedLanguage[];
50
+ export declare function importLanguage(language: SupportedLanguage): Promise<void>;
@@ -1,230 +1,166 @@
1
- // Modular language database system - import only what you need
2
- // English is imported by default as fallback
3
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
4
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
5
- import enDatabase from 'iso3166-2-db/i18n/dispute/UN/en';
6
- // All available languages from iso3166-2-db package
7
- export const AVAILABLE_LANGUAGES = ['en', 'ru', 'de', 'fr', 'es', 'zh', 'hi', 'pt', 'ja', 'ar', 'it', 'he'];
8
- // Dynamic language database registry - starts with English only
9
- const languageDatabases = {
10
- en: enDatabase, // English (always available)
11
- };
12
1
  /**
13
- * Register a language database for use
14
- * @param language - Language code
15
- * @param database - The language database from iso3166-2-db
16
- * @example
17
- * import ruDatabase from 'iso3166-2-db/i18n/dispute/UN/ru';
18
- * registerLanguage('ru', ruDatabase);
2
+ * Geodata CDN-backed country/region data.
3
+ *
4
+ * Instead of bundling the 775KB iso3166-2-db package, we fetch slim JSON
5
+ * from Vercel Blob CDN on demand:
6
+ * - countries.json (~10KB) fetched once on first getCountries() call
7
+ * - regions.json (~95KB) — fetched lazily on first getStatesForCountry() call
8
+ *
9
+ * Both are cached in memory after first fetch. The API surface is identical
10
+ * to the previous static-import version so all consumers work unchanged.
19
11
  */
20
- export function registerLanguage(language, database) {
21
- if (!AVAILABLE_LANGUAGES.includes(language)) {
22
- console.warn(`Language ${language} is not supported. Available languages: ${AVAILABLE_LANGUAGES.join(', ')}`);
23
- return;
24
- }
25
- languageDatabases[language] = database;
12
+ // CDN base serving the pre-built slim geodata (uploaded by scripts/build-geodata-cdn.ts)
13
+ const GEODATA_CDN_BASE = 'https://cvwnizdbugpz6jwk.public.blob.vercel-storage.com/geodata/v1';
14
+ export const AVAILABLE_LANGUAGES = ['en', 'ru', 'de', 'da', 'fr', 'es', 'zh', 'hi', 'pt', 'ja', 'ar', 'it', 'he'];
15
+ // ─── In-memory cache ─────────────────────────────────────────────────────────
16
+ const countriesCache = new Map();
17
+ const regionsCache = new Map();
18
+ const inflightCountries = new Map();
19
+ const inflightRegions = new Map();
20
+ // ─── CDN fetchers (deduplicated, cached) ─────────────────────────────────────
21
+ async function fetchCountriesRaw(language) {
22
+ const cached = countriesCache.get(language);
23
+ if (cached)
24
+ return cached;
25
+ // Deduplicate concurrent fetches for the same language
26
+ let inflight = inflightCountries.get(language);
27
+ if (inflight)
28
+ return inflight;
29
+ inflight = (async () => {
30
+ const url = `${GEODATA_CDN_BASE}/${language}/countries.json`;
31
+ const res = await fetch(url);
32
+ if (!res.ok)
33
+ throw new Error(`Geodata fetch failed: ${url} (${res.status})`);
34
+ const data = await res.json();
35
+ countriesCache.set(language, data);
36
+ inflightCountries.delete(language);
37
+ return data;
38
+ })();
39
+ inflightCountries.set(language, inflight);
40
+ return inflight;
26
41
  }
27
- /**
28
- * Check if a language is registered
29
- * @param language - Language code to check
30
- * @returns true if the language is registered
31
- */
32
- export function isLanguageRegistered(language) {
33
- return language in languageDatabases;
42
+ async function fetchRegionsRaw(language) {
43
+ const cached = regionsCache.get(language);
44
+ if (cached)
45
+ return cached;
46
+ let inflight = inflightRegions.get(language);
47
+ if (inflight)
48
+ return inflight;
49
+ inflight = (async () => {
50
+ const url = `${GEODATA_CDN_BASE}/${language}/regions.json`;
51
+ const res = await fetch(url);
52
+ if (!res.ok)
53
+ throw new Error(`Geodata fetch failed: ${url} (${res.status})`);
54
+ const data = await res.json();
55
+ regionsCache.set(language, data);
56
+ inflightRegions.delete(language);
57
+ return data;
58
+ })();
59
+ inflightRegions.set(language, inflight);
60
+ return inflight;
34
61
  }
35
- /**
36
- * Get list of registered languages
37
- * @returns Array of registered language codes
38
- */
39
- export function getRegisteredLanguages() {
40
- return Object.keys(languageDatabases);
62
+ // ─── Synchronous accessors (return cached data, empty if not yet loaded) ─────
63
+ // These preserve the existing synchronous API. Consumers should call
64
+ // ensureCountriesLoaded / ensureRegionsLoaded first (the useISOData hook does).
65
+ function getCountriesCached(language) {
66
+ return countriesCache.get(language) || countriesCache.get('en') || {};
41
67
  }
42
- /**
43
- * Dynamically import and register a language
44
- * @param language - Language code to import
45
- * @returns Promise that resolves when language is registered
46
- * @example
47
- * await importLanguage('ru');
48
- */
49
- export async function importLanguage(language) {
50
- if (isLanguageRegistered(language)) {
51
- return; // Already registered
52
- }
53
- try {
54
- let database;
55
- switch (language) {
56
- case 'ru':
57
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
58
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
59
- database = (await import('iso3166-2-db/i18n/dispute/UN/ru')).default;
60
- break;
61
- case 'de':
62
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
63
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
64
- database = (await import('iso3166-2-db/i18n/dispute/UN/de')).default;
65
- break;
66
- case 'fr':
67
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
68
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
69
- database = (await import('iso3166-2-db/i18n/dispute/UN/fr')).default;
70
- break;
71
- case 'es':
72
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
73
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
74
- database = (await import('iso3166-2-db/i18n/dispute/UN/es')).default;
75
- break;
76
- case 'zh':
77
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
78
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
79
- database = (await import('iso3166-2-db/i18n/dispute/UN/zh')).default;
80
- break;
81
- case 'hi':
82
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
83
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
84
- database = (await import('iso3166-2-db/i18n/dispute/UN/hi')).default;
85
- break;
86
- case 'pt':
87
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
88
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
89
- database = (await import('iso3166-2-db/i18n/dispute/UN/pt')).default;
90
- break;
91
- case 'ja':
92
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
93
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
94
- database = (await import('iso3166-2-db/i18n/dispute/UN/ja')).default;
95
- break;
96
- case 'ar':
97
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
98
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
99
- database = (await import('iso3166-2-db/i18n/dispute/UN/ar')).default;
100
- break;
101
- case 'it':
102
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
103
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
104
- database = (await import('iso3166-2-db/i18n/dispute/UN/it')).default;
105
- break;
106
- case 'he':
107
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
108
- // @ts-ignore - iso3166-2-db doesn't have TypeScript definitions
109
- database = (await import('iso3166-2-db/i18n/dispute/UN/he')).default;
110
- break;
111
- default:
112
- throw new Error(`Language ${language} is not supported`);
113
- }
114
- registerLanguage(language, database);
115
- }
116
- catch (error) {
117
- console.error(`Failed to import language ${language}:`, error);
118
- throw new Error(`Failed to import language ${language}`);
119
- }
68
+ function getRegionsCached(language) {
69
+ return regionsCache.get(language) || regionsCache.get('en') || {};
120
70
  }
121
- // Function to get language-specific database
122
- function getWorldDatabase(language = 'en') {
123
- const database = languageDatabases[language];
124
- if (!database) {
125
- console.warn(`Language ${language} not available, falling back to English. Use importLanguage('${language}') to load it.`);
126
- return languageDatabases.en;
127
- }
128
- return database;
71
+ // ─── Public async loaders (call these before using sync getters) ─────────────
72
+ export async function ensureCountriesLoaded(language = 'en') {
73
+ await fetchCountriesRaw(language);
129
74
  }
130
- // Transform the ISO3166 data into our expected format
75
+ export async function ensureRegionsLoaded(language = 'en') {
76
+ await fetchRegionsRaw(language);
77
+ }
78
+ export async function ensureGeoDataLoaded(language = 'en') {
79
+ await Promise.all([fetchCountriesRaw(language), fetchRegionsRaw(language)]);
80
+ }
81
+ // ─── Backward-compatible sync API ────────────────────────────────────────────
82
+ // These match the old iso3166.ts signatures exactly.
131
83
  export const getCountries = (language = 'en') => {
132
- const worldDatabase = getWorldDatabase(language);
133
- const countries = [];
134
- Object.keys(worldDatabase).forEach((countryCode) => {
135
- const countryData = worldDatabase[countryCode];
136
- countries.push({
137
- code: countryData.iso, // iso3166-1 alpha-2 code
138
- name: countryData.name,
139
- iso3: countryData.iso3, // iso3166-1 alpha-3 code
140
- numeric: countryData.numeric,
141
- uniqueKey: `country-${countryData.iso}`,
142
- });
143
- });
144
- // Sort countries alphabetically by name
145
- return countries.sort((a, b) => a.name.localeCompare(b.name));
146
- };
147
- // Get all states/regions for all countries
148
- export const getAllStates = (language = 'en') => {
149
- const worldDatabase = getWorldDatabase(language);
150
- const states = [];
151
- Object.keys(worldDatabase).forEach((countryCode) => {
152
- const countryData = worldDatabase[countryCode];
153
- if (countryData.regions && Array.isArray(countryData.regions)) {
154
- countryData.regions.forEach((region, index) => {
155
- states.push({
156
- code: region.iso, // iso3166-2 code (the part after the dash)
157
- name: region.name,
158
- countryCode: countryData.iso,
159
- uniqueKey: `state-${countryData.iso}-${region.iso}-${index}`,
160
- });
161
- });
162
- }
163
- });
164
- // Sort states alphabetically by name
165
- return states.sort((a, b) => a.name.localeCompare(b.name));
84
+ const raw = getCountriesCached(language);
85
+ return Object.entries(raw)
86
+ .map(([code, c]) => ({
87
+ code,
88
+ name: c.n,
89
+ iso3: c.i3,
90
+ numeric: c.nu,
91
+ uniqueKey: `country-${code}`,
92
+ }))
93
+ .sort((a, b) => a.name.localeCompare(b.name));
166
94
  };
167
- // Get states for a specific country
168
95
  export const getStatesForCountry = (countryCode, language = 'en') => {
169
- const worldDatabase = getWorldDatabase(language);
170
- const countryData = worldDatabase[countryCode];
171
- if (!countryData?.regions || !Array.isArray(countryData.regions)) {
96
+ const raw = getRegionsCached(language);
97
+ const tuples = raw[countryCode];
98
+ if (!tuples)
172
99
  return [];
173
- }
174
- return countryData.regions
175
- .map((region, index) => ({
176
- code: region.iso,
177
- name: region.name,
178
- countryCode: countryData.iso,
179
- uniqueKey: `state-${countryData.iso}-${region.iso}-${index}`,
100
+ return tuples
101
+ .map(([iso, name], i) => ({
102
+ code: iso,
103
+ name,
104
+ countryCode,
105
+ uniqueKey: `state-${countryCode}-${iso}-${i}`,
180
106
  }))
181
107
  .sort((a, b) => a.name.localeCompare(b.name));
182
108
  };
183
- // Find country by name (fuzzy search)
109
+ export const getAllStates = (language = 'en') => {
110
+ const raw = getRegionsCached(language);
111
+ const states = [];
112
+ for (const [countryCode, tuples] of Object.entries(raw)) {
113
+ tuples.forEach(([iso, name], i) => {
114
+ states.push({ code: iso, name, countryCode, uniqueKey: `state-${countryCode}-${iso}-${i}` });
115
+ });
116
+ }
117
+ return states.sort((a, b) => a.name.localeCompare(b.name));
118
+ };
184
119
  export const findCountryByName = (countryName, language = 'en') => {
185
120
  const countries = getCountries(language);
186
- const normalizedSearchName = countryName.toLowerCase().trim();
187
- // Exact match first
188
- const exactMatch = countries.find((country) => country.name.toLowerCase() === normalizedSearchName);
189
- if (exactMatch)
190
- return exactMatch;
191
- // Partial match
192
- const partialMatch = countries.find((country) => country.name.toLowerCase().includes(normalizedSearchName) ||
193
- normalizedSearchName.includes(country.name.toLowerCase()));
194
- return partialMatch ?? null;
121
+ const needle = countryName.toLowerCase().trim();
122
+ return countries.find(c => c.name.toLowerCase() === needle)
123
+ || countries.find(c => c.name.toLowerCase().includes(needle) || needle.includes(c.name.toLowerCase()))
124
+ || null;
195
125
  };
196
- // Find region by ISO code (e.g., "US-CA" for California)
197
126
  export const findRegionByCode = (regionCode, language = 'en') => {
198
127
  const [countryCode, stateCode] = regionCode.split('-');
199
128
  if (!countryCode || !stateCode)
200
129
  return null;
201
- const states = getStatesForCountry(countryCode, language);
202
- return states.find((state) => state.code === stateCode) ?? null;
130
+ return getStatesForCountry(countryCode, language).find(s => s.code === stateCode) ?? null;
203
131
  };
204
- // Validate if a country code exists
205
132
  export const isValidCountryCode = (countryCode, language = 'en') => {
206
- const worldDatabase = getWorldDatabase(language);
207
- return Object.prototype.hasOwnProperty.call(worldDatabase, countryCode);
133
+ return countryCode in getCountriesCached(language);
208
134
  };
209
- // Validate if a state code exists for a given country
210
135
  export const isValidStateCode = (countryCode, stateCode, language = 'en') => {
211
- const states = getStatesForCountry(countryCode, language);
212
- return states.some((state) => state.code === stateCode);
136
+ return getStatesForCountry(countryCode, language).some(s => s.code === stateCode);
213
137
  };
214
- // Get country info including regions
215
138
  export const getCountryWithRegions = (countryCode, language = 'en') => {
216
- const worldDatabase = getWorldDatabase(language);
217
- const countryData = worldDatabase[countryCode];
218
- if (!countryData)
139
+ const raw = getCountriesCached(language);
140
+ const c = raw[countryCode];
141
+ if (!c)
219
142
  return null;
220
143
  return {
221
- country: {
222
- code: countryData.iso,
223
- name: countryData.name,
224
- iso3: countryData.iso3,
225
- numeric: countryData.numeric,
226
- uniqueKey: `country-${countryData.iso}`,
227
- },
144
+ country: { code: countryCode, name: c.n, iso3: c.i3, numeric: c.nu, uniqueKey: `country-${countryCode}` },
228
145
  regions: getStatesForCountry(countryCode, language),
229
146
  };
230
147
  };
148
+ // ─── Language management (backward-compat) ───────────────────────────────────
149
+ export function registerLanguage(_language, _database) {
150
+ // No-op: CDN mode doesn't need manual registration
151
+ }
152
+ export function isLanguageRegistered(language) {
153
+ return countriesCache.has(language);
154
+ }
155
+ export function getRegisteredLanguages() {
156
+ return [...countriesCache.keys()];
157
+ }
158
+ export async function importLanguage(language) {
159
+ await ensureGeoDataLoaded(language);
160
+ }
161
+ // ─── Prefetch ────────────────────────────────────────────────────────────────
162
+ // Kick off English fetch the moment this module is imported so the country
163
+ // dropdown is populated by the time the React tree renders.
164
+ if (typeof globalThis !== 'undefined' && typeof fetch !== 'undefined') {
165
+ ensureGeoDataLoaded('en').catch(() => { });
166
+ }