@planet-matrix/mobius-model 0.4.0 → 0.6.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 (179) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +134 -21
  3. package/dist/index.js +45 -4
  4. package/dist/index.js.map +186 -11
  5. package/oxlint.config.ts +6 -0
  6. package/package.json +16 -10
  7. package/src/abort/README.md +92 -0
  8. package/src/abort/abort-manager.ts +278 -0
  9. package/src/abort/abort-signal-listener-manager.ts +81 -0
  10. package/src/abort/index.ts +2 -0
  11. package/src/basic/README.md +69 -117
  12. package/src/basic/enhance.ts +10 -0
  13. package/src/basic/function.ts +81 -62
  14. package/src/basic/index.ts +2 -0
  15. package/src/basic/is.ts +152 -71
  16. package/src/basic/object.ts +82 -0
  17. package/src/basic/promise.ts +29 -8
  18. package/src/basic/string.ts +2 -33
  19. package/src/color/README.md +105 -0
  20. package/src/color/index.ts +3 -0
  21. package/src/color/internal.ts +42 -0
  22. package/src/color/rgb/analyze.ts +236 -0
  23. package/src/color/rgb/construct.ts +130 -0
  24. package/src/color/rgb/convert.ts +227 -0
  25. package/src/color/rgb/derive.ts +303 -0
  26. package/src/color/rgb/index.ts +6 -0
  27. package/src/color/rgb/internal.ts +208 -0
  28. package/src/color/rgb/parse.ts +302 -0
  29. package/src/color/rgb/serialize.ts +144 -0
  30. package/src/color/types.ts +57 -0
  31. package/src/color/xyz/analyze.ts +80 -0
  32. package/src/color/xyz/construct.ts +19 -0
  33. package/src/color/xyz/convert.ts +71 -0
  34. package/src/color/xyz/index.ts +3 -0
  35. package/src/color/xyz/internal.ts +23 -0
  36. package/src/css/README.md +93 -0
  37. package/src/css/class.ts +559 -0
  38. package/src/css/index.ts +1 -0
  39. package/src/encoding/README.md +92 -0
  40. package/src/encoding/base64.ts +107 -0
  41. package/src/encoding/index.ts +1 -0
  42. package/src/environment/README.md +97 -0
  43. package/src/environment/basic.ts +26 -0
  44. package/src/environment/device.ts +311 -0
  45. package/src/environment/feature.ts +285 -0
  46. package/src/environment/geo.ts +337 -0
  47. package/src/environment/index.ts +7 -0
  48. package/src/environment/runtime.ts +400 -0
  49. package/src/environment/snapshot.ts +60 -0
  50. package/src/environment/variable.ts +239 -0
  51. package/src/event/README.md +90 -0
  52. package/src/event/class-event-proxy.ts +228 -0
  53. package/src/event/common.ts +19 -0
  54. package/src/event/event-manager.ts +203 -0
  55. package/src/event/index.ts +4 -0
  56. package/src/event/instance-event-proxy.ts +186 -0
  57. package/src/event/internal.ts +24 -0
  58. package/src/exception/README.md +96 -0
  59. package/src/exception/browser.ts +219 -0
  60. package/src/exception/index.ts +4 -0
  61. package/src/exception/nodejs.ts +169 -0
  62. package/src/exception/normalize.ts +106 -0
  63. package/src/exception/types.ts +99 -0
  64. package/src/identifier/README.md +92 -0
  65. package/src/identifier/id.ts +119 -0
  66. package/src/identifier/index.ts +2 -0
  67. package/src/identifier/uuid.ts +187 -0
  68. package/src/index.ts +18 -1
  69. package/src/log/README.md +79 -0
  70. package/src/log/index.ts +5 -0
  71. package/src/log/log-emitter.ts +72 -0
  72. package/src/log/log-record.ts +10 -0
  73. package/src/log/log-scheduler.ts +74 -0
  74. package/src/log/log-type.ts +8 -0
  75. package/src/log/logger.ts +543 -0
  76. package/src/orchestration/README.md +89 -0
  77. package/src/orchestration/coordination/barrier.ts +214 -0
  78. package/src/orchestration/coordination/count-down-latch.ts +215 -0
  79. package/src/orchestration/coordination/errors.ts +98 -0
  80. package/src/orchestration/coordination/index.ts +16 -0
  81. package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
  82. package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
  83. package/src/orchestration/coordination/keyed-lock.ts +168 -0
  84. package/src/orchestration/coordination/mutex.ts +257 -0
  85. package/src/orchestration/coordination/permit.ts +127 -0
  86. package/src/orchestration/coordination/read-write-lock.ts +444 -0
  87. package/src/orchestration/coordination/semaphore.ts +280 -0
  88. package/src/orchestration/index.ts +1 -0
  89. package/src/random/README.md +78 -0
  90. package/src/random/index.ts +1 -0
  91. package/src/random/string.ts +35 -0
  92. package/src/reactor/README.md +4 -0
  93. package/src/reactor/reactor-core/primitive.ts +9 -9
  94. package/src/reactor/reactor-core/reactive-system.ts +5 -5
  95. package/src/singleton/README.md +79 -0
  96. package/src/singleton/factory.ts +55 -0
  97. package/src/singleton/index.ts +2 -0
  98. package/src/singleton/manager.ts +204 -0
  99. package/src/storage/README.md +107 -0
  100. package/src/storage/index.ts +1 -0
  101. package/src/storage/table.ts +449 -0
  102. package/src/timer/README.md +86 -0
  103. package/src/timer/expiration/expiration-manager.ts +594 -0
  104. package/src/timer/expiration/index.ts +3 -0
  105. package/src/timer/expiration/min-heap.ts +208 -0
  106. package/src/timer/expiration/remaining-manager.ts +241 -0
  107. package/src/timer/index.ts +1 -0
  108. package/src/type/README.md +54 -307
  109. package/src/type/class.ts +2 -2
  110. package/src/type/index.ts +14 -14
  111. package/src/type/is.ts +265 -2
  112. package/src/type/object.ts +37 -0
  113. package/src/type/string.ts +7 -2
  114. package/src/type/tuple.ts +6 -6
  115. package/src/type/union.ts +16 -0
  116. package/src/web/README.md +77 -0
  117. package/src/web/capture.ts +35 -0
  118. package/src/web/clipboard.ts +97 -0
  119. package/src/web/dom.ts +117 -0
  120. package/src/web/download.ts +16 -0
  121. package/src/web/event.ts +46 -0
  122. package/src/web/index.ts +10 -0
  123. package/src/web/local-storage.ts +113 -0
  124. package/src/web/location.ts +28 -0
  125. package/src/web/permission.ts +172 -0
  126. package/src/web/script-loader.ts +432 -0
  127. package/tests/unit/abort/abort-manager.spec.ts +225 -0
  128. package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
  129. package/tests/unit/basic/array.spec.ts +1 -1
  130. package/tests/unit/basic/object.spec.ts +32 -1
  131. package/tests/unit/basic/stream.spec.ts +1 -1
  132. package/tests/unit/basic/string.spec.ts +0 -9
  133. package/tests/unit/color/rgb/analyze.spec.ts +110 -0
  134. package/tests/unit/color/rgb/construct.spec.ts +56 -0
  135. package/tests/unit/color/rgb/convert.spec.ts +60 -0
  136. package/tests/unit/color/rgb/derive.spec.ts +103 -0
  137. package/tests/unit/color/rgb/parse.spec.ts +66 -0
  138. package/tests/unit/color/rgb/serialize.spec.ts +46 -0
  139. package/tests/unit/color/xyz/analyze.spec.ts +33 -0
  140. package/tests/unit/color/xyz/construct.spec.ts +10 -0
  141. package/tests/unit/color/xyz/convert.spec.ts +18 -0
  142. package/tests/unit/css/class.spec.ts +157 -0
  143. package/tests/unit/encoding/base64.spec.ts +40 -0
  144. package/tests/unit/environment/basic.spec.ts +20 -0
  145. package/tests/unit/environment/device.spec.ts +146 -0
  146. package/tests/unit/environment/feature.spec.ts +388 -0
  147. package/tests/unit/environment/geo.spec.ts +111 -0
  148. package/tests/unit/environment/runtime.spec.ts +364 -0
  149. package/tests/unit/environment/snapshot.spec.ts +4 -0
  150. package/tests/unit/environment/variable.spec.ts +190 -0
  151. package/tests/unit/event/class-event-proxy.spec.ts +225 -0
  152. package/tests/unit/event/event-manager.spec.ts +246 -0
  153. package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
  154. package/tests/unit/exception/browser.spec.ts +213 -0
  155. package/tests/unit/exception/nodejs.spec.ts +144 -0
  156. package/tests/unit/exception/normalize.spec.ts +57 -0
  157. package/tests/unit/identifier/id.spec.ts +71 -0
  158. package/tests/unit/identifier/uuid.spec.ts +85 -0
  159. package/tests/unit/log/log-emitter.spec.ts +33 -0
  160. package/tests/unit/log/log-scheduler.spec.ts +40 -0
  161. package/tests/unit/log/log-type.spec.ts +7 -0
  162. package/tests/unit/log/logger.spec.ts +222 -0
  163. package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
  164. package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
  165. package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
  166. package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
  167. package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
  168. package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
  169. package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
  170. package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
  171. package/tests/unit/random/string.spec.ts +11 -0
  172. package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
  173. package/tests/unit/reactor/preact-signal.spec.ts +1 -2
  174. package/tests/unit/singleton/singleton.spec.ts +49 -0
  175. package/tests/unit/storage/table.spec.ts +620 -0
  176. package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
  177. package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
  178. package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
  179. package/.oxlintrc.json +0 -5
