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/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