@cocreate/utils 1.44.2 → 1.44.3
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.
- package/package.json +1 -1
- package/src/ObjectId.js +59 -37
- package/src/ast/index.js +404 -0
- package/src/{operators.js → ast/operators.js} +247 -250
- package/src/cloudProviders.js +150 -0
- package/src/createUpdate.js +1 -29
- package/src/dataQuery.js +2 -2
- package/src/getClientInfo.js +357 -0
- package/src/getClockOffset.js +46 -0
- package/src/getCountryLocation.js +278 -0
- package/src/index.js +47 -35
- package/src/ulid.js +72 -0
- package/src/uuid.js +67 -0
- package/src/core.js +0 -6
- package/src/operators copy.js +0 -687
- package/src/operators.ast.js +0 -287
- package/src/operators.v1.js +0 -687
- package/src/safeParse.js +0 -171
- package/src/uid.js +0 -16
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive Cloud Provider Registry (ESM)
|
|
3
|
+
* Maps infrastructure probes (AWS, GCP, Azure, DigitalOcean, OCI, Hetzner, Vultr, Equinix)
|
|
4
|
+
* to exact geographical coordinates (lat/lng), country, and region IDs.
|
|
5
|
+
*/
|
|
6
|
+
export const cloudProviders = [
|
|
7
|
+
// AWS Regions
|
|
8
|
+
{ provider: 'aws', region: 'us-east-1', name: 'N. Virginia', country: 'US', lat: 38.9489, lng: -77.4499 },
|
|
9
|
+
{ provider: 'aws', region: 'us-east-2', name: 'Ohio', country: 'US', lat: 40.1089, lng: -82.9545 },
|
|
10
|
+
{ provider: 'aws', region: 'us-west-1', name: 'N. California', country: 'US', lat: 37.3541, lng: -121.9552 },
|
|
11
|
+
{ provider: 'aws', region: 'us-west-2', name: 'Oregon', country: 'US', lat: 45.8399, lng: -119.7006 },
|
|
12
|
+
{ provider: 'aws', region: 'ca-central-1', name: 'Montreal', country: 'CA', lat: 45.5017, lng: -73.5673 },
|
|
13
|
+
{ provider: 'aws', region: 'eu-central-1', name: 'Frankfurt', country: 'DE', lat: 50.1109, lng: 8.6821 },
|
|
14
|
+
{ provider: 'aws', region: 'eu-west-1', name: 'Ireland', country: 'IE', lat: 53.3498, lng: -6.2603 },
|
|
15
|
+
{ provider: 'aws', region: 'eu-west-2', name: 'London', country: 'GB', lat: 51.5074, lng: -0.1278 },
|
|
16
|
+
{ provider: 'aws', region: 'eu-west-3', name: 'Paris', country: 'FR', lat: 48.8566, lng: 2.3522 },
|
|
17
|
+
{ provider: 'aws', region: 'eu-north-1', name: 'Stockholm', country: 'SE', lat: 59.3293, lng: 18.0686 },
|
|
18
|
+
{ provider: 'aws', region: 'eu-south-1', name: 'Milan', country: 'IT', lat: 45.4642, lng: 9.1900 },
|
|
19
|
+
{ provider: 'aws', region: 'ap-northeast-1', name: 'Tokyo', country: 'JP', lat: 35.6762, lng: 139.6503 },
|
|
20
|
+
{ provider: 'aws', region: 'ap-northeast-2', name: 'Seoul', country: 'KR', lat: 37.5665, lng: 126.9780 },
|
|
21
|
+
{ provider: 'aws', region: 'ap-northeast-3', name: 'Osaka', country: 'JP', lat: 34.6937, lng: 135.5023 },
|
|
22
|
+
{ provider: 'aws', region: 'ap-southeast-1', name: 'Singapore', country: 'SG', lat: 1.3521, lng: 103.8198 },
|
|
23
|
+
{ provider: 'aws', region: 'ap-southeast-2', name: 'Sydney', country: 'AU', lat: -33.8688, lng: 151.2093 },
|
|
24
|
+
{ provider: 'aws', region: 'ap-south-1', name: 'Mumbai', country: 'IN', lat: 19.0760, lng: 72.8777 },
|
|
25
|
+
{ provider: 'aws', region: 'sa-east-1', name: 'São Paulo', country: 'BR', lat: -23.5505, lng: -46.6333 },
|
|
26
|
+
{ provider: 'aws', region: 'af-south-1', name: 'Cape Town', country: 'ZA', lat: -33.9249, lng: 18.4241 },
|
|
27
|
+
{ provider: 'aws', region: 'me-south-1', name: 'Bahrain', country: 'BH', lat: 26.0667, lng: 50.5577 },
|
|
28
|
+
|
|
29
|
+
// GCP Regions
|
|
30
|
+
{ provider: 'gcp', region: 'us-central1', name: 'Iowa', country: 'US', lat: 41.2619, lng: -95.8608 },
|
|
31
|
+
{ provider: 'gcp', region: 'us-east1', name: 'South Carolina', country: 'US', lat: 33.1960, lng: -80.0131 },
|
|
32
|
+
{ provider: 'gcp', region: 'us-east4', name: 'N. Virginia', country: 'US', lat: 38.9489, lng: -77.4499 },
|
|
33
|
+
{ provider: 'gcp', region: 'us-west1', name: 'Oregon', country: 'US', lat: 45.6018, lng: -121.1842 },
|
|
34
|
+
{ provider: 'gcp', region: 'us-west2', name: 'Los Angeles', country: 'US', lat: 34.0522, lng: -118.2437 },
|
|
35
|
+
{ provider: 'gcp', region: 'northamerica-northeast1', name: 'Montreal', country: 'CA', lat: 45.5017, lng: -73.5673 },
|
|
36
|
+
{ provider: 'gcp', region: 'southamerica-east1', name: 'São Paulo', country: 'BR', lat: -23.5505, lng: -46.6333 },
|
|
37
|
+
{ provider: 'gcp', region: 'europe-west1', name: 'Belgium', country: 'BE', lat: 50.4541, lng: 3.8258 },
|
|
38
|
+
{ provider: 'gcp', region: 'europe-west2', name: 'London', country: 'GB', lat: 51.5074, lng: -0.1278 },
|
|
39
|
+
{ provider: 'gcp', region: 'europe-west3', name: 'Frankfurt', country: 'DE', lat: 50.1109, lng: 8.6821 },
|
|
40
|
+
{ provider: 'gcp', region: 'europe-west4', name: 'Eemshaven', country: 'NL', lat: 53.4373, lng: 6.8335 },
|
|
41
|
+
{ provider: 'gcp', region: 'asia-east1', name: 'Taiwan', country: 'TW', lat: 24.0518, lng: 120.5161 },
|
|
42
|
+
{ provider: 'gcp', region: 'asia-east2', name: 'Hong Kong', country: 'HK', lat: 22.3193, lng: 114.1694 },
|
|
43
|
+
{ provider: 'gcp', region: 'asia-northeast1', name: 'Tokyo', country: 'JP', lat: 35.6762, lng: 139.6503 },
|
|
44
|
+
{ provider: 'gcp', region: 'asia-northeast2', name: 'Osaka', country: 'JP', lat: 34.6937, lng: 135.5023 },
|
|
45
|
+
{ provider: 'gcp', region: 'asia-southeast1', name: 'Singapore', country: 'SG', lat: 1.3521, lng: 103.8198 },
|
|
46
|
+
{ provider: 'gcp', region: 'asia-south1', name: 'Mumbai', country: 'IN', lat: 19.0760, lng: 72.8777 },
|
|
47
|
+
{ provider: 'gcp', region: 'australia-southeast1', name: 'Sydney', country: 'AU', lat: -33.8688, lng: 151.2093 },
|
|
48
|
+
|
|
49
|
+
// Azure Regions
|
|
50
|
+
{ provider: 'azure', region: 'eastus', name: 'Virginia', country: 'US', lat: 37.3719, lng: -79.8164 },
|
|
51
|
+
{ provider: 'azure', region: 'eastus2', name: 'Virginia 2', country: 'US', lat: 36.6681, lng: -78.3881 },
|
|
52
|
+
{ provider: 'azure', region: 'westus', name: 'California', country: 'US', lat: 37.7833, lng: -122.4167 },
|
|
53
|
+
{ provider: 'azure', region: 'westus2', name: 'Washington', country: 'US', lat: 47.2330, lng: -119.8520 },
|
|
54
|
+
{ provider: 'azure', region: 'centralus', name: 'Iowa', country: 'US', lat: 41.5908, lng: -93.6208 },
|
|
55
|
+
{ provider: 'azure', region: 'canadacentral', name: 'Toronto', country: 'CA', lat: 43.6532, lng: -79.3832 },
|
|
56
|
+
{ provider: 'azure', region: 'brazilsouth', name: 'São Paulo', country: 'BR', lat: -23.5505, lng: -46.6333 },
|
|
57
|
+
{ provider: 'azure', region: 'northeurope', name: 'Ireland', country: 'IE', lat: 53.3498, lng: -6.2603 },
|
|
58
|
+
{ provider: 'azure', region: 'westeurope', name: 'Netherlands', country: 'NL', lat: 52.3676, lng: 4.9041 },
|
|
59
|
+
{ provider: 'azure', region: 'ukwest', name: 'Cardiff', country: 'GB', lat: 51.4815, lng: -3.1791 },
|
|
60
|
+
{ provider: 'azure', region: 'ukeast', name: 'London', country: 'GB', lat: 51.5074, lng: -0.1278 },
|
|
61
|
+
{ provider: 'azure', region: 'francecentral', name: 'Paris', country: 'FR', lat: 48.8566, lng: 2.3522 },
|
|
62
|
+
{ provider: 'azure', region: 'germanywestcentral', name: 'Frankfurt', country: 'DE', lat: 50.1109, lng: 8.6821 },
|
|
63
|
+
{ provider: 'azure', region: 'japaneast', name: 'Tokyo', country: 'JP', lat: 35.6762, lng: 139.6503 },
|
|
64
|
+
{ provider: 'azure', region: 'japanwest', name: 'Osaka', country: 'JP', lat: 34.6937, lng: 135.5023 },
|
|
65
|
+
{ provider: 'azure', region: 'southeastasia', name: 'Singapore', country: 'SG', lat: 1.3521, lng: 103.8198 },
|
|
66
|
+
{ provider: 'azure', region: 'australiaeast', name: 'New South Wales', country: 'AU', lat: -33.8688, lng: 151.2093 },
|
|
67
|
+
{ provider: 'azure', region: 'centralindia', name: 'Pune', country: 'IN', lat: 18.5204, lng: 73.8567 },
|
|
68
|
+
|
|
69
|
+
// DigitalOcean Regions
|
|
70
|
+
{ provider: 'digitalocean', region: 'nyc1', name: 'New York 1', country: 'US', lat: 40.7128, lng: -74.0060 },
|
|
71
|
+
{ provider: 'digitalocean', region: 'nyc3', name: 'New York 3', country: 'US', lat: 40.7128, lng: -74.0060 },
|
|
72
|
+
{ provider: 'digitalocean', region: 'sfo3', name: 'San Francisco', country: 'US', lat: 37.7749, lng: -122.4194 },
|
|
73
|
+
{ provider: 'digitalocean', region: 'ams3', name: 'Amsterdam', country: 'NL', lat: 52.3676, lng: 4.9041 },
|
|
74
|
+
{ provider: 'digitalocean', region: 'sgp1', name: 'Singapore', country: 'SG', lat: 1.3521, lng: 103.8198 },
|
|
75
|
+
{ provider: 'digitalocean', region: 'lon1', name: 'London', country: 'GB', lat: 51.5074, lng: -0.1278 },
|
|
76
|
+
{ provider: 'digitalocean', region: 'fra1', name: 'Frankfurt', country: 'DE', lat: 50.1109, lng: 8.6821 },
|
|
77
|
+
{ provider: 'digitalocean', region: 'tor1', name: 'Toronto', country: 'CA', lat: 43.6532, lng: -79.3832 },
|
|
78
|
+
{ provider: 'digitalocean', region: 'blr1', name: 'Bangalore', country: 'IN', lat: 12.9716, lng: 77.5946 },
|
|
79
|
+
{ provider: 'digitalocean', region: 'syd1', name: 'Sydney', country: 'AU', lat: -33.8688, lng: 151.2093 },
|
|
80
|
+
|
|
81
|
+
// OCI Regions
|
|
82
|
+
{ provider: 'oci', region: 'us-ashburn-1', name: 'Ashburn', country: 'US', lat: 39.0438, lng: -77.4874 },
|
|
83
|
+
{ provider: 'oci', region: 'us-phoenix-1', name: 'Phoenix', country: 'US', lat: 33.4484, lng: -112.0740 },
|
|
84
|
+
{ provider: 'oci', region: 'us-sanjose-1', name: 'San Jose', country: 'US', lat: 37.3382, lng: -121.8863 },
|
|
85
|
+
{ provider: 'oci', region: 'ca-toronto-1', name: 'Toronto', country: 'CA', lat: 43.6532, lng: -79.3832 },
|
|
86
|
+
{ provider: 'oci', region: 'sa-saopaulo-1', name: 'São Paulo', country: 'BR', lat: -23.5505, lng: -46.6333 },
|
|
87
|
+
{ provider: 'oci', region: 'eu-frankfurt-1', name: 'Frankfurt', country: 'DE', lat: 50.1109, lng: 8.6821 },
|
|
88
|
+
{ provider: 'oci', region: 'eu-zurich-1', name: 'Zurich', country: 'CH', lat: 47.3769, lng: 8.5417 },
|
|
89
|
+
{ provider: 'oci', region: 'eu-amsterdam-1', name: 'Amsterdam', country: 'NL', lat: 52.3676, lng: 4.9041 },
|
|
90
|
+
{ provider: 'oci', region: 'uk-london-1', name: 'London', country: 'GB', lat: 51.5074, lng: -0.1278 },
|
|
91
|
+
{ provider: 'oci', region: 'ap-tokyo-1', name: 'Tokyo', country: 'JP', lat: 35.6762, lng: 139.6503 },
|
|
92
|
+
{ provider: 'oci', region: 'ap-osaka-1', name: 'Osaka', country: 'JP', lat: 34.6937, lng: 135.5023 },
|
|
93
|
+
{ provider: 'oci', region: 'ap-seoul-1', name: 'Seoul', country: 'KR', lat: 37.5665, lng: 126.9780 },
|
|
94
|
+
{ provider: 'oci', region: 'ap-sydney-1', name: 'Sydney', country: 'AU', lat: -33.8688, lng: 151.2093 },
|
|
95
|
+
{ provider: 'oci', region: 'ap-mumbai-1', name: 'Mumbai', country: 'IN', lat: 19.0760, lng: 72.8777 },
|
|
96
|
+
{ provider: 'oci', region: 'me-jeddah-1', name: 'Jeddah', country: 'SA', lat: 21.5433, lng: 39.1728 },
|
|
97
|
+
|
|
98
|
+
// Hetzner Regions
|
|
99
|
+
{ provider: 'hetzner', region: 'fsn1', name: 'Falkenstein', country: 'DE', lat: 50.4779, lng: 12.3713 },
|
|
100
|
+
{ provider: 'hetzner', region: 'nbg1', name: 'Nuremberg', country: 'DE', lat: 49.4521, lng: 11.0767 },
|
|
101
|
+
{ provider: 'hetzner', region: 'hel1', name: 'Helsinki', country: 'FI', lat: 60.1699, lng: 24.9384 },
|
|
102
|
+
{ provider: 'hetzner', region: 'ash', name: 'Ashburn', country: 'US', lat: 39.0438, lng: -77.4874 },
|
|
103
|
+
{ provider: 'hetzner', region: 'hil', name: 'Hillsboro', country: 'US', lat: 45.5229, lng: -122.9898 },
|
|
104
|
+
{ provider: 'hetzner', region: 'sin', name: 'Singapore', country: 'SG', lat: 1.3521, lng: 103.8198 },
|
|
105
|
+
|
|
106
|
+
// Vultr Regions
|
|
107
|
+
{ provider: 'vultr', region: 'ewr', name: 'New Jersey', country: 'US', lat: 40.7128, lng: -74.0060 },
|
|
108
|
+
{ provider: 'vultr', region: 'ord', name: 'Chicago', country: 'US', lat: 41.8781, lng: -87.6298 },
|
|
109
|
+
{ provider: 'vultr', region: 'dfw', name: 'Dallas', country: 'US', lat: 32.7767, lng: -96.7970 },
|
|
110
|
+
{ provider: 'vultr', region: 'sea', name: 'Seattle', country: 'US', lat: 47.6062, lng: -122.3321 },
|
|
111
|
+
{ provider: 'vultr', region: 'lax', name: 'Los Angeles', country: 'US', lat: 34.0522, lng: -118.2437 },
|
|
112
|
+
{ provider: 'vultr', region: 'atl', name: 'Atlanta', country: 'US', lat: 33.7490, lng: -84.3880 },
|
|
113
|
+
{ provider: 'vultr', region: 'mia', name: 'Miami', country: 'US', lat: 25.7617, lng: -80.1918 },
|
|
114
|
+
{ provider: 'vultr', region: 'sjc', name: 'Silicon Valley', country: 'US', lat: 37.3382, lng: -121.8863 },
|
|
115
|
+
{ provider: 'vultr', region: 'yto', name: 'Toronto', country: 'CA', lat: 43.6532, lng: -79.3832 },
|
|
116
|
+
{ provider: 'vultr', region: 'sao', name: 'São Paulo', country: 'BR', lat: -23.5505, lng: -46.6333 },
|
|
117
|
+
{ provider: 'vultr', region: 'ams', name: 'Amsterdam', country: 'NL', lat: 52.3676, lng: 4.9041 },
|
|
118
|
+
{ provider: 'vultr', region: 'fra', name: 'Frankfurt', country: 'DE', lat: 50.1109, lng: 8.6821 },
|
|
119
|
+
{ provider: 'vultr', region: 'lhr', name: 'London', country: 'GB', lat: 51.5074, lng: -0.1278 },
|
|
120
|
+
{ provider: 'vultr', region: 'par', name: 'Paris', country: 'FR', lat: 48.8566, lng: 2.3522 },
|
|
121
|
+
{ provider: 'vultr', region: 'sgp', name: 'Singapore', country: 'SG', lat: 1.3521, lng: 103.8198 },
|
|
122
|
+
{ provider: 'vultr', region: 'nrt', name: 'Tokyo', country: 'JP', lat: 35.6762, lng: 139.6503 },
|
|
123
|
+
{ provider: 'vultr', region: 'icn', name: 'Seoul', country: 'KR', lat: 37.5665, lng: 126.9780 },
|
|
124
|
+
{ provider: 'vultr', region: 'syd', name: 'Sydney', country: 'AU', lat: -33.8688, lng: 151.2093 },
|
|
125
|
+
{ provider: 'vultr', region: 'mel', name: 'Melbourne', country: 'AU', lat: -37.8136, lng: 144.9631 },
|
|
126
|
+
{ provider: 'vultr', region: 'blr', name: 'Bangalore', country: 'IN', lat: 12.9716, lng: 77.5946 },
|
|
127
|
+
{ provider: 'vultr', region: 'jnb', name: 'Johannesburg', country: 'ZA', lat: -26.2041, lng: 28.0473 },
|
|
128
|
+
|
|
129
|
+
// Equinix Regions
|
|
130
|
+
{ provider: 'equinix', region: 'da', name: 'Dallas', country: 'US', lat: 32.7767, lng: -96.7970 },
|
|
131
|
+
{ provider: 'equinix', region: 'ny', name: 'New York', country: 'US', lat: 40.7128, lng: -74.0060 },
|
|
132
|
+
{ provider: 'equinix', region: 'sv', name: 'Silicon Valley', country: 'US', lat: 37.3382, lng: -121.8863 },
|
|
133
|
+
{ provider: 'equinix', region: 'ch', name: 'Chicago', country: 'US', lat: 41.8781, lng: -87.6298 },
|
|
134
|
+
{ provider: 'equinix', region: 'la', name: 'Los Angeles', country: 'US', lat: 34.0522, lng: -118.2437 },
|
|
135
|
+
{ provider: 'equinix', region: 'se', name: 'Seattle', country: 'US', lat: 47.6062, lng: -122.3321 },
|
|
136
|
+
{ provider: 'equinix', region: 'at', name: 'Atlanta', country: 'US', lat: 33.7490, lng: -84.3880 },
|
|
137
|
+
{ provider: 'equinix', region: 'tr', name: 'Toronto', country: 'CA', lat: 43.6532, lng: -79.3832 },
|
|
138
|
+
{ provider: 'equinix', region: 'sp', name: 'São Paulo', country: 'BR', lat: -23.5505, lng: -46.6333 },
|
|
139
|
+
{ provider: 'equinix', region: 'fr', name: 'Frankfurt', country: 'DE', lat: 50.1109, lng: 8.6821 },
|
|
140
|
+
{ provider: 'equinix', region: 'am', name: 'Amsterdam', country: 'NL', lat: 52.3676, lng: 4.9041 },
|
|
141
|
+
{ provider: 'equinix', region: 'ld', name: 'London', country: 'GB', lat: 51.5074, lng: -0.1278 },
|
|
142
|
+
{ provider: 'equinix', region: 'pa', name: 'Paris', country: 'FR', lat: 48.8566, lng: 2.3522 },
|
|
143
|
+
{ provider: 'equinix', region: 'ma', name: 'Madrid', country: 'ES', lat: 40.4168, lng: -3.7038 },
|
|
144
|
+
{ provider: 'equinix', region: 'ty', name: 'Tokyo', country: 'JP', lat: 35.6762, lng: 139.6503 },
|
|
145
|
+
{ provider: 'equinix', region: 'sg', name: 'Singapore', country: 'SG', lat: 1.3521, lng: 103.8198 },
|
|
146
|
+
{ provider: 'equinix', region: 'hk', name: 'Hong Kong', country: 'HK', lat: 22.3193, lng: 114.1694 },
|
|
147
|
+
{ provider: 'equinix', region: 'sy', name: 'Sydney', country: 'AU', lat: -33.8688, lng: 151.2093 }
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
export default cloudProviders
|
package/src/createUpdate.js
CHANGED
|
@@ -1,35 +1,7 @@
|
|
|
1
1
|
import { dotNotationToObject } from "./dotNotationToObject.js";
|
|
2
2
|
import { objectToDotNotation } from "./objectToDotNotation.js";
|
|
3
3
|
import { getValueFromObject } from "./getValueFromObject.js";
|
|
4
|
-
|
|
5
|
-
function isEqualArray(arr1, arr2) {
|
|
6
|
-
if (arr1.length !== arr2.length) {
|
|
7
|
-
return false;
|
|
8
|
-
}
|
|
9
|
-
for (let i = 0; i < arr1.length; i++) {
|
|
10
|
-
if (!isEqualObject(arr1[i], arr2[i])) {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function isEqualObject(obj1, obj2) {
|
|
18
|
-
const keys1 = Object.keys(obj1);
|
|
19
|
-
const keys2 = Object.keys(obj2);
|
|
20
|
-
|
|
21
|
-
if (keys1.length !== keys2.length) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
for (const key of keys1) {
|
|
26
|
-
if (obj1[key] !== obj2[key]) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
4
|
+
import { isEqualArray, isEqualObject } from "./dataQuery.js";
|
|
33
5
|
|
|
34
6
|
function dotNotationToObjectUpdate(data, object = {}) {
|
|
35
7
|
try {
|
package/src/dataQuery.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getValueFromObject } from "./getValueFromObject.js";
|
|
2
2
|
import { isValidDate } from "./isValidDate.js";
|
|
3
3
|
|
|
4
|
-
function isEqualArray(arr1, arr2) {
|
|
4
|
+
export function isEqualArray(arr1, arr2) {
|
|
5
5
|
if (arr1.length !== arr2.length) {
|
|
6
6
|
return false;
|
|
7
7
|
}
|
|
@@ -13,7 +13,7 @@ function isEqualArray(arr1, arr2) {
|
|
|
13
13
|
return true;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
function isEqualObject(obj1, obj2) {
|
|
16
|
+
export function isEqualObject(obj1, obj2) {
|
|
17
17
|
const keys1 = Object.keys(obj1);
|
|
18
18
|
const keys2 = Object.keys(obj2);
|
|
19
19
|
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2026 CoCreate LLC and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
********************************************************************************/
|
|
17
|
+
|
|
18
|
+
import getCountryLocation from './getCountryLocation.js';
|
|
19
|
+
import cloudProviders from './cloudProviders.js';
|
|
20
|
+
|
|
21
|
+
// Strict validation patterns for IPv4 and IPv6 addresses
|
|
22
|
+
const IPV4_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
23
|
+
const IPV6_REGEX = /^(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}$/;
|
|
24
|
+
|
|
25
|
+
// Operational defaults and fallback configurations
|
|
26
|
+
const DEFAULT_TIMEOUT_MS = 3000;
|
|
27
|
+
const LOCAL_FALLBACK_IP = "127.0.0.1";
|
|
28
|
+
const SESSION_STORAGE_KEY = "client_info";
|
|
29
|
+
|
|
30
|
+
// Single environment flags
|
|
31
|
+
const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
32
|
+
const isNode = typeof process !== "undefined" && process.versions && !!process.versions.node;
|
|
33
|
+
|
|
34
|
+
// Single ordered list of rich JSON geolocation providers
|
|
35
|
+
const PROVIDERS = [
|
|
36
|
+
{
|
|
37
|
+
name: "ipapi.co",
|
|
38
|
+
url: "https://ipapi.co/json/",
|
|
39
|
+
parser: (data) => ({
|
|
40
|
+
ip: data.ip,
|
|
41
|
+
city: data.city,
|
|
42
|
+
region: data.region,
|
|
43
|
+
country: data.country_name,
|
|
44
|
+
countryCode: data.country_code,
|
|
45
|
+
latitude: (data.latitude != null && data.latitude !== "") ? parseFloat(data.latitude) : null,
|
|
46
|
+
longitude: (data.longitude != null && data.longitude !== "") ? parseFloat(data.longitude) : null,
|
|
47
|
+
timezone: data.timezone,
|
|
48
|
+
org: data.org || data.asn,
|
|
49
|
+
})
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "ip-api.com",
|
|
53
|
+
url: "https://ip-api.com/json/?fields=status,message,country,countryCode,region,regionName,city,lat,lon,timezone,isp,org,query",
|
|
54
|
+
parser: (data) => {
|
|
55
|
+
if (data.status !== "success") return null;
|
|
56
|
+
return {
|
|
57
|
+
ip: data.query,
|
|
58
|
+
city: data.city,
|
|
59
|
+
region: data.regionName,
|
|
60
|
+
country: data.country,
|
|
61
|
+
countryCode: data.countryCode,
|
|
62
|
+
latitude: (data.lat != null && data.lat !== "") ? parseFloat(data.lat) : null,
|
|
63
|
+
longitude: (data.lon != null && data.lon !== "") ? parseFloat(data.lon) : null,
|
|
64
|
+
timezone: data.timezone,
|
|
65
|
+
org: data.org || data.isp,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "ipwho.is",
|
|
71
|
+
url: "https://ipwho.is/",
|
|
72
|
+
parser: (data) => {
|
|
73
|
+
if (!data.success) return null;
|
|
74
|
+
return {
|
|
75
|
+
ip: data.ip,
|
|
76
|
+
city: data.city,
|
|
77
|
+
region: data.region,
|
|
78
|
+
country: data.country,
|
|
79
|
+
countryCode: data.country_code,
|
|
80
|
+
latitude: (data.latitude != null && data.latitude !== "") ? parseFloat(data.latitude) : null,
|
|
81
|
+
longitude: (data.longitude != null && data.longitude !== "") ? parseFloat(data.longitude) : null,
|
|
82
|
+
timezone: data.timezone?.id,
|
|
83
|
+
org: data.connection?.isp || data.connection?.org,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "my-ip.io",
|
|
89
|
+
url: "https://api.my-ip.io/v2/ip.json",
|
|
90
|
+
parser: (data) => {
|
|
91
|
+
if (!data || !data.success) return null;
|
|
92
|
+
return {
|
|
93
|
+
ip: data.ip,
|
|
94
|
+
city: data.city,
|
|
95
|
+
region: data.region,
|
|
96
|
+
country: data.country?.name,
|
|
97
|
+
countryCode: data.country?.code,
|
|
98
|
+
latitude: (data.location?.lat != null && data.location.lat !== "") ? parseFloat(data.location.lat) : null,
|
|
99
|
+
longitude: (data.location?.lon != null && data.location.lon !== "") ? parseFloat(data.location.lon) : null,
|
|
100
|
+
timezone: data.timeZone,
|
|
101
|
+
org: data.asn?.name,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Internal helper to perform fetch requests with an AbortSignal timeout.
|
|
109
|
+
*/
|
|
110
|
+
async function fetchWithTimeout(url, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(url, {
|
|
116
|
+
signal: controller.signal,
|
|
117
|
+
headers: { "Accept": "application/json, text/plain, */*" }
|
|
118
|
+
});
|
|
119
|
+
clearTimeout(timeoutId);
|
|
120
|
+
return response;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
clearTimeout(timeoutId);
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Internal helper to validate IPv4 and IPv6 strings.
|
|
129
|
+
*/
|
|
130
|
+
function isValidIP(ip) {
|
|
131
|
+
if (typeof ip !== "string") return false;
|
|
132
|
+
const cleanIp = ip.trim();
|
|
133
|
+
return IPV4_REGEX.test(cleanIp) || IPV6_REGEX.test(cleanIp);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Inspects node OS network interfaces to find a valid non-internal local IP.
|
|
138
|
+
*/
|
|
139
|
+
function getLocalNodeIP() {
|
|
140
|
+
if (!isNode) return null;
|
|
141
|
+
try {
|
|
142
|
+
const req = typeof __non_webpack_require__ !== "undefined" ? __non_webpack_require__ : require;
|
|
143
|
+
const os = req("os");
|
|
144
|
+
const interfaces = os.networkInterfaces();
|
|
145
|
+
for (const interfaceName of Object.keys(interfaces)) {
|
|
146
|
+
for (const iface of interfaces[interfaceName]) {
|
|
147
|
+
if (!iface.internal && iface.family === "IPv4" && iface.address) {
|
|
148
|
+
return iface.address;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// Fallback gracefully if restricted
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolves a raw client information object into concrete latitude, longitude, and ISO country code.
|
|
160
|
+
* Optimized path:
|
|
161
|
+
* 1. Direct valid coordinates -> Immediate return with continent added
|
|
162
|
+
* 2. Missing coordinates but region present -> Attempt cloud region match for lat/lon
|
|
163
|
+
* 3. Fallback -> Return getCountryLocation coordinates
|
|
164
|
+
*
|
|
165
|
+
* @param {Object|string} info - Client telemetry or location metadata
|
|
166
|
+
* @returns {{latitude: number, longitude: number, countryCode: string, continent: string}|null} Resolved location object
|
|
167
|
+
*/
|
|
168
|
+
export function resolveInfo(info) {
|
|
169
|
+
if (!info || typeof info !== 'object') return null;
|
|
170
|
+
|
|
171
|
+
const { latitude, longitude, countryCode, region } = info;
|
|
172
|
+
|
|
173
|
+
// Fetch location data; use fallback empty object in case of undefined return
|
|
174
|
+
const countryLocation = getCountryLocation(countryCode) || {};
|
|
175
|
+
|
|
176
|
+
// 1. Direct path: Valid numeric coordinates present
|
|
177
|
+
if (latitude !== null && longitude !== null && latitude !== undefined && longitude !== undefined) {
|
|
178
|
+
return {
|
|
179
|
+
latitude,
|
|
180
|
+
longitude,
|
|
181
|
+
countryCode,
|
|
182
|
+
continent: countryLocation.continent || null
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 2. Region String Match Path: Try cloudProviders if coordinates are missing
|
|
187
|
+
if (region) {
|
|
188
|
+
const cleanRegion = String(region).trim().toLowerCase();
|
|
189
|
+
const match = cloudProviders.find(cloud =>
|
|
190
|
+
cloud.name && cloud.name.toLowerCase().includes(cleanRegion)
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (match) {
|
|
194
|
+
// If the cloud provider match has a specific country, update the continent reference
|
|
195
|
+
const matchCountryCode = match.country || countryCode;
|
|
196
|
+
const matchLocation = match.country ? (getCountryLocation(match.country) || {}) : countryLocation;
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
latitude: match.lat ?? match.latitude ?? null,
|
|
200
|
+
longitude: match.lng ?? match.longitude ?? null,
|
|
201
|
+
countryCode: matchCountryCode,
|
|
202
|
+
continent: matchLocation.continent || countryLocation.continent || null
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 3. Ultimate Fallback: Use getCountryLocation coordinates
|
|
208
|
+
return {
|
|
209
|
+
latitude: countryLocation.latitude ?? countryLocation.lat ?? null,
|
|
210
|
+
longitude: countryLocation.longitude ?? countryLocation.lng ?? null,
|
|
211
|
+
countryCode: countryLocation.countryCode ?? countryLocation.country ?? countryCode,
|
|
212
|
+
continent: countryLocation.continent || null
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Formats data into a normalized client information payload.
|
|
218
|
+
*/
|
|
219
|
+
function normalizeInfoPayload(rawData = {}, sourceProvider = "unknown", isPartial = false, isLocal = false) {
|
|
220
|
+
const payload = {
|
|
221
|
+
ip: rawData.ip || LOCAL_FALLBACK_IP,
|
|
222
|
+
city: rawData.city || null,
|
|
223
|
+
region: rawData.region || null,
|
|
224
|
+
country: rawData.country || null,
|
|
225
|
+
countryCode: rawData.countryCode || null,
|
|
226
|
+
latitude: typeof rawData.latitude === "number" && !isNaN(rawData.latitude) ? rawData.latitude : null,
|
|
227
|
+
longitude: typeof rawData.longitude === "number" && !isNaN(rawData.longitude) ? rawData.longitude : null,
|
|
228
|
+
timezone: rawData.timezone || null,
|
|
229
|
+
org: rawData.org || null,
|
|
230
|
+
source: sourceProvider,
|
|
231
|
+
isPartial,
|
|
232
|
+
isLocal,
|
|
233
|
+
timestamp: Date.now()
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Enhance and normalize the payload using the external fallback maps
|
|
237
|
+
const resolved = resolveInfo(payload);
|
|
238
|
+
|
|
239
|
+
if (resolved) {
|
|
240
|
+
payload.latitude = resolved.latitude ?? payload.latitude;
|
|
241
|
+
payload.longitude = resolved.longitude ?? payload.longitude;
|
|
242
|
+
payload.continent = resolved.continent || null;
|
|
243
|
+
|
|
244
|
+
if (resolved.countryCode) {
|
|
245
|
+
payload.countryCode = resolved.countryCode;
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
payload.continent = null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return payload;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Internal resolver that attempts provider endpoints sequentially.
|
|
256
|
+
*/
|
|
257
|
+
async function fetchClientInfo(options = {}) {
|
|
258
|
+
const timeout = options.timeout || DEFAULT_TIMEOUT_MS;
|
|
259
|
+
|
|
260
|
+
// Single iteration loop over ordered JSON providers
|
|
261
|
+
for (const provider of PROVIDERS) {
|
|
262
|
+
try {
|
|
263
|
+
const res = await fetchWithTimeout(provider.url, timeout);
|
|
264
|
+
if (res.ok) {
|
|
265
|
+
const json = await res.json();
|
|
266
|
+
const parsed = provider.parser(json);
|
|
267
|
+
|
|
268
|
+
if (parsed && isValidIP(parsed.ip)) {
|
|
269
|
+
const isPartial = Boolean(provider.isPartial || !parsed.city);
|
|
270
|
+
return normalizeInfoPayload(parsed, provider.name, isPartial, false);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
// Fail silently and try the next provider
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Local Interface Fallback if all external APIs fail
|
|
279
|
+
const localNodeIp = getLocalNodeIP();
|
|
280
|
+
const finalIp = localNodeIp || LOCAL_FALLBACK_IP;
|
|
281
|
+
|
|
282
|
+
return normalizeInfoPayload({ ip: finalIp }, localNodeIp ? "node:os" : "fallback", true, true);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Single set of module-level references for active promise and resolved payload
|
|
286
|
+
let cachedInfoPromise = null;
|
|
287
|
+
let cachedInfoPayload = null;
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Single unified function to retrieve client IP, location, and network metadata.
|
|
291
|
+
* Returns the same in-memory object reference across calls.
|
|
292
|
+
*
|
|
293
|
+
* @param {Object} [options]
|
|
294
|
+
* @param {number} [options.timeout=3000] Hard timeout limit per request (ms)
|
|
295
|
+
* @param {boolean} [options.bypassCache=false] Force a fresh network request
|
|
296
|
+
* @returns {Promise<Object>} Standardized location object
|
|
297
|
+
*/
|
|
298
|
+
export async function getClientInfo(options = {}) {
|
|
299
|
+
const { bypassCache = false } = options;
|
|
300
|
+
|
|
301
|
+
// 1. Return existing in-memory object reference if available
|
|
302
|
+
if (!bypassCache && cachedInfoPayload) {
|
|
303
|
+
return cachedInfoPayload;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 2. Return active in-flight request promise if currently executing
|
|
307
|
+
if (!bypassCache && cachedInfoPromise) {
|
|
308
|
+
return cachedInfoPromise;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 3. Rehydrate from browser sessionStorage across page refreshes
|
|
312
|
+
if (!bypassCache && isBrowser) {
|
|
313
|
+
try {
|
|
314
|
+
const stored = sessionStorage.getItem(SESSION_STORAGE_KEY);
|
|
315
|
+
if (stored) {
|
|
316
|
+
const parsed = JSON.parse(stored);
|
|
317
|
+
if (parsed && isValidIP(parsed.ip)) {
|
|
318
|
+
cachedInfoPayload = parsed;
|
|
319
|
+
return cachedInfoPayload;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
// Fall back silently
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// 4. Fetch fresh network data, assign to cachedInfoPayload, and persist to sessionStorage
|
|
328
|
+
cachedInfoPromise = fetchClientInfo(options).then((info) => {
|
|
329
|
+
cachedInfoPayload = info;
|
|
330
|
+
cachedInfoPromise = null;
|
|
331
|
+
|
|
332
|
+
if (isBrowser) {
|
|
333
|
+
try {
|
|
334
|
+
sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(info));
|
|
335
|
+
} catch (e) {
|
|
336
|
+
// Ignore storage errors
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return cachedInfoPayload;
|
|
340
|
+
}).catch((err) => {
|
|
341
|
+
cachedInfoPromise = null;
|
|
342
|
+
throw err;
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
return cachedInfoPromise;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Automatically listen for browser network reconnects to refresh location data
|
|
349
|
+
if (isBrowser) {
|
|
350
|
+
window.addEventListener("online", () => {
|
|
351
|
+
getClientInfo({ bypassCache: true }).catch(() => {
|
|
352
|
+
// Suppress background reconnect error
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export default getClientInfo;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2026 CoCreate and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
********************************************************************************/
|
|
18
|
+
|
|
19
|
+
let clockOffset = 0;
|
|
20
|
+
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
|
21
|
+
|
|
22
|
+
// Runs EXACTLY ONCE across your entire app load
|
|
23
|
+
if (isBrowser) {
|
|
24
|
+
const t0 = Date.now();
|
|
25
|
+
fetch('/api/ping', { method: 'HEAD' })
|
|
26
|
+
.then((res) => {
|
|
27
|
+
const serverDateStr = res.headers.get('date');
|
|
28
|
+
if (serverDateStr) {
|
|
29
|
+
const t1 = Date.now();
|
|
30
|
+
const tServer = new Date(serverDateStr).getTime();
|
|
31
|
+
const latency = Math.floor((t1 - t0) / 2);
|
|
32
|
+
clockOffset = tServer - (t0 + latency);
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
.catch(() => {
|
|
36
|
+
clockOffset = 0;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns the cached time offset in milliseconds.
|
|
42
|
+
* Always 0 on the server (NTP-managed) or fallback.
|
|
43
|
+
*/
|
|
44
|
+
export function getClockOffset() {
|
|
45
|
+
return clockOffset;
|
|
46
|
+
}
|