@@ -0,0 +1,285 @@
1
+ import { useFactory } from "./basic.ts"
2
+
3
+ import type { Use } from "./basic.ts"
4
+
5
+ import type { StringAutoCompletable } from "../type/string.ts"
6
+
7
+ /**
8
+ * Define built-in and custom feature keys for capability detection.
9
+ */
10
+ export type Feature =
11
+ | StringAutoCompletable
12
+ | "process"
13
+ | "navigator"
14
+ | "permissions"
15
+ | "document"
16
+ | "css"
17
+
18
+ interface FeatureRegistryItem {
19
+ feature: Feature
20
+ detect: () => boolean
21
+ }
22
+ const internalFeatureRegistry = new Map<Feature, FeatureRegistryItem>();
23
+ /**
24
+ * Check whether a feature is registered.
25
+ */
26
+ export const isFeatureRegistered = (feature: Feature): boolean => {
27
+ return internalFeatureRegistry.has(feature);
28
+ };
29
+ /**
30
+ * Register a feature detector.
31
+ */
32
+ export const registerFeature = (item: FeatureRegistryItem): void => {
33
+ internalFeatureRegistry.set(item.feature, item);
34
+ };
35
+ /**
36
+ * Unregister a feature detector.
37
+ */
38
+ export const unregisterFeature = (feature: Feature): void => {
39
+ internalFeatureRegistry.delete(feature);
40
+ };
41
+ /**
42
+ * List all registered feature keys.
43
+ */
44
+ export const listFeatures = (): Feature[] => {
45
+ return Array.from(internalFeatureRegistry.keys());
46
+ }
47
+
48
+ /**
49
+ * Get a feature registry item by feature key.
50
+ */
51
+ export const getFeatureRegistryItem = (feature: Feature): FeatureRegistryItem | undefined => {
52
+ return internalFeatureRegistry.get(feature);
53
+ }
54
+
55
+ /**
56
+ * Define detection results for registered features.
57
+ */
58
+ export type FeatureFlags = Record<Feature, boolean>;
59
+ /**
60
+ * Get the flags for all registered features.
61
+ */
62
+ export const getFeatureFlags = (): FeatureFlags => {
63
+ // oxlint-disable-next-line no-unsafe-type-assertion
64
+ const flags: FeatureFlags = {} as FeatureFlags;
65
+
66
+ for (const [name, item] of internalFeatureRegistry) {
67
+ flags[name] = item.detect();
68
+ }
69
+
70
+ return flags;
71
+ }
72
+
73
+ /**
74
+ * Check if the current environment satisfies the specified feature.
75
+ */
76
+ export const isSatisfiesFeature = (feature: Feature): boolean => {
77
+ const featureRegistryItem = getFeatureRegistryItem(feature);
78
+ if (featureRegistryItem === undefined) {
79
+ throw new Error(`Feature "${feature}" is not registered.`);
80
+ }
81
+ return featureRegistryItem.detect() === true;
82
+ }
83
+
84
+ /**
85
+ * Check if the current environment satisfies the specified feature conditions.
86
+ */
87
+ export const isSatisfiesFeatures = (condition: Partial<FeatureFlags>): boolean => {
88
+ let result = true;
89
+ Object.keys(condition).forEach((feature) => {
90
+ const featureRegistryItem = getFeatureRegistryItem(feature as Feature);
91
+ if (featureRegistryItem === undefined) {
92
+ throw new Error(`Feature "${feature}" is not registered.`);
93
+ }
94
+ if (featureRegistryItem.detect() !== condition[feature]) {
95
+ result = false;
96
+ }
97
+ });
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * Check if the `process` object is available in the environment.
103
+ */
104
+ export const supportProcess = (): boolean => {
105
+ return typeof process !== "undefined" && process !== null && typeof process === "object"
106
+ }
107
+ /**
108
+ * Get the `process` object from the current environment.
109
+ */
110
+ export const getProcess = (): NodeJS.Process => {
111
+ return globalThis.process
112
+ }
113
+ /**
114
+ * Get the `process` object if it is available, otherwise throw an error.
115
+ */
116
+ export const ensureProcess = (): NodeJS.Process => {
117
+ if (supportProcess() === true) {
118
+ return getProcess()
119
+ } else {
120
+ throw new Error("The process object is not supported in the current environment.")
121
+ }
122
+ }
123
+ /**
124
+ * Execute a function with the `process` object if it is available.
125
+ */
126
+ export const useProcess: Use<NodeJS.Process> = useFactory(getProcess, supportProcess)
127
+ registerFeature({
128
+ feature: "process",
129
+ detect: supportProcess,
130
+ })
131
+
132
+ /**
133
+ * Check if the `navigator` object is available in the environment.
134
+ */
135
+ export const supportNavigator = (): boolean => {
136
+ return typeof navigator !== "undefined" && navigator !== null && typeof navigator === "object"
137
+ }
138
+ /**
139
+ * Get the `navigator` object from the current environment.
140
+ */
141
+ export const getNavigator = (): Navigator => {
142
+ return globalThis.navigator
143
+ }
144
+ /**
145
+ * Get the `navigator` object if it is available, otherwise throw an error.
146
+ */
147
+ export const ensureNavigator = (): Navigator => {
148
+ if (supportNavigator() === true) {
149
+ return getNavigator()
150
+ } else {
151
+ throw new Error("The navigator object is not supported in the current environment.")
152
+ }
153
+ }
154
+ /**
155
+ * Execute a function with the `navigator` object if it is available.
156
+ */
157
+ export const useNavigator: Use<Navigator> = useFactory(getNavigator, supportNavigator)
158
+ registerFeature({
159
+ feature: "navigator",
160
+ detect: supportNavigator,
161
+ })
162
+
163
+ /**
164
+ * Check if the `clipboard` API is available in the environment.
165
+ */
166
+ export const supportClipboard = (): boolean => {
167
+ return supportNavigator() && "clipboard" in navigator
168
+ }
169
+ /**
170
+ * Get the `clipboard` API from the current environment.
171
+ */
172
+ export const getClipboard = (): Clipboard => {
173
+ return navigator.clipboard
174
+ }
175
+ /**
176
+ * Get the `clipboard` API if it is available, otherwise throw an error.
177
+ */
178
+ export const ensureClipboard = (): Clipboard => {
179
+ if (supportClipboard() === true) {
180
+ return getClipboard()
181
+ } else {
182
+ throw new Error("The clipboard API is not supported in the current environment.")
183
+ }
184
+ }
185
+ /**
186
+ * Execute a function with the `clipboard` API if it is available.
187
+ */
188
+ export const useClipboard: Use<Clipboard> = useFactory(getClipboard, supportClipboard)
189
+ registerFeature({
190
+ feature: "clipboard",
191
+ detect: supportClipboard,
192
+ })
193
+
194
+ /**
195
+ * Check if the `permissions` API is available in the environment.
196
+ */
197
+ export const supportPermissions = (): boolean => {
198
+ return supportNavigator() && "permissions" in navigator
199
+ }
200
+ /**
201
+ * Get the `permissions` API from the current environment.
202
+ */
203
+ export const getPermissions = (): Permissions => {
204
+ return navigator.permissions
205
+ }
206
+ /**
207
+ * Get the `permissions` API if it is available, otherwise throw an error.
208
+ */
209
+ export const ensurePermissions = (): Permissions => {
210
+ if (supportPermissions() === true) {
211
+ return getPermissions()
212
+ } else {
213
+ throw new Error("The permissions API is not supported in the current environment.")
214
+ }
215
+ }
216
+ /**
217
+ * Execute a function with the `permissions` API if it is available.
218
+ */
219
+ export const usePermissions: Use<Permissions> = useFactory(getPermissions, supportPermissions)
220
+ registerFeature({
221
+ feature: "permissions",
222
+ detect: supportPermissions,
223
+ })
224
+
225
+ /**
226
+ * Check if the `document` object is available in the environment.
227
+ */
228
+ export const supportDocument = (): boolean => {
229
+ return typeof document !== "undefined" && document !== null && typeof document === "object"
230
+ }
231
+ /**
232
+ * Get the `document` object from the current environment.
233
+ */
234
+ export const getDocument = (): Document => {
235
+ return globalThis.document
236
+ }
237
+ /**
238
+ * Get the `document` object if it is available, otherwise throw an error.
239
+ */
240
+ export const ensureDocument = (): Document => {
241
+ if (supportDocument() === true) {
242
+ return getDocument()
243
+ } else {
244
+ throw new Error("The document object is not supported in the current environment.")
245
+ }
246
+ }
247
+ /**
248
+ * Execute a function with the `document` object if it is available.
249
+ */
250
+ export const useDocument: Use<Document> = useFactory(getDocument, supportDocument)
251
+ registerFeature({
252
+ feature: "document",
253
+ detect: supportDocument,
254
+ })
255
+
256
+ /**
257
+ * Check if the `CSS` object is available in the environment.
258
+ */
259
+ export const supportCSS = (): boolean => {
260
+ return typeof CSS !== "undefined" && CSS !== null && typeof CSS === "object"
261
+ }
262
+ /**
263
+ * Get the `CSS` object from the current environment.
264
+ */
265
+ export const getCSS = (): typeof CSS => {
266
+ return globalThis.CSS
267
+ }
268
+ /**
269
+ * Get the `CSS` object if it is available, otherwise throw an error.
270
+ */
271
+ export const ensureCSS = (): typeof CSS => {
272
+ if (supportCSS() === true) {
273
+ return getCSS()
274
+ } else {
275
+ throw new Error("The CSS object is not supported in the current environment.")
276
+ }
277
+ }
278
+ /**
279
+ * Execute a function with the `CSS` object if it is available.
280
+ */
281
+ export const useCSS: Use<typeof CSS> = useFactory(getCSS, supportCSS)
282
+ registerFeature({
283
+ feature: "css",
284
+ detect: supportCSS,
285
+ })
@@ -0,0 +1,337 @@
1
+ /**
2
+ * 描述来自 Cloudflare trace 接口的地理信息。
3
+ *
4
+ * @example
5
+ * ```
6
+ * fl=1232f78
7
+ * h=www.cloudflare.com
8
+ * ip=23.132.124.129
9
+ * ts=1772261014.000
10
+ * visit_scheme=https
11
+ * uag=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0
12
+ * colo=SJC
13
+ * sliver=none
14
+ * http=http/2
15
+ * loc=US
16
+ * tls=TLSv1.3
17
+ * sni=plaintext
18
+ * warp=off
19
+ * gateway=off
20
+ * rbi=off
21
+ * kex=X25519MLKEM768
22
+ * ```
23
+ */
24
+ export interface GeoInfoFromCloudflare {
25
+ /**
26
+ * Data center request was handled at (Cloudflare internal code)
27
+ */
28
+ fl?: string | undefined
29
+ /**
30
+ * Hostname of the incoming HTTP request
31
+ */
32
+ h?: string | undefined
33
+ /**
34
+ * Client IP address detected by Cloudflare
35
+ */
36
+ ip?: string | undefined
37
+ /**
38
+ * Timestamp of request (in seconds)
39
+ */
40
+ ts?: string | undefined
41
+ /**
42
+ * Request scheme used by the visitor (for example: http/https)
43
+ */
44
+ visit_scheme?: string | undefined
45
+ /**
46
+ * User-Agent header value
47
+ */
48
+ uag?: string | undefined
49
+ /**
50
+ * IATA-like code of the Cloudflare edge location
51
+ */
52
+ colo?: string | undefined
53
+ /**
54
+ * Cloudflare edge “sliver” identifier
55
+ */
56
+ sliver?: string | undefined
57
+ /**
58
+ * HTTP protocol version used for the request
59
+ */
60
+ http?: string | undefined
61
+ /**
62
+ * Country code inferred from client IP (ISO 3166-1 alpha-2)
63
+ */
64
+ loc?: string | undefined
65
+ /**
66
+ * TLS protocol version negotiated for the connection
67
+ */
68
+ tls?: string | undefined
69
+ /**
70
+ * Server Name Indication (SNI) mode
71
+ */
72
+ sni?: string | undefined
73
+ /**
74
+ * Whether WARP is enabled for the client
75
+ */
76
+ warp?: string | undefined
77
+ /**
78
+ * Whether Cloudflare Gateway is enabled for the client
79
+ */
80
+ gateway?: string | undefined
81
+ /**
82
+ * Whether browser isolation (RBI) is enabled
83
+ */
84
+ rbi?: string | undefined
85
+ /**
86
+ * Key exchange algorithm used by TLS
87
+ */
88
+ kex?: string | undefined
89
+ }
90
+ /**
91
+ * Parse geolocation information from Cloudflare trace response.
92
+ */
93
+ const parseGeoInfoFromCloudflare = (text: string): GeoInfoFromCloudflare => {
94
+ const pairs = text
95
+ .split('\n')
96
+ .map((pair: string) => pair.split('='))
97
+ .filter(([key]) => key !== '')
98
+ const result = pairs.reduce((geo: GeoInfoFromCloudflare, pair) => {
99
+ const [key, value] = pair
100
+ if (key !== undefined) {
101
+ // oxlint-disable-next-line no-unsafe-type-assertion
102
+ geo[key as keyof GeoInfoFromCloudflare] = value
103
+ }
104
+ return geo
105
+ }, {})
106
+ return result
107
+ }
108
+ /**
109
+ * Get device geolocation information from Cloudflare
110
+ *
111
+ * @see {@link https://dev.to/brojenuel/get-ip-clients-addresses-6k6}
112
+ * @see {@link https://dataflowkit.com/blog/determine-location-of-users/}
113
+ */
114
+ export const getGeoInfoFromCloudflare = async (): Promise<GeoInfoFromCloudflare> => {
115
+ const response = await fetch('https://www.cloudflare.com/cdn-cgi/trace', {
116
+ method: 'GET',
117
+ mode: 'cors',
118
+ credentials: 'omit'
119
+ })
120
+ const responseText = await response.text()
121
+ const geo = parseGeoInfoFromCloudflare(responseText)
122
+
123
+ return geo
124
+ }
125
+
126
+ /**
127
+ * 描述来自 ipapi 响应的地理信息。
128
+ *
129
+ * @example
130
+ * ```
131
+ * {
132
+ * "ip": "23.132.124.129",
133
+ * "network": "23.132.124.0/24",
134
+ * "version": "IPv4",
135
+ * "city": "Los Angeles",
136
+ * "region": "California",
137
+ * "region_code": "CA",
138
+ * "country": "US",
139
+ * "country_name": "United States",
140
+ * "country_code": "US",
141
+ * "country_code_iso3": "USA",
142
+ * "country_capital": "Washington",
143
+ * "country_tld": ".us",
144
+ * "continent_code": "NA",
145
+ * "in_eu": false,
146
+ * "postal": "90001",
147
+ * "latitude": 34.05223,
148
+ * "longitude": -118.24368,
149
+ * "timezone": "America/Los_Angeles",
150
+ * "utc_offset": "-0800",
151
+ * "country_calling_code": "+1",
152
+ * "currency": "USD",
153
+ * "currency_name": "Dollar",
154
+ * "languages": "en-US,es-US,haw,fr",
155
+ * "country_area": 9629091,
156
+ * "country_population": 327167434,
157
+ * "asn": "AS7018",
158
+ * "org": "AT&T Enterprises, LLC"
159
+ * }
160
+ * ```
161
+ */
162
+ export interface GeoInfoFromIpapi {
163
+ /**
164
+ * Public (external) IP address (same as URL ip).
165
+ */
166
+ ip?: string | undefined
167
+ /**
168
+ * Network address in CIDR notation.
169
+ */
170
+ network?: string | undefined
171
+ /**
172
+ * IP version (IPv4 or IPv6).
173
+ */
174
+ version?: string | undefined
175
+ /**
176
+ * City name.
177
+ */
178
+ city?: string | undefined
179
+ /**
180
+ * Region name (administrative division).
181
+ */
182
+ region?: string | undefined
183
+ /**
184
+ * Region code.
185
+ */
186
+ region_code?: string | undefined
187
+ /**
188
+ * Country code (2 letter, ISO 3166-1 alpha-2).
189
+ */
190
+ country?: string | undefined
191
+ /**
192
+ * Short country name.
193
+ */
194
+ country_name?: string | undefined
195
+ /**
196
+ * Country code (2 letter, ISO 3166-1 alpha-2).
197
+ */
198
+ country_code?: string | undefined
199
+ /**
200
+ * Country code (3 letter, ISO 3166-1 alpha-3).
201
+ */
202
+ country_code_iso3?: string | undefined
203
+ /**
204
+ * Capital of the country.
205
+ */
206
+ country_capital?: string | undefined
207
+ /**
208
+ * Country specific TLD (top-level domain).
209
+ */
210
+ country_tld?: string | undefined
211
+ /**
212
+ * Continent code.
213
+ */
214
+ continent_code?: string | undefined
215
+ /**
216
+ * Whether IP address belongs to a country that is a member of the European Union (EU).
217
+ */
218
+ in_eu?: boolean | undefined
219
+ /**
220
+ * Postal code / zip code.
221
+ */
222
+ postal?: string | undefined
223
+ /**
224
+ * Latitude coordinate.
225
+ */
226
+ latitude?: number | undefined
227
+ /**
228
+ * Longitude coordinate.
229
+ */
230
+ longitude?: number | undefined
231
+ /**
232
+ * Timezone (IANA format i.e. “Area/Location”).
233
+ */
234
+ timezone?: string | undefined
235
+ /**
236
+ * UTC offset (with daylight saving time) as `+HHMM` or `-HHMM` (HH is hours, MM is minutes).
237
+ */
238
+ utc_offset?: string | undefined
239
+ /**
240
+ * Country calling code (dial in code, comma separated).
241
+ */
242
+ country_calling_code?: string | undefined
243
+ /**
244
+ * Currency code (ISO 4217 format).
245
+ */
246
+ currency?: string | undefined
247
+ /**
248
+ * Currency name.
249
+ */
250
+ currency_name?: string | undefined
251
+ /**
252
+ * Languages spoken (comma separated 2 or 3 letter ISO 639 code with optional hyphen separated country suffix).
253
+ */
254
+ languages?: string | undefined
255
+ /**
256
+ * Area of the country (in sq km).
257
+ */
258
+ country_area?: number | undefined
259
+ /**
260
+ * Population of the country.
261
+ */
262
+ country_population?: number | undefined
263
+ /**
264
+ * Autonomous System Number.
265
+ */
266
+ asn?: string | undefined
267
+ /**
268
+ * Organization name.
269
+ */
270
+ org?: string | undefined
271
+ /**
272
+ * Host or domain name associated with the IP (*optional beta add-on, please contact us for details).
273
+ */
274
+ hostname?: string | undefined
275
+ }
276
+ /**
277
+ * Get geographical information from IP address using ipapi.co API.
278
+ *
279
+ * @see {@link https://ipapi.co/}
280
+ */
281
+ export const getGeoInfoFromIpapi = async (): Promise<GeoInfoFromIpapi> => {
282
+ const response = await fetch(`https://ipapi.co/json/`, {
283
+ method: 'GET',
284
+ mode: 'cors',
285
+ credentials: 'omit'
286
+ })
287
+ // oxlint-disable-next-line no-unsafe-type-assertion
288
+ const geo = await response.json() as GeoInfoFromIpapi
289
+ return geo
290
+ }
291
+
292
+ /**
293
+ * 描述来自 ipify API 的公网 IP 信息。
294
+ *
295
+ * @example
296
+ * ```
297
+ * {
298
+ * "ip": "23.132.124.129"
299
+ * }
300
+ * ```
301
+ */
302
+ export interface GeoInfoFromIpify {
303
+ ip: string
304
+ }
305
+ /**
306
+ * Get public IP address using ipify API.
307
+ *
308
+ * @see {@link https://www.ipify.org/}
309
+ */
310
+ export const getGeoInfoFromIpify = async (): Promise<GeoInfoFromIpify> => {
311
+ const response = await fetch("https://api.ipify.org?format=json", {
312
+ method: 'GET',
313
+ })
314
+ // oxlint-disable-next-line no-unsafe-type-assertion
315
+ const geo = await response.json() as GeoInfoFromIpify
316
+ return geo
317
+ }
318
+
319
+ /**
320
+ * Describe aggregated geolocation data from all providers.
321
+ */
322
+ export interface GeoInfo {
323
+ cloudflare: GeoInfoFromCloudflare
324
+ ipapi: GeoInfoFromIpapi
325
+ ipify: GeoInfoFromIpify
326
+ }
327
+ /**
328
+ * Get geographical information from multiple sources.
329
+ */
330
+ export const getGeoInfo = async (): Promise<GeoInfo> => {
331
+ const [cloudflare, ipapi, ipify] = await Promise.all([
332
+ getGeoInfoFromCloudflare(),
333
+ getGeoInfoFromIpapi(),
334
+ getGeoInfoFromIpify()
335
+ ])
336
+ return { cloudflare, ipapi, ipify }
337
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./basic.ts"
2
+ export * from "./geo.ts"
3
+ export * from "./device.ts"
4
+ export * from "./runtime.ts"
5
+ export * from "./feature.ts"
6
+ export * from "./variable.ts"
7
+ export * from "./snapshot.ts"