@bagelink/vue 1.15.164 → 1.15.166

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.
@@ -1,2 +1,10 @@
1
- export declare function ipapi(): Promise<any>;
1
+ export interface IpApiResult {
2
+ country_code: string;
3
+ [key: string]: unknown;
4
+ }
5
+ /**
6
+ * Resolve the visitor's country from their IP.
7
+ * Result is cached in sessionStorage so multiple TelInputs share one lookup.
8
+ */
9
+ export declare function ipapi(): Promise<IpApiResult>;
2
10
  //# sourceMappingURL=ipapi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ipapi.d.ts","sourceRoot":"","sources":["../../src/utils/ipapi.ts"],"names":[],"mappings":"AAIA,wBAAsB,KAAK,iBAmB1B"}
1
+ {"version":3,"file":"ipapi.d.ts","sourceRoot":"","sources":["../../src/utils/ipapi.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACtB;AA+DD;;;GAGG;AACH,wBAAsB,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,CAmBlD"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/vue",
3
3
  "type": "module",
4
- "version": "1.15.164",
4
+ "version": "1.15.166",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -32,7 +32,7 @@ watch(
32
32
  </div>
33
33
  </slot>
34
34
  <transition v-else :duration="300" name="slide-fade" mode="out-in" appear>
35
- <component :is="Component" :key="route?.params.itemID" />
35
+ <component :is="Component" :key="route.fullPath" />
36
36
  </transition>
37
37
  </RouterView>
38
38
  </template>
@@ -14,6 +14,8 @@ const props = defineProps<{
14
14
  placeholder?: string
15
15
  excludeCountries?: string[]
16
16
  onlyCountries?: string[]
17
+ /** Preferred ISO-2 country when geo-IP is unavailable (e.g. "NL"). */
18
+ defaultCountry?: string
17
19
  required?: boolean
18
20
  disabled?: boolean
19
21
  underlined?: boolean
@@ -100,9 +102,35 @@ function selectCountry(country: Country) {
100
102
  }
101
103
  }
102
104
 
105
+ function findCountry(iso2?: string | null) {
106
+ if (!iso2) return undefined
107
+ const code = iso2.toLowerCase()
108
+ return countries.value.find(c => c.iso2.toLowerCase() === code)
109
+ }
110
+
111
+ /** Locale / language fallback when geo-IP is blocked (CORS, rate limit, offline). */
112
+ function countryFromBrowser(): Country | undefined {
113
+ const candidates = [
114
+ props.defaultCountry,
115
+ typeof navigator !== 'undefined' ? navigator.language?.split('-')[1] : undefined,
116
+ typeof navigator !== 'undefined' ? navigator.languages?.[0]?.split('-')[1] : undefined,
117
+ ]
118
+ for (const code of candidates) {
119
+ const match = findCountry(code)
120
+ if (match) return match
121
+ }
122
+ return undefined
123
+ }
124
+
103
125
  async function getIp() {
104
126
  const { country_code } = await ipapi()
105
- selectCountry(countries.value.find(c => c.iso2 === country_code) ?? countries.value[0])
127
+ const match = findCountry(country_code) ?? countryFromBrowser()
128
+ if (match) selectCountry(match)
129
+ }
130
+
131
+ function selectFallbackCountry() {
132
+ const match = countryFromBrowser()
133
+ if (match) selectCountry(match)
106
134
  }
107
135
 
108
136
  // Get the country code for use with libphonenumber-js
@@ -175,7 +203,11 @@ async function initializeCountry() {
175
203
  if (phoneNumber.value) {
176
204
  detectCountryFromNumber(phoneNumber.value)
177
205
  } else {
178
- await getIp()
206
+ try {
207
+ await getIp()
208
+ } catch {
209
+ selectFallbackCountry()
210
+ }
179
211
  }
180
212
  const formatted = parseAndFormatPhoneNumber(phoneNumber.value)
181
213
  if (formatted !== phoneNumber.value) {
@@ -1,24 +1,92 @@
1
1
  import axios from 'axios'
2
2
 
3
- let pendingRequest: Promise<any> | null = null
3
+ export interface IpApiResult {
4
+ country_code: string
5
+ [key: string]: unknown
6
+ }
7
+
8
+ const CACHE_KEY = 'ipapi'
9
+ let pendingRequest: Promise<IpApiResult> | null = null
10
+
11
+ function normalizeCountryCode(code: unknown): string | null {
12
+ if (typeof code !== 'string') return null
13
+ const normalized = code.trim().toUpperCase()
14
+ return /^[A-Z]{2}$/.test(normalized) ? normalized : null
15
+ }
16
+
17
+ function fromCache(): IpApiResult | null {
18
+ try {
19
+ const raw = sessionStorage.getItem(CACHE_KEY)
20
+ if (!raw) return null
21
+ const data = JSON.parse(raw) as IpApiResult
22
+ const country_code = normalizeCountryCode(data?.country_code)
23
+ if (!country_code) {
24
+ sessionStorage.removeItem(CACHE_KEY)
25
+ return null
26
+ }
27
+ return { ...data, country_code }
28
+ }
29
+ catch {
30
+ sessionStorage.removeItem(CACHE_KEY)
31
+ return null
32
+ }
33
+ }
4
34
 
5
- export async function ipapi() {
6
- const apiData = sessionStorage.getItem('ipapi')
7
- if (apiData) {
8
- return JSON.parse(apiData)
35
+ function toCache(data: IpApiResult) {
36
+ try {
37
+ sessionStorage.setItem(CACHE_KEY, JSON.stringify(data))
9
38
  }
39
+ catch {
40
+ /* ignore quota / private mode */
41
+ }
42
+ }
43
+
44
+ /** Providers that support browser CORS and return a country ISO-2 code. */
45
+ const providers: Array<() => Promise<string | null>> = [
46
+ async () => {
47
+ const { data } = await axios.get('https://api.country.is/', { timeout: 4000 })
48
+ return normalizeCountryCode(data?.country)
49
+ },
50
+ async () => {
51
+ const { data } = await axios.get('https://ipaddress.to/api/lookup/my', { timeout: 4000 })
52
+ return normalizeCountryCode(data?.location?.country_code)
53
+ },
54
+ ]
55
+
56
+ async function resolveCountryCode(): Promise<string> {
57
+ for (const provider of providers) {
58
+ try {
59
+ const code = await provider()
60
+ if (code) return code
61
+ }
62
+ catch {
63
+ /* try next provider */
64
+ }
65
+ }
66
+ throw new Error('Could not resolve country from IP')
67
+ }
68
+
69
+ /**
70
+ * Resolve the visitor's country from their IP.
71
+ * Result is cached in sessionStorage so multiple TelInputs share one lookup.
72
+ */
73
+ export async function ipapi(): Promise<IpApiResult> {
74
+ const cached = fromCache()
75
+ if (cached) return cached
10
76
 
11
- // Deduplicate concurrent calls — share a single in-flight request
12
77
  if (!pendingRequest) {
13
- pendingRequest = axios.get('https://ipapi.co/json/').then(res => {
14
- const data = JSON.stringify(res.data)
15
- sessionStorage.setItem('ipapi', data)
16
- pendingRequest = null
17
- return res.data
18
- }).catch(err => {
19
- pendingRequest = null
20
- throw err
21
- })
78
+ pendingRequest = resolveCountryCode()
79
+ .then((country_code) => {
80
+ const result = { country_code }
81
+ toCache(result)
82
+ pendingRequest = null
83
+ return result
84
+ })
85
+ .catch((err) => {
86
+ pendingRequest = null
87
+ throw err
88
+ })
22
89
  }
90
+
23
91
  return pendingRequest
24
92
  }