radar-sdk-js 4.2.0-beta.0 → 4.2.0-beta.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/README.md +7 -7
- package/dist/http.d.ts +3 -1
- package/dist/radar.js +2248 -0
- package/dist/{radar.esm.js.map → radar.js.map} +1 -1
- package/dist/types.d.ts +15 -5
- package/dist/ui/RadarLogoControl.d.ts +6 -0
- package/dist/ui/RadarMap.d.ts +9 -0
- package/dist/ui/RadarMarker.d.ts +12 -0
- package/dist/ui/map.d.ts +4 -2
- package/dist/util/jwt.d.ts +1 -0
- package/dist/util/net.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -3
- package/src/api/routing.ts +12 -0
- package/src/api/search.ts +2 -0
- package/src/api/track.ts +93 -7
- package/src/api/verify.ts +2 -2
- package/src/http.ts +31 -12
- package/src/types.ts +19 -7
- package/src/ui/RadarLogoControl.ts +24 -0
- package/src/ui/RadarMap.ts +165 -0
- package/src/ui/RadarMarker.ts +86 -0
- package/src/ui/autocomplete.ts +2 -1
- package/src/ui/map.ts +9 -141
- package/src/util/jwt.ts +28 -0
- package/src/util/net.ts +54 -0
- package/src/version.ts +1 -1
- package/dist/radar.esm.js +0 -2381
- package/dist/radar.umd.js +0 -2387
- package/dist/radar.umd.js.map +0 -1
package/dist/radar.js
ADDED
|
@@ -0,0 +1,2248 @@
|
|
|
1
|
+
import maplibregl from 'maplibre-gl';
|
|
2
|
+
|
|
3
|
+
class Config {
|
|
4
|
+
static setup(options = {}) {
|
|
5
|
+
Config.options = options;
|
|
6
|
+
}
|
|
7
|
+
static get() {
|
|
8
|
+
return Config.options || {};
|
|
9
|
+
}
|
|
10
|
+
static clear() {
|
|
11
|
+
Config.options = {};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
Config.defaultOptions = {
|
|
15
|
+
live: false,
|
|
16
|
+
logLevel: 'error',
|
|
17
|
+
host: 'https://api.radar.io',
|
|
18
|
+
version: 'v1',
|
|
19
|
+
debug: false,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const LOG_LEVELS = {
|
|
23
|
+
none: 0,
|
|
24
|
+
error: 1,
|
|
25
|
+
warn: 2,
|
|
26
|
+
info: 3,
|
|
27
|
+
debug: 4,
|
|
28
|
+
};
|
|
29
|
+
// get the numeric level for logLevel option
|
|
30
|
+
const getLevel = () => {
|
|
31
|
+
// disable logging in tests
|
|
32
|
+
if (window && window.RADAR_TEST_ENV) {
|
|
33
|
+
return LOG_LEVELS.none;
|
|
34
|
+
}
|
|
35
|
+
const { logLevel, debug } = Config.get();
|
|
36
|
+
if (debug) {
|
|
37
|
+
return LOG_LEVELS.debug;
|
|
38
|
+
}
|
|
39
|
+
if (logLevel) {
|
|
40
|
+
return LOG_LEVELS[logLevel];
|
|
41
|
+
}
|
|
42
|
+
return LOG_LEVELS.error; // default to error-level logging if not set
|
|
43
|
+
};
|
|
44
|
+
class Logger {
|
|
45
|
+
static debug(message) {
|
|
46
|
+
if (getLevel() === LOG_LEVELS.debug) {
|
|
47
|
+
console.log(`Radar SDK (debug): ${message.trim()}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
static info(message) {
|
|
51
|
+
if (getLevel() >= LOG_LEVELS.info) {
|
|
52
|
+
console.log(`Radar SDK: ${message.trim()}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
static warn(message) {
|
|
56
|
+
if (getLevel() >= LOG_LEVELS.warn) {
|
|
57
|
+
console.warn(`Radar SDK: ${message.trim()}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
static error(message) {
|
|
61
|
+
if (getLevel() >= LOG_LEVELS.error) {
|
|
62
|
+
console.error(`Radar SDK: ${message.trim()}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
class Storage {
|
|
68
|
+
// local storage key definitions for identifying track users
|
|
69
|
+
static get USER_ID() {
|
|
70
|
+
return 'radar-userId';
|
|
71
|
+
}
|
|
72
|
+
static get DEVICE_ID() {
|
|
73
|
+
return 'radar-deviceId';
|
|
74
|
+
}
|
|
75
|
+
static get INSTALL_ID() {
|
|
76
|
+
return 'radar-installId';
|
|
77
|
+
}
|
|
78
|
+
static get SESSION_ID() {
|
|
79
|
+
return 'radar-sessionId';
|
|
80
|
+
}
|
|
81
|
+
static get DESCRIPTION() {
|
|
82
|
+
return 'radar-description';
|
|
83
|
+
}
|
|
84
|
+
static get METADATA() {
|
|
85
|
+
return 'radar-metadata';
|
|
86
|
+
}
|
|
87
|
+
static get CACHED_LOCATION() {
|
|
88
|
+
return 'radar-cached-location';
|
|
89
|
+
}
|
|
90
|
+
static get TRIP_OPTIONS() {
|
|
91
|
+
return 'radar-trip-options';
|
|
92
|
+
}
|
|
93
|
+
static getStorage() {
|
|
94
|
+
const storage = window === null || window === void 0 ? void 0 : window.localStorage;
|
|
95
|
+
if (!storage) {
|
|
96
|
+
Logger.warn('localStorage not available.');
|
|
97
|
+
}
|
|
98
|
+
return storage;
|
|
99
|
+
}
|
|
100
|
+
static setItem(key, value) {
|
|
101
|
+
const storage = this.getStorage();
|
|
102
|
+
if (!storage) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (value === undefined || value === null) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
storage.setItem(key, value);
|
|
109
|
+
}
|
|
110
|
+
static getItem(key) {
|
|
111
|
+
const storage = this.getStorage();
|
|
112
|
+
if (!storage) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const value = storage.getItem(key);
|
|
116
|
+
if (value !== undefined && value !== null) {
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
static getJSON(key) {
|
|
122
|
+
const item = this.getItem(key);
|
|
123
|
+
if (!item) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
return JSON.parse(item);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
Logger.warn(`could not getJSON from storage for key: ${key}`);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
static removeItem(key) {
|
|
135
|
+
const storage = this.getStorage();
|
|
136
|
+
if (!storage) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
storage.removeItem(key);
|
|
140
|
+
}
|
|
141
|
+
static clear() {
|
|
142
|
+
const storage = this.getStorage();
|
|
143
|
+
if (!storage) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
storage.clear();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/******************************************************************************
|
|
151
|
+
Copyright (c) Microsoft Corporation.
|
|
152
|
+
|
|
153
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
154
|
+
purpose with or without fee is hereby granted.
|
|
155
|
+
|
|
156
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
157
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
158
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
159
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
160
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
161
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
162
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
163
|
+
***************************************************************************** */
|
|
164
|
+
/* global Reflect, Promise */
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
function __awaiter(thisArg, _arguments, P, generator) {
|
|
168
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
169
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
170
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
171
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
172
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
173
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
class RadarError extends Error {
|
|
178
|
+
constructor(message) {
|
|
179
|
+
super(message);
|
|
180
|
+
this.status = ''; // to be overridden (support for legacy status)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
class RadarPublishableKeyError extends RadarError {
|
|
184
|
+
constructor(message) {
|
|
185
|
+
super(message);
|
|
186
|
+
this.name = 'RadarPublishableKeyError';
|
|
187
|
+
this.status = 'ERROR_PUBLISHABLE_KEY';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
class RadarLocationError extends RadarError {
|
|
191
|
+
constructor(message) {
|
|
192
|
+
super(message);
|
|
193
|
+
this.name = 'RadarLocationError';
|
|
194
|
+
this.status = 'ERROR_LOCATION';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
class RadarLocationPermissionsError extends RadarError {
|
|
198
|
+
constructor(message) {
|
|
199
|
+
super(message);
|
|
200
|
+
this.name = 'RadarLocationPermissionsError';
|
|
201
|
+
this.status = 'ERROR_PERMISSIONS';
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
class RadarDesktopAppError extends RadarError {
|
|
205
|
+
constructor() {
|
|
206
|
+
super('Desktop app not running.');
|
|
207
|
+
this.name = 'RadarDesktopAppError';
|
|
208
|
+
this.status = 'ERROR_DESKTOP_APP';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// HTTP Errors
|
|
212
|
+
class RadarBadRequestError extends RadarError {
|
|
213
|
+
constructor(response) {
|
|
214
|
+
var _a;
|
|
215
|
+
super(((_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.message) || 'Bad request.');
|
|
216
|
+
this.name = 'RadarBadRequestError';
|
|
217
|
+
this.code = 400;
|
|
218
|
+
this.response = response;
|
|
219
|
+
this.status = 'ERROR_BAD_REQUEST';
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
class RadarUnauthorizedError extends RadarError {
|
|
223
|
+
constructor(response) {
|
|
224
|
+
var _a;
|
|
225
|
+
super(((_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.message) || 'Unauthorized.');
|
|
226
|
+
this.name = 'RadarUnauthorizedError';
|
|
227
|
+
this.code = 401;
|
|
228
|
+
this.response = response;
|
|
229
|
+
this.status = 'ERROR_UNAUTHORIZED';
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
class RadarPaymentRequiredError extends RadarError {
|
|
233
|
+
constructor(response) {
|
|
234
|
+
var _a;
|
|
235
|
+
super(((_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.message) || 'Payment required.');
|
|
236
|
+
this.name = 'RadarPaymentRequiredError';
|
|
237
|
+
this.code = 402;
|
|
238
|
+
this.response = response;
|
|
239
|
+
this.status = 'ERROR_PAYMENT_REQUIRED';
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
class RadarForbiddenError extends RadarError {
|
|
243
|
+
constructor(response) {
|
|
244
|
+
var _a;
|
|
245
|
+
super(((_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.message) || 'Forbidden.');
|
|
246
|
+
this.name = 'RadarForbiddenError';
|
|
247
|
+
this.code = 403;
|
|
248
|
+
this.response = response;
|
|
249
|
+
this.status = 'ERROR_FORBIDDEN';
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
class RadarNotFoundError extends RadarError {
|
|
253
|
+
constructor(response) {
|
|
254
|
+
var _a;
|
|
255
|
+
super(((_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.message) || 'Not found.');
|
|
256
|
+
this.name = 'RadarNotFoundError';
|
|
257
|
+
this.code = 404;
|
|
258
|
+
this.response = response;
|
|
259
|
+
this.status = 'ERROR_NOT_FOUND';
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
class RadarRateLimitError extends RadarError {
|
|
263
|
+
constructor(response) {
|
|
264
|
+
var _a, _b;
|
|
265
|
+
super(((_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.message) || 'Rate limit exceeded.');
|
|
266
|
+
this.name = 'RadarRateLimitError';
|
|
267
|
+
this.code = 429;
|
|
268
|
+
this.response = response;
|
|
269
|
+
this.type = (_b = response === null || response === void 0 ? void 0 : response.meta) === null || _b === void 0 ? void 0 : _b.type;
|
|
270
|
+
this.status = 'ERROR_RATE_LIMIT';
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
class RadarServerError extends RadarError {
|
|
274
|
+
constructor(response) {
|
|
275
|
+
var _a;
|
|
276
|
+
super(((_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.message) || 'Internal server error.');
|
|
277
|
+
this.name = 'RadarServerError';
|
|
278
|
+
this.response = response;
|
|
279
|
+
this.status = 'ERROR_SERVER';
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
class RadarTimeoutError extends RadarError {
|
|
283
|
+
constructor() {
|
|
284
|
+
super('Request timed out.');
|
|
285
|
+
this.name = 'RadarTimeoutError';
|
|
286
|
+
this.status = 'ERROR_TIMED_OUT';
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
class RadarUnknownError extends RadarError {
|
|
290
|
+
constructor(response) {
|
|
291
|
+
var _a;
|
|
292
|
+
super(((_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.message) || 'Something went wrong.');
|
|
293
|
+
this.name = 'RadarUnknownError';
|
|
294
|
+
this.response = response;
|
|
295
|
+
this.status = 'ERROR_UNKNOWN';
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
class RadarAutocompleteContainerNotFound extends RadarError {
|
|
299
|
+
constructor(message) {
|
|
300
|
+
super(message);
|
|
301
|
+
this.name = 'RadarAutocompleteContainerNotFound';
|
|
302
|
+
this.status = 'CONTAINER_NOT_FOUND';
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/GeolocationPositionError
|
|
307
|
+
const PERMISSION_ERROR_MESSAGES = {
|
|
308
|
+
1: 'Permission denied.',
|
|
309
|
+
2: 'Position unavailable.',
|
|
310
|
+
3: 'Timeout.',
|
|
311
|
+
};
|
|
312
|
+
const DEFAULT_POSITION_OPTIONS = {
|
|
313
|
+
maximumAge: 0,
|
|
314
|
+
timeout: 1000 * 30,
|
|
315
|
+
enableHighAccuracy: true,
|
|
316
|
+
};
|
|
317
|
+
// set "enableHighAccuracy" for navigator only when desiredAccuracy is "high"
|
|
318
|
+
const useHighAccuracy = (desiredAccuracy) => (Boolean(desiredAccuracy === 'high'));
|
|
319
|
+
class Navigator {
|
|
320
|
+
static getCurrentPosition(overrides = {}) {
|
|
321
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
322
|
+
return new Promise((resolve, reject) => {
|
|
323
|
+
const options = Config.get();
|
|
324
|
+
if (!navigator || !navigator.geolocation) {
|
|
325
|
+
return reject(new RadarLocationError('navigator.geolocation is not available.'));
|
|
326
|
+
}
|
|
327
|
+
// use cached location if available and options are set
|
|
328
|
+
if (options.cacheLocationMinutes) {
|
|
329
|
+
try {
|
|
330
|
+
const rawCachedLocation = Storage.getItem(Storage.CACHED_LOCATION);
|
|
331
|
+
if (rawCachedLocation) {
|
|
332
|
+
const cachedLocation = JSON.parse(rawCachedLocation);
|
|
333
|
+
const { latitude, longitude, accuracy, expiresAt } = cachedLocation || {};
|
|
334
|
+
if (Date.now() < parseInt(expiresAt)) {
|
|
335
|
+
if (latitude && longitude && accuracy) {
|
|
336
|
+
return resolve({ latitude, longitude, accuracy });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
Logger.warn('could not load cached location.');
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// set options from config
|
|
346
|
+
const positionOptions = Object.assign({}, DEFAULT_POSITION_OPTIONS);
|
|
347
|
+
if (options.locationMaximumAge !== undefined) {
|
|
348
|
+
positionOptions.maximumAge = options.locationMaximumAge;
|
|
349
|
+
}
|
|
350
|
+
if (options.locationTimeout !== undefined) {
|
|
351
|
+
positionOptions.timeout = options.locationTimeout;
|
|
352
|
+
}
|
|
353
|
+
if (options.desiredAccuracy !== undefined) {
|
|
354
|
+
positionOptions.enableHighAccuracy = useHighAccuracy(options.desiredAccuracy);
|
|
355
|
+
}
|
|
356
|
+
// set options from overrides
|
|
357
|
+
if (overrides.desiredAccuracy !== undefined) {
|
|
358
|
+
positionOptions.enableHighAccuracy = useHighAccuracy(overrides.desiredAccuracy);
|
|
359
|
+
}
|
|
360
|
+
Logger.info(`Using geolocation options: ${JSON.stringify(positionOptions)}`);
|
|
361
|
+
// get current location from browser
|
|
362
|
+
navigator.geolocation.getCurrentPosition((position) => {
|
|
363
|
+
if (!position || !position.coords) {
|
|
364
|
+
return reject(new RadarLocationError('device location return empty coordinates.'));
|
|
365
|
+
}
|
|
366
|
+
const { latitude, longitude, accuracy } = position.coords;
|
|
367
|
+
// cache location if option is set
|
|
368
|
+
if (options.cacheLocationMinutes) {
|
|
369
|
+
const cacheLocationMinutes = Number.parseFloat(options.cacheLocationMinutes);
|
|
370
|
+
const updatedAt = Date.now();
|
|
371
|
+
const expiresAt = updatedAt + (cacheLocationMinutes * 60 * 1000); // convert to ms
|
|
372
|
+
const lastLocation = { latitude, longitude, accuracy, updatedAt, expiresAt };
|
|
373
|
+
Storage.setItem(Storage.CACHED_LOCATION, JSON.stringify(lastLocation));
|
|
374
|
+
}
|
|
375
|
+
return resolve({ latitude, longitude, accuracy });
|
|
376
|
+
}, (err) => {
|
|
377
|
+
if (err && err.code) {
|
|
378
|
+
const message = PERMISSION_ERROR_MESSAGES[err.code.toString()] || 'unknown';
|
|
379
|
+
return reject(new RadarLocationPermissionsError(message));
|
|
380
|
+
}
|
|
381
|
+
return reject(new RadarLocationError('Could not determine location.'));
|
|
382
|
+
}, positionOptions);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
static getPermissionStatus() {
|
|
387
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
388
|
+
return new Promise((resolve, reject) => {
|
|
389
|
+
if (!navigator || !navigator.permissions) {
|
|
390
|
+
return reject(new RadarLocationError('navigator.permissions is not available.'));
|
|
391
|
+
}
|
|
392
|
+
navigator.permissions.query({ name: 'geolocation' }).then((permissionsStatus) => {
|
|
393
|
+
let locationAuthorization = 'NOT_DETERMINED';
|
|
394
|
+
switch (permissionsStatus.state) {
|
|
395
|
+
case 'granted':
|
|
396
|
+
locationAuthorization = 'GRANTED_FOREGROUND';
|
|
397
|
+
break;
|
|
398
|
+
case 'denied':
|
|
399
|
+
locationAuthorization = 'DENIED';
|
|
400
|
+
break;
|
|
401
|
+
case 'prompt':
|
|
402
|
+
locationAuthorization = 'NOT_DETERMINED';
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
return resolve(locationAuthorization);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
var SDK_VERSION = '4.2.0-beta.3';
|
|
413
|
+
|
|
414
|
+
class Http {
|
|
415
|
+
static request({ method, path, data, host, versioned = true, headers = {}, }) {
|
|
416
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
417
|
+
return new Promise((resolve, reject) => {
|
|
418
|
+
const options = Config.get();
|
|
419
|
+
// check for publishableKey on request
|
|
420
|
+
const publishableKey = options.publishableKey;
|
|
421
|
+
if (!publishableKey) {
|
|
422
|
+
reject(new RadarPublishableKeyError('publishableKey not set.'));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
// setup request URL
|
|
426
|
+
const urlHost = host || options.host;
|
|
427
|
+
const version = options.version;
|
|
428
|
+
let url = `${urlHost}/${version}/${path}`;
|
|
429
|
+
if (!versioned) {
|
|
430
|
+
url = `${urlHost}/${path}`;
|
|
431
|
+
}
|
|
432
|
+
// remove undefined values from request data
|
|
433
|
+
let body = {};
|
|
434
|
+
Object.keys(data || {}).forEach((key) => {
|
|
435
|
+
const value = data[key];
|
|
436
|
+
if (value !== undefined) {
|
|
437
|
+
body[key] = value;
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
// convert data to querystring for GET requests
|
|
441
|
+
if (method === 'GET') {
|
|
442
|
+
const params = Object.keys(body).map((key) => (`${key}=${encodeURIComponent(body[key])}`));
|
|
443
|
+
if (params.length > 0) {
|
|
444
|
+
const queryString = params.join('&');
|
|
445
|
+
url = `${url}?${queryString}`;
|
|
446
|
+
}
|
|
447
|
+
body = undefined; // dont send body for GET request
|
|
448
|
+
}
|
|
449
|
+
const xhr = new XMLHttpRequest();
|
|
450
|
+
xhr.open(method, url, true);
|
|
451
|
+
const defaultHeaders = {
|
|
452
|
+
'Authorization': publishableKey,
|
|
453
|
+
'Content-Type': 'application/json',
|
|
454
|
+
'X-Radar-Device-Type': 'Web',
|
|
455
|
+
'X-Radar-SDK-Version': SDK_VERSION,
|
|
456
|
+
};
|
|
457
|
+
// set custom config headers if present
|
|
458
|
+
let configHeaders = {};
|
|
459
|
+
if (typeof options.getRequestHeaders === 'function') {
|
|
460
|
+
configHeaders = options.getRequestHeaders();
|
|
461
|
+
}
|
|
462
|
+
// combines default headers with custom headers and config headers
|
|
463
|
+
const allHeaders = Object.assign(defaultHeaders, headers, configHeaders);
|
|
464
|
+
// set headers
|
|
465
|
+
Object.entries(allHeaders).forEach(([key, val]) => {
|
|
466
|
+
xhr.setRequestHeader(key, val);
|
|
467
|
+
});
|
|
468
|
+
xhr.onload = () => {
|
|
469
|
+
var _a;
|
|
470
|
+
let response;
|
|
471
|
+
try {
|
|
472
|
+
if (allHeaders['Content-Type'] === 'application/json') {
|
|
473
|
+
response = JSON.parse(xhr.response);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
response = { code: xhr.status, data: xhr.response };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch (e) {
|
|
480
|
+
return reject(new RadarServerError(response));
|
|
481
|
+
}
|
|
482
|
+
const error = (_a = response === null || response === void 0 ? void 0 : response.meta) === null || _a === void 0 ? void 0 : _a.error;
|
|
483
|
+
if (error === 'ERROR_PERMISSIONS') {
|
|
484
|
+
return reject(new RadarLocationPermissionsError('Location permissions not granted.'));
|
|
485
|
+
}
|
|
486
|
+
else if (error === 'ERROR_LOCATION') {
|
|
487
|
+
return reject(new RadarLocationError('Could not determine location.'));
|
|
488
|
+
}
|
|
489
|
+
else if (error === 'ERROR_NETWORK') {
|
|
490
|
+
return reject(new RadarTimeoutError());
|
|
491
|
+
}
|
|
492
|
+
if (xhr.status == 200) {
|
|
493
|
+
return resolve(response);
|
|
494
|
+
}
|
|
495
|
+
if (options.debug) {
|
|
496
|
+
Logger.debug(`API call failed: ${url}`);
|
|
497
|
+
Logger.debug(JSON.stringify(response));
|
|
498
|
+
}
|
|
499
|
+
if (xhr.status === 400) {
|
|
500
|
+
reject(new RadarBadRequestError(response));
|
|
501
|
+
}
|
|
502
|
+
else if (xhr.status === 401) {
|
|
503
|
+
reject(new RadarUnauthorizedError(response));
|
|
504
|
+
}
|
|
505
|
+
else if (xhr.status === 402) {
|
|
506
|
+
reject(new RadarPaymentRequiredError(response));
|
|
507
|
+
}
|
|
508
|
+
else if (xhr.status === 403) {
|
|
509
|
+
reject(new RadarForbiddenError(response));
|
|
510
|
+
}
|
|
511
|
+
else if (xhr.status === 404) {
|
|
512
|
+
reject(new RadarNotFoundError(response));
|
|
513
|
+
}
|
|
514
|
+
else if (xhr.status === 429) {
|
|
515
|
+
reject(new RadarRateLimitError(response));
|
|
516
|
+
}
|
|
517
|
+
else if (500 <= xhr.status && xhr.status < 600) {
|
|
518
|
+
reject(new RadarServerError(response));
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
reject(new RadarUnknownError(response));
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
xhr.onerror = function () {
|
|
525
|
+
if (host && (host === 'http://localhost:52516' || host === 'https://radar-verify.com:52516')) {
|
|
526
|
+
reject(new RadarDesktopAppError());
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
reject(new RadarServerError());
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
xhr.ontimeout = function () {
|
|
533
|
+
reject(new RadarTimeoutError());
|
|
534
|
+
};
|
|
535
|
+
xhr.send(JSON.stringify(body));
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
class AddressesAPI {
|
|
542
|
+
static validateAddress(params) {
|
|
543
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
544
|
+
const options = Config.get();
|
|
545
|
+
const response = yield Http.request({
|
|
546
|
+
method: 'GET',
|
|
547
|
+
path: 'addresses/validate',
|
|
548
|
+
data: params,
|
|
549
|
+
});
|
|
550
|
+
const { address, result } = response;
|
|
551
|
+
const validateAddressRes = {
|
|
552
|
+
address,
|
|
553
|
+
result,
|
|
554
|
+
};
|
|
555
|
+
if (options.debug) {
|
|
556
|
+
validateAddressRes.response = response;
|
|
557
|
+
}
|
|
558
|
+
return validateAddressRes;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const generateUUID = () => {
|
|
564
|
+
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
|
|
565
|
+
const r = Math.random() * 16 | 0;
|
|
566
|
+
const v = (char == 'x') ? r : (r & 0x3 | 0x8);
|
|
567
|
+
return v.toString(16);
|
|
568
|
+
});
|
|
569
|
+
return uuid;
|
|
570
|
+
};
|
|
571
|
+
class Device {
|
|
572
|
+
static getDeviceId() {
|
|
573
|
+
// use existing deviceId if present
|
|
574
|
+
const deviceId = Storage.getItem(Storage.DEVICE_ID);
|
|
575
|
+
if (deviceId) {
|
|
576
|
+
return deviceId;
|
|
577
|
+
}
|
|
578
|
+
// generate new deviceId
|
|
579
|
+
const uuid = generateUUID();
|
|
580
|
+
Storage.setItem(Storage.DEVICE_ID, uuid);
|
|
581
|
+
return uuid;
|
|
582
|
+
}
|
|
583
|
+
static getInstallId() {
|
|
584
|
+
// use existing installId if present
|
|
585
|
+
const deviceId = Storage.getItem(Storage.INSTALL_ID);
|
|
586
|
+
if (deviceId) {
|
|
587
|
+
return deviceId;
|
|
588
|
+
}
|
|
589
|
+
// generate new installId
|
|
590
|
+
const uuid = generateUUID();
|
|
591
|
+
Storage.setItem(Storage.INSTALL_ID, uuid);
|
|
592
|
+
return uuid;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const SESSION_TIMEOUT_SECS = 300; // 5 mins
|
|
597
|
+
const isValid = (sessionId) => {
|
|
598
|
+
const now = Math.trunc(Date.now() / 1000);
|
|
599
|
+
const session = Number.parseInt(sessionId);
|
|
600
|
+
const diff = Math.abs(now - session);
|
|
601
|
+
return diff < SESSION_TIMEOUT_SECS;
|
|
602
|
+
};
|
|
603
|
+
class Session {
|
|
604
|
+
static getSessionId() {
|
|
605
|
+
const sessionId = Storage.getItem(Storage.SESSION_ID);
|
|
606
|
+
// reuse session if still within 5 min threshold
|
|
607
|
+
if (sessionId && isValid(sessionId)) {
|
|
608
|
+
return sessionId;
|
|
609
|
+
}
|
|
610
|
+
// create new session if does not already exist or expired
|
|
611
|
+
const newSessionId = Math.trunc(Date.now() / 1000).toString(); // unix ts in seconds
|
|
612
|
+
Storage.setItem(Storage.SESSION_ID, newSessionId);
|
|
613
|
+
return newSessionId;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
class ConfigAPI {
|
|
618
|
+
static getConfig(params = {}) {
|
|
619
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
620
|
+
const options = Config.get();
|
|
621
|
+
if (options.version != 'v1') {
|
|
622
|
+
Logger.info('Skipping /config call.');
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const deviceId = params.deviceId || Device.getDeviceId();
|
|
626
|
+
const installId = params.installId || Device.getInstallId();
|
|
627
|
+
const sessionId = Session.getSessionId();
|
|
628
|
+
const locationAuthorization = yield Navigator.getPermissionStatus();
|
|
629
|
+
const data = {
|
|
630
|
+
deviceId,
|
|
631
|
+
installId,
|
|
632
|
+
sessionId,
|
|
633
|
+
locationAuthorization,
|
|
634
|
+
};
|
|
635
|
+
try {
|
|
636
|
+
yield Http.request({
|
|
637
|
+
method: 'GET',
|
|
638
|
+
path: 'config',
|
|
639
|
+
data,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
catch (err) {
|
|
643
|
+
Logger.warn(`Error calling /config: ${err.message}`);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
class ContextAPI {
|
|
650
|
+
static getContext(location) {
|
|
651
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
652
|
+
const options = Config.get();
|
|
653
|
+
// get device location if coordinates not provided
|
|
654
|
+
if (!location.latitude || !location.longitude) {
|
|
655
|
+
location = yield Navigator.getCurrentPosition();
|
|
656
|
+
}
|
|
657
|
+
const { latitude, longitude, accuracy } = location;
|
|
658
|
+
const response = yield Http.request({
|
|
659
|
+
method: 'GET',
|
|
660
|
+
path: 'context',
|
|
661
|
+
data: {
|
|
662
|
+
coordinates: `${latitude},${longitude}`,
|
|
663
|
+
accuracy,
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
const { geofences, place, country, state, dma, postalCode, } = response;
|
|
667
|
+
const contextRes = {
|
|
668
|
+
location,
|
|
669
|
+
geofences,
|
|
670
|
+
place,
|
|
671
|
+
country,
|
|
672
|
+
state,
|
|
673
|
+
dma,
|
|
674
|
+
postalCode,
|
|
675
|
+
};
|
|
676
|
+
if (options.debug) {
|
|
677
|
+
contextRes.response = response;
|
|
678
|
+
}
|
|
679
|
+
return contextRes;
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
class ConversionsAPI {
|
|
685
|
+
static logConversion(params) {
|
|
686
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
687
|
+
const options = Config.get();
|
|
688
|
+
const name = params.name;
|
|
689
|
+
const userId = params.userId || Storage.getItem(Storage.USER_ID);
|
|
690
|
+
const deviceId = params.deviceId || Device.getDeviceId();
|
|
691
|
+
const installId = params.installId || Device.getInstallId();
|
|
692
|
+
const metadata = params.metadata || {};
|
|
693
|
+
const createdAt = params.createdAt;
|
|
694
|
+
if (params.revenue) {
|
|
695
|
+
metadata.revenue = params.revenue;
|
|
696
|
+
}
|
|
697
|
+
const data = {
|
|
698
|
+
name,
|
|
699
|
+
userId,
|
|
700
|
+
deviceId,
|
|
701
|
+
installId,
|
|
702
|
+
metadata,
|
|
703
|
+
};
|
|
704
|
+
if (typeof createdAt === 'string') {
|
|
705
|
+
data.createdAt = createdAt;
|
|
706
|
+
}
|
|
707
|
+
else if (createdAt instanceof Date) {
|
|
708
|
+
data.createdAt = createdAt.toISOString();
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
data.createdAt = (new Date()).toISOString();
|
|
712
|
+
}
|
|
713
|
+
const response = yield Http.request({
|
|
714
|
+
method: 'POST',
|
|
715
|
+
path: 'events',
|
|
716
|
+
data,
|
|
717
|
+
});
|
|
718
|
+
const conversionRes = {
|
|
719
|
+
event: response.event,
|
|
720
|
+
};
|
|
721
|
+
if (options.debug) {
|
|
722
|
+
conversionRes.response = response;
|
|
723
|
+
}
|
|
724
|
+
return conversionRes;
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
class Geocoding {
|
|
730
|
+
static forwardGeocode(params) {
|
|
731
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
732
|
+
const options = Config.get();
|
|
733
|
+
const { query, layers, country } = params;
|
|
734
|
+
const response = yield Http.request({
|
|
735
|
+
method: 'GET',
|
|
736
|
+
path: 'geocode/forward',
|
|
737
|
+
data: {
|
|
738
|
+
query,
|
|
739
|
+
layers,
|
|
740
|
+
country,
|
|
741
|
+
},
|
|
742
|
+
});
|
|
743
|
+
const forwardGeocodeRes = {
|
|
744
|
+
addresses: response.addresses,
|
|
745
|
+
};
|
|
746
|
+
if (options.debug) {
|
|
747
|
+
forwardGeocodeRes.response = response;
|
|
748
|
+
}
|
|
749
|
+
return forwardGeocodeRes;
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
static reverseGeocode(params) {
|
|
753
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
754
|
+
const options = Config.get();
|
|
755
|
+
let { latitude, longitude, layers } = params;
|
|
756
|
+
if (!latitude || !longitude) {
|
|
757
|
+
const location = yield Navigator.getCurrentPosition();
|
|
758
|
+
latitude = location.latitude;
|
|
759
|
+
longitude = location.longitude;
|
|
760
|
+
}
|
|
761
|
+
const response = yield Http.request({
|
|
762
|
+
method: 'GET',
|
|
763
|
+
path: 'geocode/reverse',
|
|
764
|
+
data: {
|
|
765
|
+
coordinates: `${latitude},${longitude}`,
|
|
766
|
+
layers,
|
|
767
|
+
},
|
|
768
|
+
});
|
|
769
|
+
const reverseGeocodeRes = {
|
|
770
|
+
addresses: response.addresses,
|
|
771
|
+
};
|
|
772
|
+
if (options.debug) {
|
|
773
|
+
reverseGeocodeRes.response = response;
|
|
774
|
+
}
|
|
775
|
+
return reverseGeocodeRes;
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
static ipGeocode() {
|
|
779
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
780
|
+
const options = Config.get();
|
|
781
|
+
const response = yield Http.request({
|
|
782
|
+
method: 'GET',
|
|
783
|
+
path: 'geocode/ip',
|
|
784
|
+
});
|
|
785
|
+
const ipGeocodeRes = {
|
|
786
|
+
ip: response.ip,
|
|
787
|
+
address: response.address,
|
|
788
|
+
proxy: response.proxy,
|
|
789
|
+
};
|
|
790
|
+
if (options.debug) {
|
|
791
|
+
ipGeocodeRes.response = response;
|
|
792
|
+
}
|
|
793
|
+
return ipGeocodeRes;
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
class RoutingAPI {
|
|
799
|
+
static distance(params) {
|
|
800
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
801
|
+
const options = Config.get();
|
|
802
|
+
let { origin, destination, modes, units, geometry, geometryPoints, avoid, } = params;
|
|
803
|
+
// use browser location if "near" not provided
|
|
804
|
+
if (!origin) {
|
|
805
|
+
const { latitude, longitude } = yield Navigator.getCurrentPosition();
|
|
806
|
+
origin = `${latitude},${longitude}`;
|
|
807
|
+
}
|
|
808
|
+
else if (typeof origin !== 'string') { // origin is "Location" object
|
|
809
|
+
const { latitude, longitude } = origin;
|
|
810
|
+
origin = `${latitude},${longitude}`;
|
|
811
|
+
}
|
|
812
|
+
if (typeof destination !== 'string') {
|
|
813
|
+
const { latitude, longitude } = destination;
|
|
814
|
+
destination = `${latitude},${longitude}`;
|
|
815
|
+
}
|
|
816
|
+
if (Array.isArray(modes)) {
|
|
817
|
+
modes = modes.join(',');
|
|
818
|
+
}
|
|
819
|
+
if (Array.isArray(avoid)) {
|
|
820
|
+
avoid = avoid.join(',');
|
|
821
|
+
}
|
|
822
|
+
const response = yield Http.request({
|
|
823
|
+
method: 'GET',
|
|
824
|
+
path: 'route/distance',
|
|
825
|
+
data: {
|
|
826
|
+
origin,
|
|
827
|
+
destination,
|
|
828
|
+
modes,
|
|
829
|
+
units,
|
|
830
|
+
geometry,
|
|
831
|
+
geometryPoints,
|
|
832
|
+
avoid,
|
|
833
|
+
},
|
|
834
|
+
});
|
|
835
|
+
const distanceRes = {
|
|
836
|
+
routes: response.routes,
|
|
837
|
+
};
|
|
838
|
+
if (options.debug) {
|
|
839
|
+
distanceRes.response = response;
|
|
840
|
+
}
|
|
841
|
+
return distanceRes;
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
static matrix(params) {
|
|
845
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
846
|
+
const options = Config.get();
|
|
847
|
+
let { origins, destinations, mode, units, avoid, } = params;
|
|
848
|
+
// use browser location if "near" not provided
|
|
849
|
+
if (!origins) {
|
|
850
|
+
const { latitude, longitude } = yield Navigator.getCurrentPosition();
|
|
851
|
+
let originStrings = [];
|
|
852
|
+
for (let i = 0; i < destinations.length; i++) {
|
|
853
|
+
originStrings.push(`${latitude},${longitude}`);
|
|
854
|
+
}
|
|
855
|
+
origins = originStrings.join('|');
|
|
856
|
+
}
|
|
857
|
+
else if (Array.isArray(origins)) { // origin is a list of "Location" objects
|
|
858
|
+
origins = origins.map((location) => `${location.latitude},${location.longitude}`).join('|');
|
|
859
|
+
}
|
|
860
|
+
// convert array to pipe-delimited string
|
|
861
|
+
if (Array.isArray(destinations)) {
|
|
862
|
+
destinations = destinations.map((location) => `${location.latitude},${location.longitude}`).join('|');
|
|
863
|
+
}
|
|
864
|
+
if (Array.isArray(avoid)) {
|
|
865
|
+
avoid = avoid.join(',');
|
|
866
|
+
}
|
|
867
|
+
const response = yield Http.request({
|
|
868
|
+
method: 'GET',
|
|
869
|
+
path: 'route/matrix',
|
|
870
|
+
data: {
|
|
871
|
+
origins,
|
|
872
|
+
destinations,
|
|
873
|
+
mode,
|
|
874
|
+
units,
|
|
875
|
+
avoid,
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
const matrixRes = {
|
|
879
|
+
origins: response.origins,
|
|
880
|
+
destinations: response.destinations,
|
|
881
|
+
matrix: response.matrix,
|
|
882
|
+
};
|
|
883
|
+
if (options.debug) {
|
|
884
|
+
matrixRes.response = response;
|
|
885
|
+
}
|
|
886
|
+
return matrixRes;
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
class SearchAPI {
|
|
892
|
+
static autocomplete(params) {
|
|
893
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
894
|
+
const options = Config.get();
|
|
895
|
+
let { query, near, limit, layers, countryCode, expandUnits, mailable, } = params;
|
|
896
|
+
// near can be provided as a string or Location object
|
|
897
|
+
// if "near" is not provided, request will fallback to IP based location
|
|
898
|
+
if (near && typeof near !== 'string') {
|
|
899
|
+
if (near.latitude && near.longitude) {
|
|
900
|
+
near = `${near.latitude},${near.longitude}`;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
const response = yield Http.request({
|
|
904
|
+
method: 'GET',
|
|
905
|
+
path: 'search/autocomplete',
|
|
906
|
+
data: {
|
|
907
|
+
query,
|
|
908
|
+
near,
|
|
909
|
+
limit,
|
|
910
|
+
layers,
|
|
911
|
+
countryCode,
|
|
912
|
+
expandUnits,
|
|
913
|
+
mailable,
|
|
914
|
+
},
|
|
915
|
+
});
|
|
916
|
+
const autocompleteRes = {
|
|
917
|
+
addresses: response.addresses,
|
|
918
|
+
};
|
|
919
|
+
if (options.debug) {
|
|
920
|
+
autocompleteRes.response = response;
|
|
921
|
+
}
|
|
922
|
+
return autocompleteRes;
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
static searchGeofences(params) {
|
|
926
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
927
|
+
const options = Config.get();
|
|
928
|
+
let { near, radius, tags, metadata, limit, } = params;
|
|
929
|
+
// use browser location if "near" not provided
|
|
930
|
+
if (!near) {
|
|
931
|
+
const { latitude, longitude } = yield Navigator.getCurrentPosition();
|
|
932
|
+
near = `${latitude},${longitude}`;
|
|
933
|
+
}
|
|
934
|
+
else if (typeof near !== 'string') { // near is "Location" object
|
|
935
|
+
const { latitude, longitude } = near;
|
|
936
|
+
near = `${latitude},${longitude}`;
|
|
937
|
+
}
|
|
938
|
+
// convert arrays to comma-strings
|
|
939
|
+
if (Array.isArray(tags)) {
|
|
940
|
+
tags = tags.join(',');
|
|
941
|
+
}
|
|
942
|
+
const response = yield Http.request({
|
|
943
|
+
method: 'GET',
|
|
944
|
+
path: 'search/geofences',
|
|
945
|
+
data: {
|
|
946
|
+
near,
|
|
947
|
+
radius,
|
|
948
|
+
tags,
|
|
949
|
+
metadata,
|
|
950
|
+
limit,
|
|
951
|
+
},
|
|
952
|
+
});
|
|
953
|
+
const geofencesSearchRes = {
|
|
954
|
+
geofences: response.geofences,
|
|
955
|
+
};
|
|
956
|
+
if (options.debug) {
|
|
957
|
+
geofencesSearchRes.response = response;
|
|
958
|
+
}
|
|
959
|
+
return geofencesSearchRes;
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
static searchPlaces(params) {
|
|
963
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
964
|
+
const options = Config.get();
|
|
965
|
+
let { near, radius, chains, categories, groups, limit, } = params;
|
|
966
|
+
// use browser location if "near" not provided
|
|
967
|
+
if (!near) {
|
|
968
|
+
const { latitude, longitude } = yield Navigator.getCurrentPosition();
|
|
969
|
+
near = `${latitude},${longitude}`;
|
|
970
|
+
}
|
|
971
|
+
else if (typeof near !== 'string') { // near is "Location" object
|
|
972
|
+
const { latitude, longitude } = near;
|
|
973
|
+
near = `${latitude},${longitude}`;
|
|
974
|
+
}
|
|
975
|
+
// convert arrays to comma-strings
|
|
976
|
+
if (Array.isArray(chains)) {
|
|
977
|
+
chains = chains.join(',');
|
|
978
|
+
}
|
|
979
|
+
if (Array.isArray(categories)) {
|
|
980
|
+
categories = categories.join(',');
|
|
981
|
+
}
|
|
982
|
+
if (Array.isArray(groups)) {
|
|
983
|
+
groups = groups.join(',');
|
|
984
|
+
}
|
|
985
|
+
const response = yield Http.request({
|
|
986
|
+
method: 'GET',
|
|
987
|
+
path: 'search/places',
|
|
988
|
+
data: {
|
|
989
|
+
near,
|
|
990
|
+
radius,
|
|
991
|
+
chains,
|
|
992
|
+
categories,
|
|
993
|
+
groups,
|
|
994
|
+
limit,
|
|
995
|
+
},
|
|
996
|
+
});
|
|
997
|
+
const placeSearchRes = {
|
|
998
|
+
places: response.places,
|
|
999
|
+
};
|
|
1000
|
+
if (options.debug) {
|
|
1001
|
+
placeSearchRes.response = response;
|
|
1002
|
+
}
|
|
1003
|
+
return placeSearchRes;
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// https://stackoverflow.com/a/44198641
|
|
1009
|
+
const isValidDate = (date) => date && Object.prototype.toString.call(date) === '[object Date]' && !isNaN(date);
|
|
1010
|
+
class TripsAPI {
|
|
1011
|
+
static setTripOptions(tripOptions) {
|
|
1012
|
+
if (!tripOptions) {
|
|
1013
|
+
TripsAPI.clearTripOptions();
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const tripOptionsString = JSON.stringify(tripOptions);
|
|
1017
|
+
Logger.debug(`Saving trip options: ${tripOptionsString}`);
|
|
1018
|
+
Storage.setItem(Storage.TRIP_OPTIONS, tripOptionsString);
|
|
1019
|
+
}
|
|
1020
|
+
static getTripOptions() {
|
|
1021
|
+
let tripOptions = Storage.getItem(Storage.TRIP_OPTIONS);
|
|
1022
|
+
if (tripOptions) {
|
|
1023
|
+
tripOptions = JSON.parse(tripOptions);
|
|
1024
|
+
}
|
|
1025
|
+
return tripOptions;
|
|
1026
|
+
}
|
|
1027
|
+
static clearTripOptions() {
|
|
1028
|
+
Storage.removeItem(Storage.TRIP_OPTIONS);
|
|
1029
|
+
}
|
|
1030
|
+
static startTrip(tripOptions) {
|
|
1031
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1032
|
+
const options = Config.get();
|
|
1033
|
+
tripOptions = tripOptions || TripsAPI.getTripOptions();
|
|
1034
|
+
if (!tripOptions) {
|
|
1035
|
+
Logger.warn('tripOptions not set when calling "startTrip"');
|
|
1036
|
+
}
|
|
1037
|
+
const userId = tripOptions.userId || Storage.getItem(Storage.USER_ID);
|
|
1038
|
+
if (userId && userId !== Storage.getItem(Storage.USER_ID)) {
|
|
1039
|
+
// set as userId for tracking if provided
|
|
1040
|
+
Storage.setItem(Storage.USER_ID, userId);
|
|
1041
|
+
}
|
|
1042
|
+
const { externalId, destinationGeofenceTag, destinationGeofenceExternalId, mode, metadata, approachingThreshold, scheduledArrivalAt, } = tripOptions;
|
|
1043
|
+
const data = {
|
|
1044
|
+
userId,
|
|
1045
|
+
externalId,
|
|
1046
|
+
destinationGeofenceTag,
|
|
1047
|
+
destinationGeofenceExternalId,
|
|
1048
|
+
mode,
|
|
1049
|
+
metadata,
|
|
1050
|
+
approachingThreshold,
|
|
1051
|
+
};
|
|
1052
|
+
if (isValidDate(scheduledArrivalAt)) {
|
|
1053
|
+
data.scheduledArrivalAt = scheduledArrivalAt === null || scheduledArrivalAt === void 0 ? void 0 : scheduledArrivalAt.toJSON();
|
|
1054
|
+
}
|
|
1055
|
+
else {
|
|
1056
|
+
if (scheduledArrivalAt) {
|
|
1057
|
+
Logger.warn('Invalid date format for scheduledArrivalAt');
|
|
1058
|
+
}
|
|
1059
|
+
data.scheduledArrivalAt = undefined;
|
|
1060
|
+
}
|
|
1061
|
+
const response = yield Http.request({
|
|
1062
|
+
method: 'POST',
|
|
1063
|
+
path: 'trips',
|
|
1064
|
+
data,
|
|
1065
|
+
});
|
|
1066
|
+
// save trip options
|
|
1067
|
+
TripsAPI.setTripOptions(tripOptions);
|
|
1068
|
+
const tripRes = {
|
|
1069
|
+
trip: response.trip,
|
|
1070
|
+
events: response.events,
|
|
1071
|
+
};
|
|
1072
|
+
if (options.debug) {
|
|
1073
|
+
tripRes.response = response;
|
|
1074
|
+
}
|
|
1075
|
+
return tripRes;
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
static updateTrip(tripOptions, status) {
|
|
1079
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1080
|
+
const options = Config.get();
|
|
1081
|
+
tripOptions = tripOptions || TripsAPI.getTripOptions();
|
|
1082
|
+
if (!tripOptions) {
|
|
1083
|
+
Logger.warn('tripOptions not set when calling "startTrip"');
|
|
1084
|
+
}
|
|
1085
|
+
const { externalId, destinationGeofenceTag, destinationGeofenceExternalId, mode, metadata, approachingThreshold, scheduledArrivalAt, } = tripOptions;
|
|
1086
|
+
const data = {
|
|
1087
|
+
status,
|
|
1088
|
+
externalId,
|
|
1089
|
+
destinationGeofenceTag,
|
|
1090
|
+
destinationGeofenceExternalId,
|
|
1091
|
+
mode,
|
|
1092
|
+
metadata,
|
|
1093
|
+
approachingThreshold,
|
|
1094
|
+
};
|
|
1095
|
+
if (isValidDate(scheduledArrivalAt)) {
|
|
1096
|
+
data.scheduledArrivalAt = scheduledArrivalAt === null || scheduledArrivalAt === void 0 ? void 0 : scheduledArrivalAt.toJSON();
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
if (scheduledArrivalAt) {
|
|
1100
|
+
Logger.warn('Invalid date format for scheduledArrivalAt');
|
|
1101
|
+
}
|
|
1102
|
+
data.scheduledArrivalAt = undefined;
|
|
1103
|
+
}
|
|
1104
|
+
const response = yield Http.request({
|
|
1105
|
+
method: 'PATCH',
|
|
1106
|
+
path: `trips/${externalId}/update`,
|
|
1107
|
+
data,
|
|
1108
|
+
});
|
|
1109
|
+
const tripRes = {
|
|
1110
|
+
trip: response.trip,
|
|
1111
|
+
events: response.events,
|
|
1112
|
+
};
|
|
1113
|
+
if (options.debug) {
|
|
1114
|
+
tripRes.response = response;
|
|
1115
|
+
}
|
|
1116
|
+
return tripRes;
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
static completeTrip() {
|
|
1120
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1121
|
+
const tripOptions = TripsAPI.getTripOptions();
|
|
1122
|
+
const tripResponse = yield TripsAPI.updateTrip(tripOptions, 'completed');
|
|
1123
|
+
// clear local trip options
|
|
1124
|
+
TripsAPI.clearTripOptions();
|
|
1125
|
+
return tripResponse;
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
static cancelTrip() {
|
|
1129
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1130
|
+
const tripOptions = TripsAPI.getTripOptions();
|
|
1131
|
+
const tripResponse = yield TripsAPI.updateTrip(tripOptions, 'canceled');
|
|
1132
|
+
// clear local trip options
|
|
1133
|
+
TripsAPI.clearTripOptions();
|
|
1134
|
+
return tripResponse;
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const base64Encode = (str) => btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
1140
|
+
const signJWT = (payload, key) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1141
|
+
const encoder = new TextEncoder();
|
|
1142
|
+
const encodedHeader = base64Encode(JSON.stringify({
|
|
1143
|
+
alg: 'HS256',
|
|
1144
|
+
typ: 'JWT',
|
|
1145
|
+
}));
|
|
1146
|
+
const encodedPayload = base64Encode(JSON.stringify(payload));
|
|
1147
|
+
const keyData = encoder.encode(key);
|
|
1148
|
+
const messageData = encoder.encode(`${encodedHeader}.${encodedPayload}`);
|
|
1149
|
+
const cryptoKey = yield crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
|
|
1150
|
+
const signatureArrayBuffer = yield crypto.subtle.sign('HMAC', cryptoKey, messageData);
|
|
1151
|
+
const signature = base64Encode(String.fromCharCode(...Array.from(new Uint8Array(signatureArrayBuffer))));
|
|
1152
|
+
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
1153
|
+
});
|
|
1154
|
+
|
|
1155
|
+
const ping = (host) => {
|
|
1156
|
+
return new Promise((resolve) => {
|
|
1157
|
+
const socket = new WebSocket(host);
|
|
1158
|
+
let pings = 0;
|
|
1159
|
+
const latencies = [];
|
|
1160
|
+
let pingInterval;
|
|
1161
|
+
let timeoutInterval;
|
|
1162
|
+
const ping = () => {
|
|
1163
|
+
pings++;
|
|
1164
|
+
const start = Date.now();
|
|
1165
|
+
socket.send('ping');
|
|
1166
|
+
socket.onmessage = (event) => {
|
|
1167
|
+
if (event.data === 'pong') {
|
|
1168
|
+
const latency = Date.now() - start;
|
|
1169
|
+
latencies.push(latency);
|
|
1170
|
+
if (pings >= 3) {
|
|
1171
|
+
clearInterval(pingInterval);
|
|
1172
|
+
clearInterval(timeoutInterval);
|
|
1173
|
+
const median = latencies.sort((a, b) => a - b)[1];
|
|
1174
|
+
socket.close();
|
|
1175
|
+
resolve(median);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
};
|
|
1180
|
+
const timeout = () => {
|
|
1181
|
+
Logger.warn('Socket timeout');
|
|
1182
|
+
clearInterval(pingInterval);
|
|
1183
|
+
clearInterval(timeoutInterval);
|
|
1184
|
+
socket.close();
|
|
1185
|
+
resolve(-1);
|
|
1186
|
+
};
|
|
1187
|
+
socket.onerror = (err) => {
|
|
1188
|
+
Logger.warn('Error opening socket');
|
|
1189
|
+
socket.close();
|
|
1190
|
+
resolve(-1);
|
|
1191
|
+
};
|
|
1192
|
+
socket.onopen = () => {
|
|
1193
|
+
ping();
|
|
1194
|
+
pingInterval = setInterval(ping, 1000);
|
|
1195
|
+
timeoutInterval = setInterval(timeout, 10000);
|
|
1196
|
+
};
|
|
1197
|
+
});
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
class TrackAPI {
|
|
1201
|
+
static trackOnce(params) {
|
|
1202
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1203
|
+
const options = Config.get();
|
|
1204
|
+
let { latitude, longitude, accuracy, desiredAccuracy, fraud } = params;
|
|
1205
|
+
// if latitude & longitude are not provided,
|
|
1206
|
+
// try and retrieve device location (will prompt for location permissions)
|
|
1207
|
+
if (!latitude || !longitude) {
|
|
1208
|
+
const deviceLocation = yield Navigator.getCurrentPosition({ desiredAccuracy });
|
|
1209
|
+
latitude = deviceLocation.latitude;
|
|
1210
|
+
longitude = deviceLocation.longitude;
|
|
1211
|
+
accuracy = deviceLocation.accuracy;
|
|
1212
|
+
}
|
|
1213
|
+
// location authorization
|
|
1214
|
+
let locationAuthorization;
|
|
1215
|
+
try {
|
|
1216
|
+
locationAuthorization = yield Navigator.getPermissionStatus();
|
|
1217
|
+
}
|
|
1218
|
+
catch (err) {
|
|
1219
|
+
Logger.warn(`Location authorization error: ${err.message}`);
|
|
1220
|
+
}
|
|
1221
|
+
// user indentification fields
|
|
1222
|
+
const userId = params.userId || Storage.getItem(Storage.USER_ID);
|
|
1223
|
+
const deviceId = params.deviceId || Device.getDeviceId();
|
|
1224
|
+
const installId = params.installId || Device.getInstallId();
|
|
1225
|
+
const sessionId = Session.getSessionId();
|
|
1226
|
+
const deviceType = params.deviceType || 'Web';
|
|
1227
|
+
const description = params.description || Storage.getItem(Storage.DESCRIPTION);
|
|
1228
|
+
let timeZone;
|
|
1229
|
+
try {
|
|
1230
|
+
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1231
|
+
}
|
|
1232
|
+
catch (err) {
|
|
1233
|
+
Logger.warn(`Error getting time zone: ${err.message}`);
|
|
1234
|
+
}
|
|
1235
|
+
// save userId for trip tracking
|
|
1236
|
+
if (!userId) {
|
|
1237
|
+
Logger.warn('userId not provided for trackOnce.');
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
Storage.setItem(Storage.USER_ID, userId);
|
|
1241
|
+
}
|
|
1242
|
+
// other info
|
|
1243
|
+
const metadata = params.metadata || Storage.getJSON(Storage.METADATA);
|
|
1244
|
+
// trips
|
|
1245
|
+
const tripOptions = params.tripOptions || TripsAPI.getTripOptions();
|
|
1246
|
+
if (tripOptions) {
|
|
1247
|
+
tripOptions.version = '2';
|
|
1248
|
+
}
|
|
1249
|
+
const body = Object.assign(Object.assign({}, params), { locationAuthorization,
|
|
1250
|
+
accuracy,
|
|
1251
|
+
description,
|
|
1252
|
+
deviceId,
|
|
1253
|
+
deviceType, foreground: true, installId,
|
|
1254
|
+
sessionId,
|
|
1255
|
+
latitude,
|
|
1256
|
+
longitude,
|
|
1257
|
+
metadata, sdkVersion: SDK_VERSION, stopped: true, userId,
|
|
1258
|
+
tripOptions,
|
|
1259
|
+
timeZone });
|
|
1260
|
+
let response;
|
|
1261
|
+
if (fraud) {
|
|
1262
|
+
const host = 'https://api-verified.radar.io';
|
|
1263
|
+
const pingHost = 'ping.radar-verify.com';
|
|
1264
|
+
const lang = navigator.language;
|
|
1265
|
+
const langs = navigator.languages;
|
|
1266
|
+
const { dk } = yield Http.request({
|
|
1267
|
+
host,
|
|
1268
|
+
method: 'GET',
|
|
1269
|
+
path: 'config',
|
|
1270
|
+
data: {
|
|
1271
|
+
deviceId,
|
|
1272
|
+
installId,
|
|
1273
|
+
sessionId,
|
|
1274
|
+
locationAuthorization,
|
|
1275
|
+
},
|
|
1276
|
+
headers: {
|
|
1277
|
+
'X-Radar-Desktop-Device-Type': 'Web',
|
|
1278
|
+
},
|
|
1279
|
+
});
|
|
1280
|
+
let sclVal = -1;
|
|
1281
|
+
let cslVal = -1;
|
|
1282
|
+
try {
|
|
1283
|
+
const [sclRes, csl] = yield Promise.all([
|
|
1284
|
+
Http.request({
|
|
1285
|
+
host: `https://${pingHost}`,
|
|
1286
|
+
method: 'GET',
|
|
1287
|
+
path: 'ping',
|
|
1288
|
+
}),
|
|
1289
|
+
ping(`wss://${pingHost}`),
|
|
1290
|
+
]);
|
|
1291
|
+
const { scl } = sclRes;
|
|
1292
|
+
sclVal = scl;
|
|
1293
|
+
cslVal = csl;
|
|
1294
|
+
}
|
|
1295
|
+
catch (err) {
|
|
1296
|
+
// do nothing, send scl = -1 and csl = -1
|
|
1297
|
+
}
|
|
1298
|
+
const payload = {
|
|
1299
|
+
payload: JSON.stringify(Object.assign(Object.assign({}, body), { scl: sclVal, csl: cslVal, lang,
|
|
1300
|
+
langs })),
|
|
1301
|
+
};
|
|
1302
|
+
const token = yield signJWT(payload, dk);
|
|
1303
|
+
response = yield Http.request({
|
|
1304
|
+
host,
|
|
1305
|
+
method: 'POST',
|
|
1306
|
+
path: 'track',
|
|
1307
|
+
data: {
|
|
1308
|
+
token,
|
|
1309
|
+
},
|
|
1310
|
+
headers: {
|
|
1311
|
+
'X-Radar-Body-Is-Token': 'true',
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
if (options.debug && response && response.user) {
|
|
1315
|
+
if (!response.user.metadata) {
|
|
1316
|
+
response.user.metadata = {};
|
|
1317
|
+
}
|
|
1318
|
+
response.user.metadata['radar:debug'] = {
|
|
1319
|
+
sclVal,
|
|
1320
|
+
cslVal,
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
else {
|
|
1325
|
+
response = yield Http.request({
|
|
1326
|
+
method: 'POST',
|
|
1327
|
+
path: 'track',
|
|
1328
|
+
data: body,
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
const { user, events } = response;
|
|
1332
|
+
const location = { latitude, longitude, accuracy };
|
|
1333
|
+
const trackRes = {
|
|
1334
|
+
user,
|
|
1335
|
+
events,
|
|
1336
|
+
location,
|
|
1337
|
+
};
|
|
1338
|
+
if (options.debug) {
|
|
1339
|
+
trackRes.response = response;
|
|
1340
|
+
}
|
|
1341
|
+
return trackRes;
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
class VerifyAPI {
|
|
1347
|
+
static trackVerified(params, encrypted = false) {
|
|
1348
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1349
|
+
const options = Config.get();
|
|
1350
|
+
// user indentification fields
|
|
1351
|
+
const userId = params.userId || Storage.getItem(Storage.USER_ID);
|
|
1352
|
+
const deviceId = params.deviceId || Device.getDeviceId();
|
|
1353
|
+
const installId = params.installId || Device.getInstallId();
|
|
1354
|
+
const sessionId = Session.getSessionId();
|
|
1355
|
+
const description = params.description || Storage.getItem(Storage.DESCRIPTION);
|
|
1356
|
+
// save userId
|
|
1357
|
+
if (!userId) {
|
|
1358
|
+
Logger.warn('userId not provided for trackVerified.');
|
|
1359
|
+
}
|
|
1360
|
+
else {
|
|
1361
|
+
Storage.setItem(Storage.USER_ID, userId);
|
|
1362
|
+
}
|
|
1363
|
+
// other info
|
|
1364
|
+
const metadata = params.metadata || Storage.getJSON(Storage.METADATA);
|
|
1365
|
+
const body = Object.assign(Object.assign({}, params), { description,
|
|
1366
|
+
deviceId, foreground: true, installId,
|
|
1367
|
+
sessionId,
|
|
1368
|
+
metadata, sdkVersion: SDK_VERSION, stopped: true, userId,
|
|
1369
|
+
encrypted });
|
|
1370
|
+
let userAgent = navigator.userAgent;
|
|
1371
|
+
const apple = userAgent && (userAgent.toLowerCase().includes('mac') || userAgent.toLowerCase().includes('iphone') || userAgent.toLowerCase().includes('ipod') || userAgent.toLowerCase().includes('ipad'));
|
|
1372
|
+
const response = yield Http.request({
|
|
1373
|
+
method: 'GET',
|
|
1374
|
+
path: 'verify',
|
|
1375
|
+
data: body,
|
|
1376
|
+
host: apple ? 'https://radar-verify.com:52516' : 'http://localhost:52516',
|
|
1377
|
+
});
|
|
1378
|
+
const { user, events, token } = response;
|
|
1379
|
+
let location;
|
|
1380
|
+
if (user && user.location && user.location.coordinates && user.locationAccuracy) {
|
|
1381
|
+
location = {
|
|
1382
|
+
latitude: user.location.coordinates[1],
|
|
1383
|
+
longitude: user.location.coordinates[0],
|
|
1384
|
+
accuracy: user.locationAccuracy,
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
if (encrypted) {
|
|
1388
|
+
const trackTokenRes = {
|
|
1389
|
+
token,
|
|
1390
|
+
};
|
|
1391
|
+
if (options.debug) {
|
|
1392
|
+
trackTokenRes.response = response;
|
|
1393
|
+
}
|
|
1394
|
+
return trackTokenRes;
|
|
1395
|
+
}
|
|
1396
|
+
const trackRes = {
|
|
1397
|
+
user,
|
|
1398
|
+
events,
|
|
1399
|
+
location,
|
|
1400
|
+
};
|
|
1401
|
+
if (options.debug) {
|
|
1402
|
+
trackRes.response = response;
|
|
1403
|
+
}
|
|
1404
|
+
return trackRes;
|
|
1405
|
+
});
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const defaultMarkerOptions = {
|
|
1410
|
+
color: '#000257',
|
|
1411
|
+
};
|
|
1412
|
+
class RadarMarker extends maplibregl.Marker {
|
|
1413
|
+
constructor(markerOptions) {
|
|
1414
|
+
const maplibreOptions = Object.assign({}, defaultMarkerOptions);
|
|
1415
|
+
if (markerOptions.color) {
|
|
1416
|
+
maplibreOptions.color = markerOptions.color;
|
|
1417
|
+
}
|
|
1418
|
+
if (markerOptions.element) {
|
|
1419
|
+
maplibreOptions.element = markerOptions.element;
|
|
1420
|
+
}
|
|
1421
|
+
if (markerOptions.scale) {
|
|
1422
|
+
maplibreOptions.scale = markerOptions.scale;
|
|
1423
|
+
}
|
|
1424
|
+
if (markerOptions.image) {
|
|
1425
|
+
const element = document.createElement('div');
|
|
1426
|
+
element.style.backgroundImage = `url(${markerOptions.image.url})`;
|
|
1427
|
+
element.style.width = markerOptions.image.width;
|
|
1428
|
+
element.style.height = markerOptions.image.height;
|
|
1429
|
+
maplibreOptions.element = element;
|
|
1430
|
+
}
|
|
1431
|
+
super(maplibreOptions);
|
|
1432
|
+
if (markerOptions.image) {
|
|
1433
|
+
this._image = markerOptions.image;
|
|
1434
|
+
}
|
|
1435
|
+
// set popup text or HTML
|
|
1436
|
+
if (markerOptions.text) {
|
|
1437
|
+
const popup = new maplibregl.Popup({ offset: 35 }).setText(markerOptions.text);
|
|
1438
|
+
this.setPopup(popup);
|
|
1439
|
+
}
|
|
1440
|
+
else if (markerOptions.html) {
|
|
1441
|
+
const popup = new maplibregl.Popup({ offset: 35 }).setHTML(markerOptions.html);
|
|
1442
|
+
this.setPopup(popup);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
addTo(map) {
|
|
1446
|
+
if (map._customMarkerRawSvg && !this._image) {
|
|
1447
|
+
this._element.innerHTML = map._customMarkerRawSvg;
|
|
1448
|
+
}
|
|
1449
|
+
map._markers.push(this);
|
|
1450
|
+
return super.addTo(map);
|
|
1451
|
+
}
|
|
1452
|
+
remove() {
|
|
1453
|
+
if (this._map) {
|
|
1454
|
+
this._map._markers = this._map._markers.filter((marker) => marker !== this);
|
|
1455
|
+
}
|
|
1456
|
+
return super.remove();
|
|
1457
|
+
}
|
|
1458
|
+
getOptions() {
|
|
1459
|
+
const markerOptions = {
|
|
1460
|
+
// TODO: element: marker.getElement(),
|
|
1461
|
+
image: this._image,
|
|
1462
|
+
color: this._color,
|
|
1463
|
+
scale: this._scale,
|
|
1464
|
+
offset: this.getOffset(),
|
|
1465
|
+
anchor: this._anchor,
|
|
1466
|
+
draggable: this.isDraggable(),
|
|
1467
|
+
clickTolerance: this._clickTolerance,
|
|
1468
|
+
rotation: this.getRotation(),
|
|
1469
|
+
rotationAlignment: this.getRotationAlignment(),
|
|
1470
|
+
pitchAlignment: this.getPitchAlignment()
|
|
1471
|
+
};
|
|
1472
|
+
return markerOptions;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const RADAR_LOGO_URL = 'https://api.radar.io/maps/static/images/logo.svg';
|
|
1477
|
+
class RadarLogoControl {
|
|
1478
|
+
onAdd() {
|
|
1479
|
+
const img = document.createElement('img');
|
|
1480
|
+
img.src = RADAR_LOGO_URL;
|
|
1481
|
+
this.link = document.createElement('a');
|
|
1482
|
+
this.link.id = 'radar-map-logo';
|
|
1483
|
+
this.link.href = 'https://radar.com?ref=powered_by_radar';
|
|
1484
|
+
this.link.target = '_blank';
|
|
1485
|
+
this.link.appendChild(img);
|
|
1486
|
+
return this.link;
|
|
1487
|
+
}
|
|
1488
|
+
onRemove() {
|
|
1489
|
+
var _a;
|
|
1490
|
+
(_a = this.link) === null || _a === void 0 ? void 0 : _a.remove();
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const DEFAULT_STYLE = 'radar-default-v1';
|
|
1495
|
+
const RADAR_STYLES = [
|
|
1496
|
+
'radar-default-v1',
|
|
1497
|
+
'radar-light-v1',
|
|
1498
|
+
'radar-dark-v1',
|
|
1499
|
+
];
|
|
1500
|
+
const defaultMaplibreOptions = {
|
|
1501
|
+
minZoom: 1,
|
|
1502
|
+
maxZoom: 20,
|
|
1503
|
+
attributionControl: false,
|
|
1504
|
+
dragRotate: false,
|
|
1505
|
+
touchPitch: false,
|
|
1506
|
+
maplibreLogo: false,
|
|
1507
|
+
};
|
|
1508
|
+
const uuidRegex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
|
|
1509
|
+
const createStyleURL = (options, style = DEFAULT_STYLE) => (`${options.host}/maps/styles/${style}?publishableKey=${options.publishableKey}`);
|
|
1510
|
+
/** Check if style is a Radar style or a custom style */
|
|
1511
|
+
const isRadarStyle = (style) => {
|
|
1512
|
+
return RADAR_STYLES.includes(style) || uuidRegex.test(style);
|
|
1513
|
+
};
|
|
1514
|
+
/** Use formatted style URL if using one of Radar's out-of-the-box styles or is a Radar custom style */
|
|
1515
|
+
const getStyle = (options, mapOptions) => {
|
|
1516
|
+
const style = mapOptions.style;
|
|
1517
|
+
if (!style || (typeof style === 'string' && isRadarStyle(style))) {
|
|
1518
|
+
return createStyleURL(options, style);
|
|
1519
|
+
}
|
|
1520
|
+
return mapOptions.style;
|
|
1521
|
+
};
|
|
1522
|
+
class RadarMap extends maplibregl.Map {
|
|
1523
|
+
constructor(mapOptions) {
|
|
1524
|
+
const config = Config.get();
|
|
1525
|
+
if (!config.publishableKey) {
|
|
1526
|
+
Logger.warn('publishableKey not set. Call Radar.initialize() with key before creating a new map.');
|
|
1527
|
+
}
|
|
1528
|
+
// configure maplibre options
|
|
1529
|
+
const style = getStyle(config, mapOptions);
|
|
1530
|
+
const maplibreOptions = Object.assign({}, defaultMaplibreOptions, mapOptions, { style });
|
|
1531
|
+
Logger.debug(`initialize map with options: ${JSON.stringify(maplibreOptions)}`);
|
|
1532
|
+
// custom request handler for Radar styles
|
|
1533
|
+
maplibreOptions.transformRequest = (url, resourceType) => {
|
|
1534
|
+
let radarStyleURL = url;
|
|
1535
|
+
let headers = { 'Authorization': config.publishableKey };
|
|
1536
|
+
if (resourceType === 'Style' && isRadarStyle(url)) {
|
|
1537
|
+
radarStyleURL = createStyleURL(config, url);
|
|
1538
|
+
}
|
|
1539
|
+
if (mapOptions.transformRequest) {
|
|
1540
|
+
return mapOptions.transformRequest(radarStyleURL, resourceType);
|
|
1541
|
+
}
|
|
1542
|
+
if (typeof config.getRequestHeaders === 'function') {
|
|
1543
|
+
headers = Object.assign(headers, config.getRequestHeaders());
|
|
1544
|
+
}
|
|
1545
|
+
return { url: radarStyleURL, headers };
|
|
1546
|
+
};
|
|
1547
|
+
super(maplibreOptions);
|
|
1548
|
+
this._markers = [];
|
|
1549
|
+
const container = this.getContainer();
|
|
1550
|
+
if (!container.style.width && !container.style.height) {
|
|
1551
|
+
Logger.warn('map container does not have a set "width" or "height"');
|
|
1552
|
+
}
|
|
1553
|
+
// add radar logo
|
|
1554
|
+
const radarLogo = new RadarLogoControl();
|
|
1555
|
+
this.addControl(radarLogo, 'bottom-left');
|
|
1556
|
+
// add attribution
|
|
1557
|
+
const attribution = new maplibregl.AttributionControl({ compact: false });
|
|
1558
|
+
this.addControl(attribution, 'bottom-right');
|
|
1559
|
+
// add zoom controls
|
|
1560
|
+
const nav = new maplibregl.NavigationControl({ showCompass: false });
|
|
1561
|
+
this.addControl(nav, 'bottom-right');
|
|
1562
|
+
// handle map resize actions
|
|
1563
|
+
const onResize = () => {
|
|
1564
|
+
const attrib = document.querySelector('.maplibregl-ctrl-attrib');
|
|
1565
|
+
if (attrib) {
|
|
1566
|
+
const width = this.getContainer().clientWidth;
|
|
1567
|
+
if (width < 380) {
|
|
1568
|
+
attrib.classList.add('hidden');
|
|
1569
|
+
}
|
|
1570
|
+
else {
|
|
1571
|
+
attrib.classList.remove('hidden');
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
this.on('resize', onResize);
|
|
1576
|
+
this.on('load', onResize);
|
|
1577
|
+
const onStyleLoad = () => __awaiter(this, void 0, void 0, function* () {
|
|
1578
|
+
var _a;
|
|
1579
|
+
this._customMarkerRawSvg = undefined;
|
|
1580
|
+
const style = this.getStyle();
|
|
1581
|
+
const customMarkers = (_a = style.metadata) === null || _a === void 0 ? void 0 : _a['radar:customMarkers'];
|
|
1582
|
+
if (Array.isArray(customMarkers) && customMarkers.length > 0) {
|
|
1583
|
+
const customMarker = customMarkers[0]; // only support one custom marker for now
|
|
1584
|
+
try {
|
|
1585
|
+
const markerRawSvg = yield Http.request({
|
|
1586
|
+
method: 'GET',
|
|
1587
|
+
versioned: false,
|
|
1588
|
+
path: `maps/markers/${customMarker.id}`,
|
|
1589
|
+
headers: {
|
|
1590
|
+
'Content-Type': 'image/svg+xml',
|
|
1591
|
+
},
|
|
1592
|
+
});
|
|
1593
|
+
this._customMarkerRawSvg = markerRawSvg.data;
|
|
1594
|
+
}
|
|
1595
|
+
catch (err) {
|
|
1596
|
+
Logger.warn(`Error getting custom marker: ${customMarker.id} - using default marker.`);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
// set markers if necessary
|
|
1600
|
+
this._markers.forEach((marker) => {
|
|
1601
|
+
if (this._customMarkerRawSvg && !marker._image) {
|
|
1602
|
+
// set custom marker
|
|
1603
|
+
marker._element.innerHTML = this._customMarkerRawSvg;
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
const markerOptions = marker.getOptions();
|
|
1607
|
+
const newMarker = new RadarMarker(markerOptions); // get default element
|
|
1608
|
+
// set default element
|
|
1609
|
+
marker._element.innerHTML = newMarker._element.innerHTML;
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
});
|
|
1613
|
+
this.on('styledata', onStyleLoad);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
class MapUI {
|
|
1618
|
+
static getMapLibre() {
|
|
1619
|
+
return maplibregl;
|
|
1620
|
+
}
|
|
1621
|
+
static createMap(mapOptions) {
|
|
1622
|
+
const radarMap = new RadarMap(mapOptions);
|
|
1623
|
+
return radarMap;
|
|
1624
|
+
}
|
|
1625
|
+
static createMarker(markerOptions = {}) {
|
|
1626
|
+
const radarMarker = new RadarMarker(markerOptions);
|
|
1627
|
+
return radarMarker;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const CLASSNAMES = {
|
|
1632
|
+
WRAPPER: 'radar-autocomplete-wrapper',
|
|
1633
|
+
INPUT: 'radar-autocomplete-input',
|
|
1634
|
+
SEARCH_ICON: 'radar-autocomplete-search-icon',
|
|
1635
|
+
RESULTS_LIST: 'radar-autocomplete-results-list',
|
|
1636
|
+
RESULTS_ITEM: 'radar-autocomplete-results-item',
|
|
1637
|
+
RESULTS_MARKER: 'radar-autocomplete-results-marker',
|
|
1638
|
+
SELECTED_ITEM: 'radar-autocomplete-results-item-selected',
|
|
1639
|
+
POWERED_BY_RADAR: 'radar-powered',
|
|
1640
|
+
NO_RESULTS: 'radar-no-results',
|
|
1641
|
+
};
|
|
1642
|
+
const ARIA = {
|
|
1643
|
+
EXPANDED: 'aria-expanded',
|
|
1644
|
+
};
|
|
1645
|
+
const defaultAutocompleteOptions = {
|
|
1646
|
+
container: 'autocomplete',
|
|
1647
|
+
debounceMS: 200,
|
|
1648
|
+
minCharacters: 3,
|
|
1649
|
+
limit: 8,
|
|
1650
|
+
placeholder: 'Search address',
|
|
1651
|
+
responsive: true,
|
|
1652
|
+
disabled: false,
|
|
1653
|
+
showMarkers: true,
|
|
1654
|
+
hideResultsOnBlur: true,
|
|
1655
|
+
};
|
|
1656
|
+
// determine whether to use px or CSS string
|
|
1657
|
+
const formatCSSValue = (value) => {
|
|
1658
|
+
if (typeof value === 'number') {
|
|
1659
|
+
return `${value}px`;
|
|
1660
|
+
}
|
|
1661
|
+
return value;
|
|
1662
|
+
};
|
|
1663
|
+
const DEFAULT_WIDTH = 400;
|
|
1664
|
+
const setWidth = (input, options) => {
|
|
1665
|
+
// if responsive and width is provided, treat it as maxWidth
|
|
1666
|
+
if (options.responsive) {
|
|
1667
|
+
input.style.width = '100%';
|
|
1668
|
+
if (options.width) {
|
|
1669
|
+
input.style.maxWidth = formatCSSValue(options.width);
|
|
1670
|
+
}
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
// if not responsive, set fixed width and unset maxWidth
|
|
1674
|
+
input.style.width = formatCSSValue(options.width || DEFAULT_WIDTH);
|
|
1675
|
+
input.style.removeProperty('max-width');
|
|
1676
|
+
};
|
|
1677
|
+
const setHeight = (resultsList, options) => {
|
|
1678
|
+
if (options.maxHeight) {
|
|
1679
|
+
resultsList.style.maxHeight = formatCSSValue(options.maxHeight);
|
|
1680
|
+
resultsList.style.overflowY = 'auto'; /* allow overflow when maxHeight is applied */
|
|
1681
|
+
}
|
|
1682
|
+
};
|
|
1683
|
+
const getMarkerIcon = (color = "#ACBDC8") => {
|
|
1684
|
+
const fill = color.replace('#', '%23');
|
|
1685
|
+
const svg = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
1686
|
+
<path d="M12.5704 6.57036C12.5704 4.11632 10.6342 2.11257 8.21016 2C8.14262 2 8.06757 2 8.00003 2C7.93249 2 7.85744 2 7.7899 2C5.35838 2.11257 3.42967 4.11632 3.42967 6.57036C3.42967 6.60037 3.42967 6.6379 3.42967 6.66792C3.42967 6.69794 3.42967 6.73546 3.42967 6.76548C3.42967 9.46717 7.09196 13.3621 7.4672 13.7598C7.61729 13.9174 7.84994 14 8.00003 14C8.15012 14 8.38277 13.9174 8.53286 13.7598C8.9156 13.3621 12.5704 9.46717 12.5704 6.76548C12.5704 6.72795 12.5704 6.69794 12.5704 6.66792C12.5704 6.6379 12.5704 6.60037 12.5704 6.57036ZM7.99252 8.28893C7.04693 8.28893 6.27395 7.52345 6.27395 6.57036C6.27395 5.61726 7.03943 4.85178 7.99252 4.85178C8.94562 4.85178 9.7111 5.61726 9.7111 6.57036C9.7111 7.52345 8.94562 8.28893 7.99252 8.28893Z" fill="${fill}"/>
|
|
1687
|
+
</svg>`.trim();
|
|
1688
|
+
return `data:image/svg+xml;charset=utf-8,${svg}`;
|
|
1689
|
+
};
|
|
1690
|
+
class AutocompleteUI {
|
|
1691
|
+
// create a new AutocompleteUI instance
|
|
1692
|
+
static createAutocomplete(autocompleteOptions) {
|
|
1693
|
+
return new AutocompleteUI(autocompleteOptions);
|
|
1694
|
+
}
|
|
1695
|
+
constructor(options = {}) {
|
|
1696
|
+
this.config = Object.assign({}, defaultAutocompleteOptions, options);
|
|
1697
|
+
// setup state
|
|
1698
|
+
this.isOpen = false;
|
|
1699
|
+
this.debouncedFetchResults = this.debounce(this.fetchResults, this.config.debounceMS);
|
|
1700
|
+
this.results = [];
|
|
1701
|
+
this.highlightedIndex = -1;
|
|
1702
|
+
// set threshold alias
|
|
1703
|
+
if (this.config.threshold !== undefined) {
|
|
1704
|
+
this.config.minCharacters = this.config.threshold;
|
|
1705
|
+
Logger.warn('AutocompleteUI option "threshold" is deprecated, use "minCharacters" instead.');
|
|
1706
|
+
}
|
|
1707
|
+
if (options.near) {
|
|
1708
|
+
if (typeof options.near === 'string') {
|
|
1709
|
+
this.near = options.near;
|
|
1710
|
+
}
|
|
1711
|
+
else {
|
|
1712
|
+
this.near = `${options.near.latitude},${options.near.longitude}`;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
// get container element
|
|
1716
|
+
let containerEL;
|
|
1717
|
+
if (typeof this.config.container === 'string') { // lookup container element by ID
|
|
1718
|
+
containerEL = document.getElementById(this.config.container);
|
|
1719
|
+
}
|
|
1720
|
+
else { // use provided element
|
|
1721
|
+
containerEL = this.config.container; // HTMLElement
|
|
1722
|
+
}
|
|
1723
|
+
if (!containerEL) {
|
|
1724
|
+
throw new RadarAutocompleteContainerNotFound(`Could not find container element: ${this.config.container}`);
|
|
1725
|
+
}
|
|
1726
|
+
this.container = containerEL;
|
|
1727
|
+
// create wrapper for input and result list
|
|
1728
|
+
this.wrapper = document.createElement('div');
|
|
1729
|
+
this.wrapper.classList.add(CLASSNAMES.WRAPPER);
|
|
1730
|
+
this.wrapper.style.display = this.config.responsive ? 'block' : 'inline-block';
|
|
1731
|
+
setWidth(this.wrapper, this.config);
|
|
1732
|
+
// result list element
|
|
1733
|
+
this.resultsList = document.createElement('ul');
|
|
1734
|
+
this.resultsList.classList.add(CLASSNAMES.RESULTS_LIST);
|
|
1735
|
+
setHeight(this.resultsList, this.config);
|
|
1736
|
+
if (containerEL.nodeName === 'INPUT') {
|
|
1737
|
+
// if an <input> element is provided, use that as the inputField,
|
|
1738
|
+
// and append the resultList to it's parent container
|
|
1739
|
+
this.inputField = containerEL;
|
|
1740
|
+
// append to dom
|
|
1741
|
+
this.wrapper.appendChild(this.resultsList);
|
|
1742
|
+
containerEL.parentNode.appendChild(this.wrapper);
|
|
1743
|
+
}
|
|
1744
|
+
else {
|
|
1745
|
+
// if container is not an input, create new input and append to container
|
|
1746
|
+
// create new input
|
|
1747
|
+
this.inputField = document.createElement('input');
|
|
1748
|
+
this.inputField.classList.add(CLASSNAMES.INPUT);
|
|
1749
|
+
this.inputField.placeholder = this.config.placeholder;
|
|
1750
|
+
this.inputField.type = 'text';
|
|
1751
|
+
this.inputField.disabled = this.config.disabled;
|
|
1752
|
+
// search icon
|
|
1753
|
+
const searchIcon = document.createElement('div');
|
|
1754
|
+
searchIcon.classList.add(CLASSNAMES.SEARCH_ICON);
|
|
1755
|
+
// append to DOM
|
|
1756
|
+
this.wrapper.appendChild(this.inputField);
|
|
1757
|
+
this.wrapper.appendChild(this.resultsList);
|
|
1758
|
+
this.wrapper.appendChild(searchIcon);
|
|
1759
|
+
this.container.appendChild(this.wrapper);
|
|
1760
|
+
}
|
|
1761
|
+
// setup event listeners
|
|
1762
|
+
this.inputField.addEventListener('input', this.handleInput.bind(this));
|
|
1763
|
+
this.inputField.addEventListener('keydown', this.handleKeyboardNavigation.bind(this));
|
|
1764
|
+
if (this.config.hideResultsOnBlur) {
|
|
1765
|
+
this.inputField.addEventListener('blur', this.close.bind(this), true);
|
|
1766
|
+
}
|
|
1767
|
+
Logger.debug(`AutocompleteUI iniailized with options: ${JSON.stringify(this.config)}`);
|
|
1768
|
+
}
|
|
1769
|
+
handleInput() {
|
|
1770
|
+
// Fetch autocomplete results and display them
|
|
1771
|
+
const query = this.inputField.value;
|
|
1772
|
+
if (query.length < this.config.minCharacters) {
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
this.debouncedFetchResults(query)
|
|
1776
|
+
.then((results) => {
|
|
1777
|
+
const onResults = this.config.onResults;
|
|
1778
|
+
if (onResults) {
|
|
1779
|
+
onResults(results);
|
|
1780
|
+
}
|
|
1781
|
+
this.displayResults(results);
|
|
1782
|
+
})
|
|
1783
|
+
.catch((error) => {
|
|
1784
|
+
Logger.warn(`Autocomplete ui error: ${error.message}`);
|
|
1785
|
+
const onError = this.config.onError;
|
|
1786
|
+
if (onError) {
|
|
1787
|
+
onError(error);
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
debounce(fn, delay) {
|
|
1792
|
+
let timeoutId;
|
|
1793
|
+
let resolveFn;
|
|
1794
|
+
let rejectFn;
|
|
1795
|
+
return (...args) => {
|
|
1796
|
+
clearTimeout(timeoutId);
|
|
1797
|
+
timeoutId = setTimeout(() => {
|
|
1798
|
+
const result = fn.apply(this, args);
|
|
1799
|
+
if (result instanceof Promise) {
|
|
1800
|
+
result
|
|
1801
|
+
.then((value) => {
|
|
1802
|
+
if (resolveFn) {
|
|
1803
|
+
resolveFn(value);
|
|
1804
|
+
}
|
|
1805
|
+
})
|
|
1806
|
+
.catch((error) => {
|
|
1807
|
+
if (rejectFn) {
|
|
1808
|
+
rejectFn(error);
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
}, delay);
|
|
1813
|
+
return new Promise((resolve, reject) => {
|
|
1814
|
+
resolveFn = resolve;
|
|
1815
|
+
rejectFn = reject;
|
|
1816
|
+
});
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
fetchResults(query) {
|
|
1820
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1821
|
+
const { limit, layers, countryCode, expandUnits, mailable, onRequest } = this.config;
|
|
1822
|
+
const params = {
|
|
1823
|
+
query,
|
|
1824
|
+
limit,
|
|
1825
|
+
layers,
|
|
1826
|
+
countryCode,
|
|
1827
|
+
expandUnits,
|
|
1828
|
+
mailable,
|
|
1829
|
+
};
|
|
1830
|
+
if (this.near) {
|
|
1831
|
+
params.near = this.near;
|
|
1832
|
+
}
|
|
1833
|
+
if (onRequest) {
|
|
1834
|
+
onRequest(params);
|
|
1835
|
+
}
|
|
1836
|
+
const { addresses } = yield SearchAPI.autocomplete(params);
|
|
1837
|
+
return addresses;
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
displayResults(results) {
|
|
1841
|
+
// Clear the previous results
|
|
1842
|
+
this.clearResultsList();
|
|
1843
|
+
this.results = results;
|
|
1844
|
+
let marker;
|
|
1845
|
+
if (this.config.showMarkers) {
|
|
1846
|
+
marker = document.createElement('img');
|
|
1847
|
+
marker.classList.add(CLASSNAMES.RESULTS_MARKER);
|
|
1848
|
+
marker.setAttribute('src', getMarkerIcon(this.config.markerColor));
|
|
1849
|
+
}
|
|
1850
|
+
// Create and append list items for each result
|
|
1851
|
+
results.forEach((result, index) => {
|
|
1852
|
+
const li = document.createElement('li');
|
|
1853
|
+
li.classList.add(CLASSNAMES.RESULTS_ITEM);
|
|
1854
|
+
// construct result with bolded label
|
|
1855
|
+
let listContent;
|
|
1856
|
+
if (result.formattedAddress.includes(result.addressLabel) && result.layer !== 'postalCode') {
|
|
1857
|
+
// if addressLabel is contained in the formatted address, bold the address label
|
|
1858
|
+
const regex = new RegExp(`(${result.addressLabel}),?`);
|
|
1859
|
+
listContent = result.formattedAddress.replace(regex, '<b>$1</b>');
|
|
1860
|
+
}
|
|
1861
|
+
else {
|
|
1862
|
+
// otherwise append the address or place label to formatted address
|
|
1863
|
+
const label = result.placeLabel || result.addressLabel;
|
|
1864
|
+
listContent = `<b>${label}</b> ${result.formattedAddress}`;
|
|
1865
|
+
}
|
|
1866
|
+
li.innerHTML = listContent;
|
|
1867
|
+
// prepend marker if enabled
|
|
1868
|
+
if (marker) {
|
|
1869
|
+
li.prepend(marker.cloneNode());
|
|
1870
|
+
}
|
|
1871
|
+
// add click handler to each result, use mousedown to fire before blur event
|
|
1872
|
+
li.addEventListener('mousedown', () => {
|
|
1873
|
+
this.select(index);
|
|
1874
|
+
});
|
|
1875
|
+
this.resultsList.appendChild(li);
|
|
1876
|
+
});
|
|
1877
|
+
this.open();
|
|
1878
|
+
if (results.length > 0) {
|
|
1879
|
+
const link = document.createElement('a');
|
|
1880
|
+
link.href = 'https://radar.com?ref=powered_by_radar';
|
|
1881
|
+
link.target = '_blank';
|
|
1882
|
+
this.poweredByLink = link;
|
|
1883
|
+
const poweredByText = document.createElement('span');
|
|
1884
|
+
poweredByText.textContent = 'Powered by';
|
|
1885
|
+
link.appendChild(poweredByText);
|
|
1886
|
+
const radarLogo = document.createElement('span');
|
|
1887
|
+
radarLogo.id = 'radar-powered-logo';
|
|
1888
|
+
link.appendChild(radarLogo);
|
|
1889
|
+
const poweredByContainer = document.createElement('div');
|
|
1890
|
+
poweredByContainer.classList.add(CLASSNAMES.POWERED_BY_RADAR);
|
|
1891
|
+
poweredByContainer.appendChild(link);
|
|
1892
|
+
this.resultsList.appendChild(poweredByContainer);
|
|
1893
|
+
}
|
|
1894
|
+
else {
|
|
1895
|
+
const noResultsText = document.createElement('div');
|
|
1896
|
+
noResultsText.classList.add(CLASSNAMES.NO_RESULTS);
|
|
1897
|
+
noResultsText.textContent = 'No results';
|
|
1898
|
+
this.resultsList.appendChild(noResultsText);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
open() {
|
|
1902
|
+
if (this.isOpen) {
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
this.wrapper.setAttribute(ARIA.EXPANDED, 'true');
|
|
1906
|
+
this.resultsList.removeAttribute('hidden');
|
|
1907
|
+
this.isOpen = true;
|
|
1908
|
+
}
|
|
1909
|
+
close(e) {
|
|
1910
|
+
if (!this.isOpen) {
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
// run this code async to allow link click to propagate before blur
|
|
1914
|
+
// (add 100ms delay if closed from link click)
|
|
1915
|
+
const linkClick = e && (e.relatedTarget === this.poweredByLink);
|
|
1916
|
+
setTimeout(() => {
|
|
1917
|
+
this.wrapper.removeAttribute(ARIA.EXPANDED);
|
|
1918
|
+
this.resultsList.setAttribute('hidden', '');
|
|
1919
|
+
this.highlightedIndex = -1;
|
|
1920
|
+
this.isOpen = false;
|
|
1921
|
+
this.clearResultsList();
|
|
1922
|
+
}, linkClick ? 100 : 0);
|
|
1923
|
+
}
|
|
1924
|
+
goTo(index) {
|
|
1925
|
+
if (!this.isOpen || !this.results.length) {
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
// wrap around
|
|
1929
|
+
if (index < 0) {
|
|
1930
|
+
index = this.results.length - 1;
|
|
1931
|
+
}
|
|
1932
|
+
else if (index >= this.results.length) {
|
|
1933
|
+
index = 0;
|
|
1934
|
+
}
|
|
1935
|
+
const resultItems = this.resultsList.getElementsByTagName('li');
|
|
1936
|
+
if (this.highlightedIndex > -1) {
|
|
1937
|
+
// clear class names on previously highlighted item
|
|
1938
|
+
resultItems[this.highlightedIndex].classList.remove(CLASSNAMES.SELECTED_ITEM);
|
|
1939
|
+
}
|
|
1940
|
+
// add class name to newly highlighted item
|
|
1941
|
+
resultItems[index].classList.add(CLASSNAMES.SELECTED_ITEM);
|
|
1942
|
+
this.highlightedIndex = index;
|
|
1943
|
+
}
|
|
1944
|
+
handleKeyboardNavigation(event) {
|
|
1945
|
+
// fallback to deprecated "keyCode" if event.code not set
|
|
1946
|
+
const code = event.code !== undefined ? event.code : event.keyCode;
|
|
1947
|
+
// allow event to propagate if result list is not open
|
|
1948
|
+
if (!this.isOpen) {
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
switch (code) {
|
|
1952
|
+
// Next item
|
|
1953
|
+
case 'Tab':
|
|
1954
|
+
case 'ArrowDown':
|
|
1955
|
+
case 40:
|
|
1956
|
+
event.preventDefault();
|
|
1957
|
+
this.goTo(this.highlightedIndex + 1);
|
|
1958
|
+
break;
|
|
1959
|
+
// Prev item
|
|
1960
|
+
case 'ArrowUp':
|
|
1961
|
+
case 38:
|
|
1962
|
+
event.preventDefault();
|
|
1963
|
+
this.goTo(this.highlightedIndex - 1);
|
|
1964
|
+
break;
|
|
1965
|
+
// Select
|
|
1966
|
+
case 'Enter':
|
|
1967
|
+
case 13:
|
|
1968
|
+
this.select(this.highlightedIndex);
|
|
1969
|
+
break;
|
|
1970
|
+
// Close
|
|
1971
|
+
case 'Esc':
|
|
1972
|
+
case 27:
|
|
1973
|
+
this.close();
|
|
1974
|
+
break;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
select(index) {
|
|
1978
|
+
const result = this.results[index];
|
|
1979
|
+
if (!result) {
|
|
1980
|
+
Logger.warn(`No autocomplete result found at index: ${index}`);
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
let inputValue;
|
|
1984
|
+
if (result.formattedAddress.includes(result.addressLabel)) {
|
|
1985
|
+
inputValue = result.formattedAddress;
|
|
1986
|
+
}
|
|
1987
|
+
else {
|
|
1988
|
+
const label = result.placeLabel || result.addressLabel;
|
|
1989
|
+
inputValue = `${label}, ${result.formattedAddress}`;
|
|
1990
|
+
}
|
|
1991
|
+
this.inputField.value = inputValue;
|
|
1992
|
+
const onSelection = this.config.onSelection;
|
|
1993
|
+
if (onSelection) {
|
|
1994
|
+
onSelection(result);
|
|
1995
|
+
}
|
|
1996
|
+
// clear results list
|
|
1997
|
+
this.close();
|
|
1998
|
+
}
|
|
1999
|
+
clearResultsList() {
|
|
2000
|
+
this.resultsList.innerHTML = '';
|
|
2001
|
+
this.results = [];
|
|
2002
|
+
}
|
|
2003
|
+
// remove elements from DOM
|
|
2004
|
+
remove() {
|
|
2005
|
+
Logger.debug('AutocompleteUI removed.');
|
|
2006
|
+
this.inputField.remove();
|
|
2007
|
+
this.resultsList.remove();
|
|
2008
|
+
this.wrapper.remove();
|
|
2009
|
+
}
|
|
2010
|
+
setNear(near) {
|
|
2011
|
+
if (near === undefined || near === null) {
|
|
2012
|
+
this.near = undefined;
|
|
2013
|
+
}
|
|
2014
|
+
else if (typeof near === 'string') {
|
|
2015
|
+
this.near = near;
|
|
2016
|
+
}
|
|
2017
|
+
else {
|
|
2018
|
+
this.near = `${near.latitude},${near.longitude}`;
|
|
2019
|
+
}
|
|
2020
|
+
return this;
|
|
2021
|
+
}
|
|
2022
|
+
setPlaceholder(placeholder) {
|
|
2023
|
+
this.config.placeholder = placeholder;
|
|
2024
|
+
this.inputField.placeholder = placeholder;
|
|
2025
|
+
return this;
|
|
2026
|
+
}
|
|
2027
|
+
setDisabled(disabled) {
|
|
2028
|
+
this.config.disabled = disabled;
|
|
2029
|
+
this.inputField.disabled = disabled;
|
|
2030
|
+
return this;
|
|
2031
|
+
}
|
|
2032
|
+
setResponsive(responsive) {
|
|
2033
|
+
this.config.responsive = responsive;
|
|
2034
|
+
setWidth(this.wrapper, this.config);
|
|
2035
|
+
return this;
|
|
2036
|
+
}
|
|
2037
|
+
setWidth(width) {
|
|
2038
|
+
this.config.width = width;
|
|
2039
|
+
setWidth(this.wrapper, this.config);
|
|
2040
|
+
return this;
|
|
2041
|
+
}
|
|
2042
|
+
setMaxHeight(height) {
|
|
2043
|
+
this.config.maxHeight = height;
|
|
2044
|
+
setHeight(this.resultsList, this.config);
|
|
2045
|
+
return this;
|
|
2046
|
+
}
|
|
2047
|
+
setMinCharacters(minCharacters) {
|
|
2048
|
+
this.config.minCharacters = minCharacters;
|
|
2049
|
+
this.config.threshold = minCharacters;
|
|
2050
|
+
return this;
|
|
2051
|
+
}
|
|
2052
|
+
setLimit(limit) {
|
|
2053
|
+
this.config.limit = limit;
|
|
2054
|
+
return this;
|
|
2055
|
+
}
|
|
2056
|
+
setShowMarkers(showMarkers) {
|
|
2057
|
+
this.config.showMarkers = showMarkers;
|
|
2058
|
+
if (showMarkers) {
|
|
2059
|
+
const marker = document.createElement('img');
|
|
2060
|
+
marker.classList.add(CLASSNAMES.RESULTS_MARKER);
|
|
2061
|
+
marker.setAttribute('src', getMarkerIcon(this.config.markerColor));
|
|
2062
|
+
const resultItems = this.resultsList.getElementsByTagName('li');
|
|
2063
|
+
for (let i = 0; i < resultItems.length; i++) {
|
|
2064
|
+
const currentMarker = resultItems[i].getElementsByClassName(CLASSNAMES.RESULTS_MARKER)[0];
|
|
2065
|
+
if (!currentMarker) {
|
|
2066
|
+
resultItems[i].prepend(marker.cloneNode());
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
else {
|
|
2071
|
+
const resultItems = this.resultsList.getElementsByTagName('li');
|
|
2072
|
+
for (let i = 0; i < resultItems.length; i++) {
|
|
2073
|
+
const marker = resultItems[i].getElementsByClassName(CLASSNAMES.RESULTS_MARKER)[0];
|
|
2074
|
+
if (marker) {
|
|
2075
|
+
marker.remove();
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
return this;
|
|
2080
|
+
}
|
|
2081
|
+
setMarkerColor(color) {
|
|
2082
|
+
this.config.markerColor = color;
|
|
2083
|
+
const marker = this.resultsList.getElementsByClassName(CLASSNAMES.RESULTS_MARKER);
|
|
2084
|
+
for (let i = 0; i < marker.length; i++) {
|
|
2085
|
+
marker[i].setAttribute('src', getMarkerIcon(color));
|
|
2086
|
+
}
|
|
2087
|
+
return this;
|
|
2088
|
+
}
|
|
2089
|
+
setHideResultsOnBlur(hideResultsOnBlur) {
|
|
2090
|
+
this.config.hideResultsOnBlur = hideResultsOnBlur;
|
|
2091
|
+
if (hideResultsOnBlur) {
|
|
2092
|
+
this.inputField.addEventListener('blur', this.close.bind(this), true);
|
|
2093
|
+
}
|
|
2094
|
+
else {
|
|
2095
|
+
this.inputField.removeEventListener('blur', this.close.bind(this), true);
|
|
2096
|
+
}
|
|
2097
|
+
return this;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
const isSecretKey = (key) => (key.includes('_sk_'));
|
|
2102
|
+
const isLiveKey = (key) => (key.includes('_live_'));
|
|
2103
|
+
class Radar {
|
|
2104
|
+
static get VERSION() {
|
|
2105
|
+
return SDK_VERSION;
|
|
2106
|
+
}
|
|
2107
|
+
// "ui" namespace
|
|
2108
|
+
static get ui() {
|
|
2109
|
+
return {
|
|
2110
|
+
maplibregl: MapUI.getMapLibre(),
|
|
2111
|
+
map: MapUI.createMap,
|
|
2112
|
+
marker: MapUI.createMarker,
|
|
2113
|
+
autocomplete: AutocompleteUI.createAutocomplete,
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
static initialize(publishableKey, options = {}) {
|
|
2117
|
+
if (!publishableKey) {
|
|
2118
|
+
throw new RadarPublishableKeyError('Publishable key required in initialization.');
|
|
2119
|
+
}
|
|
2120
|
+
if (isSecretKey(publishableKey)) {
|
|
2121
|
+
throw new RadarPublishableKeyError('Secret keys are not allowed. Please use your Radar publishable key.');
|
|
2122
|
+
}
|
|
2123
|
+
// store settings in global config
|
|
2124
|
+
const live = isLiveKey(publishableKey);
|
|
2125
|
+
const logLevel = live ? 'error' : 'info';
|
|
2126
|
+
const debug = !live;
|
|
2127
|
+
const radarOptions = Object.assign(Config.defaultOptions, {
|
|
2128
|
+
publishableKey,
|
|
2129
|
+
live,
|
|
2130
|
+
logLevel,
|
|
2131
|
+
debug,
|
|
2132
|
+
}, options);
|
|
2133
|
+
Config.setup(radarOptions);
|
|
2134
|
+
Logger.info(`initialized with ${live ? 'live' : 'test'} publishableKey.`);
|
|
2135
|
+
if (options.debug) {
|
|
2136
|
+
Logger.info(`using options: ${JSON.stringify(options)}`);
|
|
2137
|
+
}
|
|
2138
|
+
// NOTE(jasonl): this allows us to run jest tests
|
|
2139
|
+
// without having to mock the ConfigAPI.getConfig call
|
|
2140
|
+
if (!(window === null || window === void 0 ? void 0 : window.RADAR_TEST_ENV)) {
|
|
2141
|
+
ConfigAPI.getConfig();
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
static clear() {
|
|
2145
|
+
Config.clear();
|
|
2146
|
+
}
|
|
2147
|
+
///////////////////////
|
|
2148
|
+
// geofencing platform
|
|
2149
|
+
///////////////////////
|
|
2150
|
+
static setUserId(userId) {
|
|
2151
|
+
if (!userId) {
|
|
2152
|
+
Storage.removeItem(Storage.USER_ID);
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
Storage.setItem(Storage.USER_ID, String(userId).trim());
|
|
2156
|
+
}
|
|
2157
|
+
static setDescription(description) {
|
|
2158
|
+
if (!description) {
|
|
2159
|
+
Storage.removeItem(Storage.DESCRIPTION);
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
Storage.setItem(Storage.DESCRIPTION, String(description).trim());
|
|
2163
|
+
}
|
|
2164
|
+
static setMetadata(metadata) {
|
|
2165
|
+
if (!metadata) {
|
|
2166
|
+
Storage.removeItem(Storage.METADATA);
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
Storage.setItem(Storage.METADATA, JSON.stringify(metadata));
|
|
2170
|
+
}
|
|
2171
|
+
static getLocation() {
|
|
2172
|
+
return Navigator.getCurrentPosition();
|
|
2173
|
+
}
|
|
2174
|
+
static trackOnce(params = {}) {
|
|
2175
|
+
try {
|
|
2176
|
+
return TrackAPI.trackOnce(params);
|
|
2177
|
+
}
|
|
2178
|
+
finally {
|
|
2179
|
+
ConfigAPI.getConfig(params); // call with updated permissions
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
static trackVerified(params = {}) {
|
|
2183
|
+
return VerifyAPI.trackVerified(params);
|
|
2184
|
+
}
|
|
2185
|
+
static trackVerifiedToken(params = {}) {
|
|
2186
|
+
return VerifyAPI.trackVerified(params, true);
|
|
2187
|
+
}
|
|
2188
|
+
static getContext(params) {
|
|
2189
|
+
return ContextAPI.getContext(params);
|
|
2190
|
+
}
|
|
2191
|
+
static setTripOptions(tripOptions) {
|
|
2192
|
+
TripsAPI.setTripOptions(tripOptions);
|
|
2193
|
+
}
|
|
2194
|
+
static clearTripOptions() {
|
|
2195
|
+
TripsAPI.clearTripOptions();
|
|
2196
|
+
}
|
|
2197
|
+
static getTripOptions() {
|
|
2198
|
+
return TripsAPI.getTripOptions();
|
|
2199
|
+
}
|
|
2200
|
+
static startTrip(tripOptions) {
|
|
2201
|
+
return TripsAPI.startTrip(tripOptions);
|
|
2202
|
+
}
|
|
2203
|
+
static updateTrip(tripOptions) {
|
|
2204
|
+
return TripsAPI.updateTrip(tripOptions);
|
|
2205
|
+
}
|
|
2206
|
+
static completeTrip() {
|
|
2207
|
+
return TripsAPI.completeTrip();
|
|
2208
|
+
}
|
|
2209
|
+
static cancelTrip() {
|
|
2210
|
+
return TripsAPI.cancelTrip();
|
|
2211
|
+
}
|
|
2212
|
+
static logConversion(params) {
|
|
2213
|
+
return ConversionsAPI.logConversion(params);
|
|
2214
|
+
}
|
|
2215
|
+
/////////////////
|
|
2216
|
+
// maps platform
|
|
2217
|
+
/////////////////
|
|
2218
|
+
static forwardGeocode(params) {
|
|
2219
|
+
return Geocoding.forwardGeocode(params);
|
|
2220
|
+
}
|
|
2221
|
+
static reverseGeocode(params) {
|
|
2222
|
+
return Geocoding.reverseGeocode(params);
|
|
2223
|
+
}
|
|
2224
|
+
static ipGeocode() {
|
|
2225
|
+
return Geocoding.ipGeocode();
|
|
2226
|
+
}
|
|
2227
|
+
static autocomplete(params) {
|
|
2228
|
+
return SearchAPI.autocomplete(params);
|
|
2229
|
+
}
|
|
2230
|
+
static searchGeofences(params) {
|
|
2231
|
+
return SearchAPI.searchGeofences(params);
|
|
2232
|
+
}
|
|
2233
|
+
static searchPlaces(params) {
|
|
2234
|
+
return SearchAPI.searchPlaces(params);
|
|
2235
|
+
}
|
|
2236
|
+
static validateAddress(params) {
|
|
2237
|
+
return AddressesAPI.validateAddress(params);
|
|
2238
|
+
}
|
|
2239
|
+
static distance(params) {
|
|
2240
|
+
return RoutingAPI.distance(params);
|
|
2241
|
+
}
|
|
2242
|
+
static matrix(params) {
|
|
2243
|
+
return RoutingAPI.matrix(params);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
export { Radar as default };
|
|
2248
|
+
//# sourceMappingURL=radar.js.map
|