@agnostack/verifyd 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2205 +1,26 @@
1
- (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('luxon')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'luxon'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@agnostack/verifyd"] = {}, global.luxon));
5
- })(this, (function (exports, luxon) { 'use strict';
6
-
7
- /* eslint-disable no-use-before-define */
8
-
9
- // #region lib-core
10
- // TODO!!!: keep in sync between verifyd and lib-core (and lib-utils-js at bottom)
11
- const normalizeShopifyId = (shopifyData) => {
12
- const isString = isType(shopifyData, 'string');
13
- if (isString && !shopifyData.startsWith('gid:')) {
14
- if (!isNumericOnly(shopifyData)) {
15
- return {}
16
- }
17
-
18
- return {
19
- id: `${shopifyData}`,
20
- }
21
- }
22
-
23
- const { id: _entityId, legacyResourceId } = isString
24
- ? { id: shopifyData }
25
- : ensureObject(shopifyData);
26
-
27
- if (stringEmpty(_entityId) && stringEmpty(legacyResourceId)) {
28
- return {}
29
- }
30
-
31
- const entity_id = ensureString(_entityId);
32
- const { entity_type, parsedId } = entity_id.match(/^gid:\/\/shopify\/(?<entity_type>.*)\/(?<parsedId>[0-9]+).*$/)?.groups ?? {};
33
- const id = ensureString(parsedId || legacyResourceId);
34
-
35
- const hasEntityId = stringNotEmpty(entity_id);
36
- const hasEntityType = stringNotEmpty(entity_type);
37
-
38
- return {
39
- ...stringNotEmpty(id) && { id },
40
- ...hasEntityId && { entity_id },
41
- ...hasEntityType && { entity_type },
42
- ...(hasEntityId && hasEntityType) && {
43
- gid: `gid://shopify/${entity_type}/${entity_id}`,
44
- },
45
- }
46
- };
47
-
48
- const random = (max = 100, min = 1) => {
49
- const floor = Math.min(max, min);
50
- return Math.floor(Math.random() * (max - floor + 1)) + floor
51
- };
52
-
53
- const nextRandom = (max = 100, min = 1) => {
54
- const maximum = Math.max(max, 0);
55
- const minimum = Math.max(min, 0);
56
-
57
- let randomNumber = maximum;
58
-
59
- if (maximum > minimum) {
60
- do {
61
- randomNumber = random(maximum, minimum);
62
- } while (randomNumber === nextRandom.last)
63
-
64
- nextRandom.last = randomNumber;
65
- }
66
-
67
- return randomNumber
68
- };
69
-
70
- const arrayRandom = (_values, _max = 1) => {
71
- const values = ensureArray(_values);
72
- const valuesLength = values.length;
73
- const maxLength = Math.min(_max, valuesLength);
74
-
75
- if (valuesLength <= maxLength) {
76
- return values
77
- }
78
-
79
- const arrayLength = valuesLength - 1;
80
- const randomValues = [];
81
- do {
82
- const newVal = values[nextRandom(arrayLength, 0)];
83
- if (!randomValues.includes(newVal)) {
84
- randomValues.push(newVal);
85
- }
86
- } while (randomValues.length < maxLength)
87
-
88
- return randomValues
89
- };
90
-
91
- const arrayRandomItem = (_values) => (
92
- arrayRandom(_values)[0]
93
- );
94
-
95
- const isArray = (value) => (
96
- // eslint-disable-next-line eqeqeq
97
- (value != undefined) && Array.isArray(value)
98
- );
99
-
100
- const isSet = (value) => (
101
- // eslint-disable-next-line eqeqeq
102
- (value != undefined) && (value instanceof Set)
103
- );
104
-
105
- const isType = (value, type) => (
106
- // eslint-disable-next-line eqeqeq, valid-typeof
107
- (value != undefined) && (typeof value === type)
108
- );
109
-
110
- const isTypeEnhanced = (value, type) => {
111
- switch (true) {
112
- case (type === 'set'): {
113
- return isSet(value)
114
- }
115
-
116
- case (type === 'array'): {
117
- return (
118
- isArray(value) &&
119
- !isSet(value)
120
- )
121
- }
122
-
123
- case (type === 'object'): {
124
- return (
125
- isType(value, type) &&
126
- !isArray(value)
127
- )
128
- }
129
-
130
- default: {
131
- return isType(value, type)
132
- }
133
- }
134
- };
135
-
136
- const safeTrim = (value) => {
137
- if (isType(value, 'string')) {
138
- // eslint-disable-next-line no-param-reassign
139
- value = value.trim();
140
- }
141
-
142
- return value
143
- };
144
-
145
- const safeParse = (value, trim) => {
146
- if (isType(value, 'string')) {
147
- if (trim) {
148
- // eslint-disable-next-line no-param-reassign
149
- value = safeTrim(value);
150
- }
151
-
152
- if (value.startsWith('{') || value.startsWith('[') || value.startsWith('"')) {
153
- // eslint-disable-next-line no-param-reassign
154
- value = JSON.parse(value);
155
- }
156
- }
157
-
158
- // TODO: should this be value ?? {}
159
- return value
160
- };
161
-
162
- const ensureString = (string) => (
163
- string ? `${string}` : ''
164
- );
165
-
166
- // HMMM: what if 'string' is an object?
167
- const ensureStringOnly = (string) => (
168
- // eslint-disable-next-line eqeqeq
169
- (string != undefined) ? `${string}` : ''
170
- );
171
-
172
- const ensureStringAscii = (string) => (
173
- // eslint-disable-next-line no-control-regex
174
- ensureStringOnly(string).replace(/[^\x00-\x7F]/g, '')
175
- );
176
-
177
- const ensureStringClean = (string) => (
178
- // eslint-disable-next-line no-control-regex
179
- ensureStringOnly(string).replace(/[^a-z0-9_-]/gi, '')
180
- );
181
-
182
- const ensureAlphaNumeric = (string) => (
183
- // eslint-disable-next-line no-control-regex
184
- ensureStringOnly(string).replace(/[^a-z0-9]/gi, '')
185
- );
186
-
187
- const isNumericOnly = (value) => {
188
- const [matched] = `${value}`.match(/^([0-9]+)$/) ?? [];
189
- // eslint-disable-next-line eqeqeq
190
- return (matched != undefined)
191
- };
192
-
193
- const isNumericNegatable = (value) => {
194
- const [matched] = `${value}`.match(/^(-?[0-9]+(\.[0-9]+)?)?$/) ?? [];
195
- // eslint-disable-next-line eqeqeq
196
- return (matched != undefined)
197
- };
198
-
199
- // TODO: explore places using ensureNumeric to move to isNumericNegatable
200
- const ensureNumeric = (string) => (
201
- Number(ensureString(string).replace(/[^0-9.]/gi, ''))
202
- );
203
-
204
- const ensureNumericOnly = (string) => (
205
- Number(ensureString(string).replace(/[^0-9]/gi, ''))
206
- );
207
-
208
- // TODO: update regex to handle negative number returns negative. Something like (?!\d\.)(-?\d+(\.\d)?)
209
- const ensureNumericNegatable = (string) => (
210
- Number(ensureString(string).replace(/[^0-9.-]/gi, ''))
211
- );
212
-
213
- const ensureNumericConstrained = (value, { min: _min = 0, max: _max = 100 } = {}) => {
214
- const min = ensureNumericNegatable(_min);
215
- const max = ensureNumericNegatable(_max);
216
-
217
- return Math.max(
218
- min,
219
- Math.min(ensureNumeric(value), max)
220
- )
221
- };
222
-
223
- const ensureArray = (array = []) => (
224
- // eslint-disable-next-line no-nested-ternary
225
- !array ? [] : Array.isArray(array) ? array : [array]
226
- );
227
-
228
- const ensureArraySet = (arrayOrSet) => (
229
- ensureArray(isSet(arrayOrSet) ? [...arrayOrSet] : arrayOrSet)
230
- );
231
-
232
- const arrayEmpty = (array, disableEmptyString = false) => (
233
- // eslint-disable-next-line eqeqeq
234
- !array || !array.length || (array.length === 1 && (array[0] == undefined || (disableEmptyString && stringEmpty(array[0]))))
235
- );
236
-
237
- const arrayNotEmpty = (array, disableEmptyString = false) => (
238
- // eslint-disable-next-line eqeqeq
239
- !arrayEmpty(array) && array[0] != undefined && (!disableEmptyString || stringNotEmpty(array[0]))
240
- );
241
-
242
- const findLastMatch = (array, filterCallback = (value) => (value)) => (
243
- ensureArray(array).filter(filterCallback).slice(-1)[0]
244
- );
245
-
246
- const ensureObject = (object) => (
247
- object ?? {}
248
- );
249
-
250
- // NOTE: this does not ensure !isType(string)
251
- const objectEmpty = (object) => (
252
- !object || !Object.keys(object).length
253
- );
254
-
255
- const objectNotEmpty = (object) => (
256
- !objectEmpty(object)
257
- );
258
-
259
- const nullable = (object) => (
260
- (!object || (object === 'null') || (object === undefined) || (object === null))
261
- ? null // TODO: explore undefined here??
262
- : object
263
- );
264
-
265
- const objectContainsAnyValue = (object) => (
266
- Object.values(ensureObject(object)).some((value) => nullable(value))
267
- );
268
-
269
- const isUndefined = (value) => (
270
- // eslint-disable-next-line eqeqeq
271
- value == undefined
272
- );
273
-
274
- const cleanObject = (object, allowEmptyLeafs, isEmptyCallback) => (
275
- Object.entries(ensureObject(object)).reduce((
276
- _cleanedObject,
277
- [key, _value]
278
- ) => {
279
- if (!allowEmptyLeafs && (isEmptyCallback?.(_value) ?? isUndefined(_value))) {
280
- return _cleanedObject // skip key if null or undefined
281
- }
282
-
283
- const value = !Array.isArray(_value)
284
- ? cleanObjectValue(_value, allowEmptyLeafs, isEmptyCallback)
285
- : _value.reduce((_data, __value) => ([
286
- ..._data,
287
- cleanObjectValue(__value, allowEmptyLeafs, isEmptyCallback)
288
- ]), []);
289
-
290
- return {
291
- ..._cleanedObject,
292
- [key]: value,
293
- }
294
- }, {})
295
- );
296
-
297
- const cleanObjectValue = (value, allowEmptyLeafs, isEmptyCallback) => (
298
- isType(value, 'object') ? cleanObject(value, allowEmptyLeafs, isEmptyCallback) : value
299
- );
300
-
301
- const stringNotEmptyOnly = (stringable) => {
302
- const string = ensureStringOnly(stringable);
303
- return ((string.length > 0) && !['null', 'undefined'].includes(string))
304
- };
305
-
306
- const stringEmptyOnly = (stringable) => (
307
- !stringNotEmptyOnly(stringable)
308
- );
309
-
310
- const stringNotEmpty = (stringable) => {
311
- const string = ensureString(stringable);
312
- return ((string.length > 0) && !['null', 'undefined'].includes(string))
313
- };
314
-
315
- const stringEmpty = (stringable) => (
316
- !stringNotEmpty(stringable)
317
- );
318
-
319
- const splitString = (splitting, index = splitting.length) => {
320
- const string = ensureString(splitting);
321
- return [string.slice(0, index), string.slice(index)]
322
- };
323
-
324
- const compareNumber = (number1 = 0, number2 = 0) => (
325
- number1 - number2
326
- );
327
-
328
- const compareString = (string1, string2) => {
329
- if (stringEmpty(string1)) {
330
- return 1
331
- }
332
-
333
- if (stringEmpty(string2)) {
334
- return -1
335
- }
336
-
337
- return ensureString(string1).localeCompare(string2)
338
- };
339
-
340
- const compareEntryKeys = ([string1], [string2]) => (
341
- compareString(string1, string2)
342
- );
343
-
344
- const objectToSortedString = (object, separator = '') => (
345
- Object.entries(ensureObject(object))
346
- .sort(compareEntryKeys)
347
- .map(([key, value]) => `${key}=${value}`)
348
- .join(separator)
349
- );
350
-
351
- const isHTML = (string) => (
352
- (string?.startsWith?.('<') || string?.startsWith?.('\n<')) ?? false
353
- );
354
-
355
- const freeze = (logMessage, object, type = 'log') => {
356
- console[type](logMessage, !object ? object : JSON.parse(JSON.stringify(ensureObject(object))));
357
- };
358
-
359
- const replaceSpaces = (string, replacement = '') => (
360
- !isType(string, 'string')
361
- ? ''
362
- : string.replace(/\s/g, replacement)
363
- );
364
-
365
- const recase = (string, replacement = '') => (
366
- !isType(string, 'string')
367
- ? ''
368
- : string
369
- .replace(/[^a-zA-Z0-9]+/g, replacement)
370
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
371
- .replace(/([a-z])([A-Z])/g, '$1-$2')
372
- .replace(/([0-9])([^0-9])/g, '$1-$2')
373
- .replace(/([^0-9])([0-9])/g, '$1-$2')
374
- .replace(/[-_]+/g, replacement)
375
- .replace(new RegExp(`${replacement}$`), '')
376
- .toLowerCase()
377
- );
378
-
379
- const zencase = (string, replacement = '-') => (
380
- !isType(string, 'string')
381
- ? ''
382
- : replaceSpaces(string, replacement)
383
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
384
- .replace(/([a-z])([A-Z])/g, '$1-$2')
385
- .replace(new RegExp(`${replacement}$`), '')
386
- .toLowerCase()
387
- );
388
-
389
- const dashcase = (string) => (
390
- recase(string, '-')
391
- );
392
-
393
- const undashcase = (string, replacement = ' ') => (
394
- ensureString(string).replace(/-/g, replacement).trim()
395
- );
396
-
397
- const snakecase = (string) => (
398
- recase(string, '_')
399
- );
400
-
401
- const unsnakecase = (string, replacement = ' ') => (
402
- ensureString(string).replace(/_/g, replacement).trim()
403
- );
404
-
405
- const uppercase = (string) => (
406
- ensureString(string).toUpperCase()
407
- );
408
-
409
- const lowercase = (string, defaultValue = '') => (
410
- !stringNotEmpty(string)
411
- ? defaultValue
412
- : ensureString(string).toLowerCase()
413
- );
414
-
415
- const slugify = (string) => (
416
- lowercase(dashcase(string))
417
- );
418
-
419
- const capitalize = (string) => {
420
- const parts = splitString(string, 1);
421
- return `${uppercase(parts[0])}${parts[1]}`
422
- };
423
-
424
- const camelCase = (string) => (
425
- stringNotEmpty(string)
426
- ? unsnakecase(undashcase(string)).replace(/\w\S*/g, (word, charIndex) => {
427
- if (charIndex === 0) {
428
- return lowercase(word)
429
- }
430
- return `${uppercase(word.charAt(0))}${lowercase(word.substr(1))}`
431
- }).split(' ').join('')
432
- : ''
433
- );
434
-
435
- const titleCase = (string, _overrides) => {
436
- if (!stringNotEmpty(string)) {
437
- return ''
438
- }
439
-
440
- const overrides = {
441
- 'add-ons': 'Add-Ons',
442
- agnostack: 'agnoStack',
443
- ai: 'AI',
444
- b2b: 'B2B',
445
- b2c: 'B2C',
446
- cartcollab: 'CartCollab',
447
- 'cartcollab(sm)': 'CartCollab(SM)',
448
- chatgpt: 'ChatGPT',
449
- covid: 'COVID',
450
- covid19: 'COVID-19',
451
- 'covid-19': 'COVID-19',
452
- crm: 'CRM',
453
- elasticpath: 'Elastic Path',
454
- ecommerce: 'eCommerce',
455
- 'e-commerce': 'eCommerce',
456
- faqs: 'FAQs',
457
- gpt: 'GPT',
458
- 'smile.io': 'Smile.io',
459
- 'stamped.io': 'Stamped.io',
460
- 'judge.me': 'Judge.me',
461
- 'influence.io': 'Influence.io',
462
- keepsmallstrong: 'KeepSmallStrong',
463
- loyaltylion: 'LoyaltyLion',
464
- openai: 'OpenAI',
465
- paypal: 'PayPal',
466
- 'postscript.io': 'Postscript.io',
467
- recharge: 'ReCharge',
468
- 'stay.ai': 'Stay.ai',
469
- 'stay ai': 'Stay Ai',
470
- shipengine: 'ShipEngine',
471
- shipperhq: 'ShipperHQ',
472
- shipstation: 'ShipStation',
473
- taxjar: 'TaxJar',
474
- yotpo: 'YotPo',
475
- ..._overrides,
476
- };
477
-
478
- const stringParts = lowercase(string)
479
- .split(' ')
480
- .reduce((
481
- _stringParts,
482
- _stringPart
483
- ) => {
484
- const stringPart = overrides[_stringPart] ?? _stringPart
485
- .replace(/([A-Z]+)/g, ' $1')
486
- .replace(/\s\s+/g, ' ')
487
- .replace(/(\b[a-z](?!\s)*)/g, (firstChar) => uppercase(firstChar));
488
-
489
- return [
490
- ..._stringParts,
491
- ...stringNotEmpty(stringPart) ? [stringPart] : []
492
- ]
493
- }, []);
494
-
495
- return stringParts.join(' ')
496
- };
497
-
498
- const removeLeadingSlash = (string) => (
499
- ensureString(string).replace(/^\//, '')
500
- );
501
-
502
- const removeLeadingTrailingQuotes = (string) => (
503
- ensureString(string).replace(/^"|"$/g, '')
504
- );
505
-
506
- // TODO: not sure if this should remove multipel at end or just one (as it does now)?
507
- const removeTrailingSlash = (string) => (
508
- ensureString(string).replace(/\/$/, '')
509
- );
510
-
511
- const removeLeadingTrailingSlash = (string) => (
512
- removeTrailingSlash(removeLeadingSlash(string))
513
- );
514
-
515
- const ensureLeadingSlash = (string) => (
516
- `/${removeLeadingSlash(string)}`
517
- );
518
-
519
- const ensureTrailingSlash = (string) => (
520
- `${removeTrailingSlash(string)}/`
521
- );
522
-
523
- const ensureExtension = (string, extension) => {
524
- if (stringEmpty(extension)) {
525
- return string
526
- }
527
-
528
- const filePath = string.match(/(.*)\..+$/)?.[1] ?? string;
529
-
530
- return `${filePath}.${extension}`
531
- };
532
-
533
- const wrappedEncode = (basePath, _wrappingPath) => {
534
- const wrappingPath = ensureString(_wrappingPath);
535
- if (stringEmpty(basePath)) {
536
- return wrappingPath
537
- }
538
-
539
- return `${basePath}${encodeURIComponent(wrappingPath)}`
540
- };
541
-
542
- const splitCommas = (value) => (
543
- ensureString(value)
544
- .replace(/, /g, ',')
545
- .split(',')
546
- .reduce((_values, _value) => {
547
- const __value = safeTrim(_value);
548
- return [
549
- ..._values,
550
- ...stringNotEmpty(__value) ? [__value] : [] // TODO: explore stringEmptyOnly?
551
- ]
552
- }, [])
553
- );
554
-
555
- const combineCommas = (values) => (
556
- [...new Set(
557
- ensureArray(values).reduce((
558
- _combined,
559
- value
560
- ) => ([
561
- ..._combined,
562
- ...splitCommas(value)
563
- ]), [])
564
- )]
565
- );
566
-
567
- const normalizeArray = (input, separator = ' ') => {
568
- const inputArray = ensureArray(input);
569
-
570
- return inputArray.reduce((normalized, _ignore, index) => ([
571
- ...normalized,
572
- inputArray.slice(0, index + 1).join(separator)
573
- ]), []).reverse()
574
- };
575
-
576
- const arrayToObject = (arrayable, key) => (
577
- ensureArray(arrayable).reduce((_object, item) => ({
578
- ..._object,
579
- [item?.[key]]: item,
580
- }), {})
581
- );
582
-
583
- const isTrue = (value, falsy) => {
584
- const string = ensureString(value);
585
- return ![...ensureArray(falsy), '', 'false'].includes(string)
586
- };
587
-
588
- // TODO: move all uses of parseKeywords to parseKeywordGroups
589
- const parseKeywords = (keywordGroup, previous) => (
590
- [...new Set(ensureArray(keywordGroup).reduce((_parsedKeywords, keyword) => {
591
- const keywords = splitCommas(keyword);
592
-
593
- return [
594
- ..._parsedKeywords,
595
- ...keywords.filter((_keyword) => stringNotEmpty(_keyword))
596
- ]
597
- }, ensureArray(previous)))].join(', ')
598
- );
599
-
600
- const parseKeywordGroups = (keywordGroups) => {
601
- const parsedKeywords = ensureArray(keywordGroups).reduce((_parsedKeywords, keywordGroup) => ([
602
- ..._parsedKeywords,
603
- ...ensureArray(keywordGroup).reduce((_parsedGroupKeywords, keyword) => {
604
- const keywords = splitCommas(keyword);
605
-
606
- return [
607
- ..._parsedGroupKeywords,
608
- ...keywords.filter((_keyword) => stringNotEmpty(_keyword))
609
- ]
610
- }, [])
611
- ]), []);
612
-
613
- return [...new Set(parsedKeywords)].join(', ')
614
- };
615
-
616
- const parseProjectName = (_name) => {
617
- const name = ensureString(_name);
618
- const [, organization, ...parts] = /(@.*)([\\]+|\/)+(.*)/.exec(name) ?? [];
619
- const projectName = organization ? name.replace(organization, '').replace('/', '') : name;
620
-
621
- return {
622
- name,
623
- parts,
624
- organization,
625
- projectName,
626
- }
627
- };
628
-
629
- const parseProject = (packageInfo) => {
630
- const { shortName, owner: _appOwner, name: _name, appName: _appName, keywords, siteName: _siteName } = packageInfo ?? {};
631
- const { name, organization, projectName } = parseProjectName(_name);
632
-
633
- const appName = _appName ?? projectName?.slice(projectName?.indexOf('-') + 1);
634
- const siteName = _siteName ?? projectName?.slice(projectName?.indexOf('-') + 1);
635
- const companyName = titleCase(organization?.replace('@', ''));
636
- const appOwner = _appOwner ?? companyName;
637
-
638
- return [{
639
- keywords,
640
- appOwner,
641
- appName,
642
- siteName,
643
- shortName,
644
- companyName,
645
- projectName,
646
- }, organization, name]
647
- };
648
-
649
- // #endregion lib-core
650
-
651
- // #region lib-utils-js
652
-
653
- const querystringToObject = (queryString) => {
654
- if (stringEmpty(queryString)) {
655
- return {}
656
- }
657
-
658
- const urlParams = new URLSearchParams(queryString);
659
- return Object.fromEntries(urlParams)
660
- };
661
-
662
- const arrayIncludesAll = (_values, comparison) => {
663
- const values = ensureArray(_values);
664
- return ensureArray(comparison).every((value) => (
665
- stringNotEmpty(value) && values.includes(value)
666
- ))
667
- };
668
-
669
- const arraysMatch = (array1, array2) => (
670
- arrayIncludesAll(array1, array2) && arrayIncludesAll(array2, array1)
671
- );
672
-
673
- const appendQuery = (_basePath, queryString) => {
674
- const basePath = ensureString(_basePath);
675
- if (stringEmpty(queryString)) {
676
- return basePath
677
- }
678
- return `${basePath}${basePath.includes('?') ? '&' : '?'}${queryString}`
679
- };
680
-
681
- /* eslint-disable no-use-before-define */
682
-
683
- // TODO: not sure if needed?
684
- // TODO!!!: keep in sync between verifyd and lib-core
685
- // TODO: consolidate these to export as COMMON_FORMATS
686
- const TIME_FORMAT_LONG = 'HH:mm';
687
- const TIME_FORMAT_AMPM = 'h:mm a';
688
-
689
- const DATE_FORMAT_MED = 'M/d/yyyy';
690
- const DATE_FORMAT_LONG = 'MM/dd/yyyy';
691
- const DATE_FORMAT_TRACKING = 'MMddyyyy';
692
- const DATE_FORMAT_ORGANIZED = 'yyyy-MM-dd';
693
-
694
- const {
695
- DATE_SHORT,
696
- DATE_MED,
697
- DATE_FULL,
698
- DATE_HUGE,
699
- DATETIME_SHORT,
700
- DATETIME_MED,
701
- DATETIME_MED_WITH_SECONDS,
702
- DATETIME_FULL,
703
- DATETIME_HUGE,
704
- TIME_SIMPLE,
705
- } = luxon.DateTime;
706
-
707
- const DATE_MINI = {
708
- ...DATE_SHORT,
709
- year: '2-digit',
710
- };
711
-
712
- const DATETIME_MINI = {
713
- ...DATETIME_SHORT,
714
- year: '2-digit',
715
- };
716
-
717
- const {
718
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
719
- timeZoneName: _timeZoneNameF,
720
- ...DATETIME_FULL_NO_ZONE
721
- } = DATETIME_FULL;
722
-
723
- const {
724
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
725
- timeZoneName: _timeZoneNameH,
726
- ...DATETIME_HUGE_NO_ZONE
727
- } = DATETIME_HUGE;
728
-
729
- const CUSTOM_FORMATS = {
730
- UTC: 'UTC',
731
- ISO: 'ISO',
732
- UNIX: 'UNIX',
733
- };
734
-
735
- const LOCALE_FORMATS = {
736
- DATE_MINI,
737
- DATE_SHORT,
738
- DATE_MED,
739
- DATE_FULL,
740
- DATE_HUGE,
741
- DATETIME_MINI,
742
- DATETIME_SHORT,
743
- DATETIME_MED,
744
- DATETIME_MED_WITH_SECONDS,
745
- DATETIME_FULL_NO_ZONE,
746
- DATETIME_HUGE_NO_ZONE,
747
- TIME_SIMPLE,
748
- };
749
-
750
- const DATE_PARTS = {
751
- day: 'dd',
752
- month: 'MM',
753
- year: 'yyyy',
754
- };
755
-
756
- const validLocaleFormats = Object.values(LOCALE_FORMATS);
757
-
758
- const hasSomeNumbers = (value) => /\d/.test(value);
759
- const hasOnlyValidCharacters = (value) => /^[\w-+:,\\/\s.]+$/.test(value); // TODO: confirm if these are all needed?
760
-
761
- const getDaysInMonth = (year, month) => (
762
- new Date(year, month, 0).getDate()
763
- );
764
-
765
- const setDateTimeLocale = (locale) => {
766
- if (stringNotEmpty(locale)) {
767
- luxon.Settings.defaultLocale = locale;
768
- }
769
- };
770
-
771
- const setDateTimeZone = (zone) => {
772
- if (stringNotEmpty(zone)) {
773
- luxon.Settings.defaultZone = zone;
774
- }
775
- };
776
-
777
- const getDateTimeFormat = ({ locale, mappings = DATE_PARTS } = {}) => {
778
- const localeParts = getCurrentDateTime().toLocaleParts({ locale });
779
-
780
- return localeParts.map(({ type, value }) => {
781
- if (type === 'literal') {
782
- return value
783
- }
784
-
785
- return mappings[type]
786
- }).filter(stringNotEmpty).join('')
787
- };
788
-
789
- const getDateTimeFromString = ({ format, value, ...options } = {}) => {
790
- if (
791
- !hasSomeNumbers(value) ||
792
- !hasOnlyValidCharacters(value) ||
793
- (isType(value, 'string') && stringEmpty(value))
794
- ) {
795
- return undefined
796
- }
797
-
798
- if (stringNotEmpty(format)) {
799
- return luxon.DateTime.fromFormat(value, format, options)
800
- }
801
-
802
- if (isNumericNegatable(value)) {
803
- return luxon.DateTime.fromSeconds(ensureNumericNegatable(value))
804
- }
805
-
806
- let dateTime = luxon.DateTime.fromISO(value, options);
807
-
808
- if (!dateTime?.isValid) {
809
- dateTime = luxon.DateTime.fromSQL(value, options);
810
- }
811
-
812
- if (!dateTime?.isValid) {
813
- dateTime = luxon.DateTime.fromRFC2822(value, options);
814
- }
815
-
816
- return dateTime
817
- };
818
-
819
- const convertDateTime = (value, { shouldThrow = true, shouldLog = shouldThrow, ...options } = {}) => {
820
- let dateTime;
821
- try {
822
- switch (true) {
823
- // eslint-disable-next-line eqeqeq
824
- case (value == undefined): {
825
- break
826
- }
827
-
828
- case luxon.DateTime.isDateTime(value): {
829
- dateTime = value;
830
- break
831
- }
832
-
833
- case (value instanceof Date): {
834
- dateTime = luxon.DateTime.fromJSDate(value, options);
835
- break
836
- }
837
-
838
- case isType(value, 'object'): {
839
- dateTime = luxon.DateTime.fromObject(value, options);
840
- break
841
- }
842
-
843
- default: {
844
- dateTime = getDateTimeFromString({ value, ...options });
845
- break
846
- }
847
- }
848
-
849
- if (dateTime && !dateTime.isValid) {
850
- if (shouldLog) {
851
- console.info('Issue converting timestamp', { value, dateTime });
852
- }
853
- throw new Error(['Error converting timestamp', dateTime?.invalidExplanation].filter(stringNotEmpty).join('. '))
854
- }
855
- } catch (error) {
856
- if (shouldThrow) {
857
- throw error
858
- }
859
- dateTime = undefined;
860
- }
861
-
862
- return dateTime
863
- };
864
-
865
- const convertDuration = (value, { shouldThrow = true, ...options } = {}) => {
866
- let duration;
867
- try {
868
- switch (true) {
869
- // eslint-disable-next-line eqeqeq
870
- case (value == undefined): {
871
- break
872
- }
873
-
874
- case luxon.Duration.isDuration(value): {
875
- duration = value;
876
- break
877
- }
878
-
879
- case isType(value, 'object'): {
880
- duration = luxon.Duration.fromObject(value, options);
881
- break
882
- }
883
-
884
- case isNumericNegatable(value): {
885
- duration = luxon.Duration.fromMillis(value, options);
886
- break
887
- }
888
-
889
- case (
890
- isType(value, 'string') &&
891
- stringNotEmpty(value) &&
892
- hasSomeNumbers(value) &&
893
- hasOnlyValidCharacters(value)
894
- ): {
895
- duration = luxon.Duration.fromISOTime(value, options);
896
-
897
- if (!duration?.isValid) {
898
- duration = luxon.Duration.fromISO(value, options);
899
- }
900
-
901
- break
902
- }
903
-
904
- default: {
905
- break
906
- }
907
- }
908
-
909
- if (duration && !duration.isValid) {
910
- console.error('Error converting duration', { value, duration });
911
- throw new Error(['Error converting duration', duration?.invalidExplanation].filter(stringNotEmpty).join('. '))
912
- }
913
- } catch (error) {
914
- if (shouldThrow) {
915
- throw error
916
- }
917
- duration = undefined;
918
- }
919
-
920
- return duration
921
- };
922
-
923
- const getDuration = (dt1, dt2) => (
924
- luxon.Interval.fromDateTimes(convertDateTime(dt1), convertDateTime(dt2)).toDuration()
925
- );
926
-
927
- const getCurrentDateTime = () => (
928
- luxon.DateTime.now()
929
- );
930
-
931
- const getDateTimeDifference = (dt1, dt2 = getCurrentDateTime(), unit) => (
932
- dt2.diff(ensureDateTime(dt1), unit)?.[unit]
933
- );
934
-
935
- const getDaysBetween = (dt1, dt2) => (
936
- Math.abs(getDateTimeDifference(dt1, dt2, 'days'))
937
- );
938
-
939
- const getWeeksBetween = (dt1, dt2) => (
940
- Math.abs(getDateTimeDifference(dt1, dt2, 'weeks'))
941
- );
942
-
943
- const isSameDateTime = (dt1, dt2 = getCurrentDateTime(), unit) => (
944
- // eslint-disable-next-line eqeqeq
945
- dt1.startOf(unit).ts == dt2.startOf(unit).ts
946
- );
947
-
948
- const isSameHour = (dt1, dt2) => (
949
- isSameDateTime(dt1, dt2, 'hour')
950
- );
951
-
952
- const isSameDay = (dt1, dt2) => (
953
- isSameDateTime(dt1, dt2, 'day')
954
- );
955
-
956
- const ensureDateTime = (value, options) => (
957
- convertDateTime(value, options) ?? getCurrentDateTime()
958
- );
959
-
960
- const formatTimestamps = (timestamps, defaultValue) => (
961
- Object.entries(ensureObject(timestamps)).reduce((_timestamps, [name, value]) => ({
962
- ..._timestamps,
963
- [name]: value ? toUnixString(value) : defaultValue,
964
- }), {})
965
- );
966
-
967
- const formatRequiredTimestamps = ({ created_at, updated_at, ...timestamps } = {}) => {
968
- const _createdTimestamp = toUnixString(created_at);
969
- const updatedTimestamp = updated_at ? toUnixString(updated_at) : _createdTimestamp;
970
- const createdTimestamp = `${Math.min(_createdTimestamp, updatedTimestamp)}`; // HMMM: why doing math on strings??
971
-
972
- const otherTimestamps = formatTimestamps(timestamps, createdTimestamp);
973
- return { created_at: createdTimestamp, updated_at: updatedTimestamp, ...otherTimestamps }
974
- };
975
-
976
- const toJSDate = (value, options) => (
977
- ensureDateTime(value, options).toJSDate()
978
- );
979
-
980
- const toUnixInteger = (value, options) => (
981
- ensureDateTime(value, options).toUnixInteger()
982
- );
983
-
984
- // consolidate w/ toFormatted({ value, CUSTOM_FORMATS.UNIX })
985
- const toUnixString = (value, options) => (
986
- `${toUnixInteger(convertDateTime(value, options), options)}`
987
- );
988
-
989
- // consolidate w/ toFormatted({ value, format: CUSTOM_FORMATS.ISO })
990
- const toISOString = (value, options) => (
991
- ensureDateTime(value, options).toISO()
992
- );
993
-
994
- // consolidate w/ toFormatted({ value, format: CUSTOM_FORMATS.UTC })
995
- const toUTCString = (value, options) => (
996
- ensureDateTime(value, options).toUTC().toString()
997
- );
998
-
999
- // HMMM: return toString if no format (or similar)??
1000
- const toFormatted = ({ format, value, zone, ...options } = {}) => {
1001
- // eslint-disable-next-line eqeqeq
1002
- if (format == undefined) {
1003
- throw new Error('Please supply a format')
1004
- }
1005
-
1006
- let datetime = ensureDateTime(value, options);
1007
-
1008
- if (stringNotEmpty(zone)) {
1009
- datetime = datetime.setZone(zone);
1010
- }
1011
-
1012
- switch (true) {
1013
- case (format === CUSTOM_FORMATS.UTC): {
1014
- return datetime.toUTC().toString()
1015
- }
1016
-
1017
- case (format === CUSTOM_FORMATS.ISO): {
1018
- return datetime.toISO()
1019
- }
1020
-
1021
- case (format === CUSTOM_FORMATS.UNIX): {
1022
- return `${toUnixInteger(datetime)}`
1023
- }
1024
-
1025
- case (isType(format, 'string')): {
1026
- return datetime.toFormat(format, options)
1027
- }
1028
-
1029
- default: {
1030
- if (!validLocaleFormats.includes(format)) {
1031
- console.warn('Please consider using a format from LOCALE_FORMATS');
1032
- }
1033
-
1034
- return datetime.toLocaleString(format, options)
1035
- }
1036
- }
1037
- };
1038
-
1039
- const convertFormatted = ({
1040
- value,
1041
- to: { format: toFormat, ...toOptions } = {},
1042
- from: { format: fromFormat, ...fromOptions } = {},
1043
- } = {}) => {
1044
- if (stringEmpty(toFormat) || stringEmpty(fromFormat)) {
1045
- throw new Error('Please supply to/from formats')
1046
- }
1047
-
1048
- if (stringEmpty(value)) {
1049
- return undefined
1050
- }
1051
-
1052
- return toFormatted({
1053
- format: toFormat,
1054
- value: getDateTimeFromString({ value, format: fromFormat, ...fromOptions }),
1055
- ...toOptions,
1056
- })
1057
- };
1058
-
1059
- const compareDate = (date1, date2) => (
1060
- -convertDateTime(date1).diff(convertDateTime(date2)).as('milliseconds')
1061
- );
1062
-
1063
- const TEMP_HOSTNAME = 'xyz.com';
1064
- const REMOVABLE_KEYS = ['shop', 'host'];
1065
-
1066
- const HEADER_CONTENT_TYPE = 'Content-Type';
1067
- const CONTENT_TYPES = {
1068
- APPLICATION_JSON: 'application/json',
1069
- };
1070
-
1071
- const getRequestMethod = (body, _method) => {
1072
- const method = _method ?? (body ? 'POST' : 'GET');
1073
-
1074
- return uppercase(method)
1075
- };
1076
-
1077
- const prepareRequestOptions = ({ method: _method, body: _body, headers: _header, ...requestOptions } = {}) => {
1078
- const method = getRequestMethod(_body, _method);
1079
-
1080
- const headers = {
1081
- [HEADER_CONTENT_TYPE]: CONTENT_TYPES.APPLICATION_JSON,
1082
- ..._header,
1083
- };
1084
-
1085
- let body = _body;
1086
- if (body && headers[HEADER_CONTENT_TYPE].startsWith(CONTENT_TYPES.APPLICATION_JSON)) {
1087
- body = JSON.stringify(safeParse(body));
1088
- }
1089
-
1090
- return {
1091
- method,
1092
- headers,
1093
- ...(body && (method !== 'GET')) && { body },
1094
- ...requestOptions,
1095
- }
1096
- };
1097
-
1098
- const convertToURL = (uri) => {
1099
- if (stringEmpty(uri)) {
1100
- return undefined
1101
- }
1102
-
1103
- const protocolRegex = /^(https?:\/\/).+/i;
1104
-
1105
- if (!protocolRegex.test(uri)) {
1106
- return new URL(`https://${TEMP_HOSTNAME}${ensureLeadingSlash(uri)}`)
1107
- }
1108
-
1109
- return new URL(uri)
1110
- };
1111
-
1112
- const normalizeURIParts = (uri) => {
1113
- const urlObject = convertToURL(uri);
1114
- if (!urlObject) {
1115
- return undefined
1116
- }
1117
-
1118
- REMOVABLE_KEYS.forEach((key) => urlObject.searchParams.delete(key));
1119
-
1120
- return {
1121
- ...(urlObject.hostname !== TEMP_HOSTNAME) && {
1122
- origin: urlObject.origin,
1123
- },
1124
- pathname: urlObject.pathname,
1125
- search: urlObject.search,
1126
- }
1127
- };
1128
-
1129
- var n,r=((n=function(){return r}).toString=n.toLocaleString=n[Symbol.toPrimitive]=function(){return ""},n.valueOf=function(){return !1},new Proxy(Object.freeze(n),{get:function(n,t){return n.hasOwnProperty(t)?n[t]:r}})),u=function(n){return n===r};
1130
-
1131
- let win = global.window;
1132
- let exists = (variable) => !u(variable);
1133
- let window = typeof win !== "undefined" ? win : r;
1134
-
1135
- class WebCrypto {
1136
- constructor({ crypto: _crypto, util: _util } = {}) {
1137
- this._crypto = _crypto ?? {};
1138
- this._util = _util ?? {};
1139
- }
1140
-
1141
- get subtle() {
1142
- return this._crypto?.subtle
1143
- }
1144
-
1145
- async getWebCrypto() {
1146
- if (!this._crypto?.subtle) {
1147
- try {
1148
- this._crypto = (await import('isomorphic-webcrypto')).default;
1149
- } catch (_ignore) {
1150
- console.info('Failed to import isomorphic-webcrypto, retrying w/ node crypto');
1151
- try {
1152
- this._crypto = (await import('crypto')).default;
1153
- } catch (error) {
1154
- // eslint-disable-next-line max-len
1155
- console.error(`Failed to import node crypto, ensure 'isomorphic-webcrypto' (or node 'crypto') is installed and/or pass in implementation via 'new WebCrypto({ crypto })'`);
1156
- throw error
1157
- }
1158
- }
1159
- }
1160
-
1161
- if (!this._crypto?.subtle) {
1162
- throw new Error('Invalid crypto, missing subtle')
1163
- }
1164
-
1165
- return this._crypto
1166
- }
1167
-
1168
- async getTextDecoder() {
1169
- if (this._util?.TextDecoder) {
1170
- return this._util.TextDecoder
1171
- }
1172
-
1173
- if (exists(window) && typeof window?.TextDecoder === 'function') {
1174
- return window.TextDecoder
1175
- }
1176
-
1177
- try {
1178
- const TextDecoder = (await import('util')).TextDecoder;
1179
- this._util.TextDecoder = TextDecoder;
1180
-
1181
- return TextDecoder
1182
- } catch (error) {
1183
- console.error(`Failed to import 'utils.TextDecoder', ensure 'util' is available and/or pass in implementation via 'new WebCrypto({ util })'`);
1184
- throw error
1185
- }
1186
- }
1187
-
1188
- async getTextEncoder() {
1189
- if (this._util?.TextEncoder) {
1190
- return this._util.TextEncoder
1191
- }
1192
-
1193
- if (exists(window) && typeof window?.TextEncoder === 'function') {
1194
- return window.TextEncoder
1195
- }
1196
-
1197
- try {
1198
- const TextEncoder = (await import('util')).TextEncoder;
1199
- this._util.TextEncoder = TextEncoder;
1200
-
1201
- return TextEncoder
1202
- } catch (error) {
1203
- console.error(`Failed to import 'utils.TextEncoder', ensure 'util' is available and/or pass in implementation via 'new WebCrypto({ util })'`);
1204
- throw error
1205
- }
1206
- }
1207
-
1208
- timingSafeEqual(value1, value2) {
1209
- if (
1210
- (value1 == undefined) ||
1211
- (value2 == undefined) ||
1212
- (value1.length !== value2.length)
1213
- ) {
1214
- return false
1215
- }
1216
-
1217
- let result = 0;
1218
- // eslint-disable-next-line no-plusplus
1219
- for (let i = 0; i < value1.length; i++) {
1220
- // eslint-disable-next-line no-bitwise
1221
- result |= value1[i] ^ value2[i];
1222
- }
1223
-
1224
- return (result === 0)
1225
- }
1226
-
1227
- stringToHex(stringValue) {
1228
- return (
1229
- Array.from(ensureString(stringValue), (char) => (
1230
- char.charCodeAt(0).toString(16).padStart(2, '0')
1231
- )).join('')
1232
- )
1233
- }
1234
-
1235
- hexToString(hexValue) {
1236
- return (
1237
- ensureArray(
1238
- ensureString(hexValue).match(/.{1,2}/g)
1239
- )
1240
- .map((byte) => String.fromCharCode(parseInt(byte, 16)))
1241
- .join('')
1242
- )
1243
- }
1244
-
1245
- async arrayBufferToString(arrayBuffer) {
1246
- const uint8Array = new Uint8Array(arrayBuffer);
1247
- const Decoder = await this.getTextDecoder();
1248
- return new Decoder().decode(uint8Array)
1249
- }
1250
-
1251
- arrayToArrayBuffer(array) {
1252
- return (
1253
- (ArrayBuffer.from != undefined)
1254
- ? ArrayBuffer.from(array)
1255
- : new Uint8Array(array).buffer
1256
- )
1257
- }
1258
-
1259
- ensureArrayBuffer(arrayOrArrayBuffer) {
1260
- return (
1261
- (arrayOrArrayBuffer instanceof ArrayBuffer)
1262
- ? arrayOrArrayBuffer
1263
- : this.arrayToArrayBuffer(arrayOrArrayBuffer)
1264
- )
1265
- }
1266
-
1267
- async generateKeyPair() {
1268
- const crypto = await this.getWebCrypto();
1269
- const keyPair = await crypto.subtle.generateKey(
1270
- {
1271
- name: 'ECDH',
1272
- namedCurve: 'P-256',
1273
- },
1274
- true,
1275
- ['deriveKey']
1276
- );
1277
-
1278
- return keyPair
1279
- }
1280
-
1281
- getKeyOperations(keyType) {
1282
- switch (keyType) {
1283
- case 'private':
1284
- case 'privateKey': {
1285
- return ['deriveKey']
1286
- }
1287
-
1288
- case 'secret':
1289
- case 'secretKey':
1290
- case 'sharedSecret': {
1291
- return ['encrypt', 'decrypt']
1292
- }
1293
-
1294
- default: {
1295
- return []
1296
- }
1297
- }
1298
- }
1299
-
1300
- getKeyAlgorythm(keyType) {
1301
- switch (keyType) {
1302
- case 'derivedKey':
1303
- case 'derived':
1304
- case 'secret':
1305
- case 'secretKey':
1306
- case 'sharedSecret': {
1307
- return {
1308
- name: 'AES-GCM',
1309
- }
1310
- }
1311
-
1312
- default: {
1313
- return {
1314
- name: 'ECDH',
1315
- namedCurve: 'P-256',
1316
- }
1317
- }
1318
- }
1319
- }
1320
-
1321
- async generateHMAC(message, derivedKey) {
1322
- if (!message || !derivedKey) {
1323
- return undefined
1324
- }
1325
-
1326
- const crypto = await this.getWebCrypto();
1327
- const Encoder = await this.getTextEncoder();
1328
-
1329
- const signature = await crypto.subtle.sign(
1330
- 'HMAC',
1331
- derivedKey,
1332
- new Encoder().encode(message)
1333
- );
1334
-
1335
- return this.stringToHex(
1336
- this.arrayBufferToString(signature)
1337
- )
1338
- }
1339
-
1340
- async verifyHMAC(message, derivedKey, verifiableHMAC) {
1341
- const calculatedHMAC = await this.generateHMAC(message, derivedKey);
1342
-
1343
- return this.timingSafeEqual(calculatedHMAC, verifiableHMAC)
1344
- }
1345
-
1346
- async getStorableKey(key) {
1347
- const crypto = await this.getWebCrypto();
1348
-
1349
- const exportedJWK = await crypto.subtle.exportKey('jwk', key);
1350
- return this.stringToHex(JSON.stringify(exportedJWK))
1351
- }
1352
-
1353
- async restoreStorableKey(keyType, storableHex) {
1354
- if (!storableHex) {
1355
- return undefined
1356
- }
1357
- const crypto = await this.getWebCrypto();
1358
-
1359
- const exportedJWK = JSON.parse(this.hexToString(storableHex) || '{}');
1360
- if (objectEmpty(exportedJWK)) {
1361
- return undefined
1362
- }
1363
-
1364
- return crypto.subtle.importKey(
1365
- 'jwk',
1366
- exportedJWK,
1367
- this.getKeyAlgorythm(keyType),
1368
- true,
1369
- this.getKeyOperations(keyType)
1370
- )
1371
- }
1372
-
1373
- async getStorableKeyPair(keyPair) {
1374
- const storableKeys = {};
1375
-
1376
- // eslint-disable-next-line no-restricted-syntax
1377
- for (const [keyType, key] of Object.entries(keyPair)) {
1378
- // eslint-disable-next-line no-await-in-loop
1379
- storableKeys[keyType] = await this.getStorableKey(key);
1380
- }
1381
-
1382
- return storableKeys
1383
- }
1384
-
1385
- async restoreStorableKeyPair(keyPair) {
1386
- const restoredKeys = {};
1387
-
1388
- // eslint-disable-next-line no-restricted-syntax
1389
- for (const [keyType, key] of Object.entries(keyPair)) {
1390
- // eslint-disable-next-line no-await-in-loop
1391
- restoredKeys[keyType] = await this.restoreStorableKey(keyType, key);
1392
- }
1393
-
1394
- return restoredKeys
1395
- }
1396
-
1397
- async deriveSharedKey({ publicKey, privateKey }) {
1398
- if (!publicKey || !privateKey) {
1399
- return undefined
1400
- }
1401
-
1402
- const crypto = await this.getWebCrypto();
1403
- const derivedKey = await crypto.subtle.deriveKey(
1404
- {
1405
- name: 'ECDH',
1406
- public: publicKey,
1407
- },
1408
- privateKey,
1409
- {
1410
- name: 'AES-GCM',
1411
- length: 256,
1412
- },
1413
- true,
1414
- ['encrypt', 'decrypt']
1415
- );
1416
- return derivedKey
1417
- }
1418
-
1419
- async deriveHMACKey({ publicKey, privateKey }) {
1420
- if (!publicKey || !privateKey) {
1421
- return undefined
1422
- }
1423
-
1424
- const crypto = await this.getWebCrypto();
1425
- const derivedKey = await crypto.subtle.deriveKey(
1426
- {
1427
- name: 'ECDH',
1428
- public: publicKey,
1429
- },
1430
- privateKey,
1431
- {
1432
- name: 'HMAC',
1433
- hash: { name: 'SHA-256' },
1434
- length: 256, // Adjusted key length, e.g., 128 bits
1435
- },
1436
- true,
1437
- ['sign', 'verify']
1438
- );
1439
- return derivedKey
1440
- }
1441
-
1442
- async getVerificationKeys({ publicKey, privateKey }) {
1443
- if (!publicKey || !privateKey) {
1444
- return {}
1445
- }
1446
-
1447
- const sharedKeyPair = await this.restoreStorableKeyPair({ publicKey, privateKey });
1448
- const derivedHMACKey = await this.deriveHMACKey(sharedKeyPair);
1449
- const derivedSecretKey = await this.deriveSharedKey(sharedKeyPair);
1450
-
1451
- return {
1452
- derivedSecretKey,
1453
- derivedHMACKey,
1454
- }
1455
- }
1456
-
1457
- async encryptMessage(decryptedMessage, derivedKey) {
1458
- if (!decryptedMessage || !derivedKey) {
1459
- return undefined
1460
- }
1461
-
1462
- const crypto = await this.getWebCrypto();
1463
- const iv = crypto.getRandomValues(new Uint8Array(12));
1464
- const Encoder = await this.getTextEncoder();
1465
- const encodedMessage = new Encoder().encode(decryptedMessage);
1466
- const ciphertext = await crypto.subtle.encrypt(
1467
- {
1468
- name: 'AES-GCM',
1469
- iv,
1470
- },
1471
- derivedKey,
1472
- encodedMessage
1473
- );
1474
-
1475
- const encryptedMessage = new Uint8Array([
1476
- ...iv,
1477
- ...new Uint8Array(ciphertext)
1478
- ]);
1479
- return Array.from(encryptedMessage)
1480
- }
1481
-
1482
- async decryptMessage(encryptedMessage, derivedKey) {
1483
- if (!encryptedMessage || !derivedKey) {
1484
- return undefined
1485
- }
1486
-
1487
- const crypto = await this.getWebCrypto();
1488
- // NOTE: this presumed an array or arrayBuffer coming in as encryptedMessage (will fail w/ IV error if its a string)
1489
- const encryptedArrayBuffer = this.ensureArrayBuffer(encryptedMessage);
1490
- const iv = encryptedArrayBuffer.slice(0, 12);
1491
- const ciphertext = encryptedArrayBuffer.slice(12);
1492
-
1493
- const decryptedArrayBuffer = await crypto.subtle.decrypt(
1494
- {
1495
- name: 'AES-GCM',
1496
- iv,
1497
- },
1498
- derivedKey,
1499
- ciphertext
1500
- );
1501
-
1502
- const Decoder = await this.getTextDecoder();
1503
- const decryptedMessage = new Decoder().decode(decryptedArrayBuffer);
1504
- return decryptedMessage
1505
- }
1506
- }
1507
-
1508
- const getKeysData = async (publicKey, { crypto: _crypto, util: _util } = {}) => {
1509
- const webCrypto = new WebCrypto({ crypto: _crypto, util: _util });
1510
-
1511
- const _ephemeralStoreableKeyPair = await webCrypto.getStorableKeyPair(
1512
- await webCrypto.generateKeyPair(publicKey)
1513
- );
1514
-
1515
- const _verificationKeyPair = await webCrypto.getVerificationKeys({
1516
- publicKey,
1517
- privateKey: _ephemeralStoreableKeyPair.privateKey,
1518
- });
1519
-
1520
- return {
1521
- publicKey,
1522
- ephemeral: _ephemeralStoreableKeyPair,
1523
- verification: _verificationKeyPair,
1524
- }
1525
- };
1526
-
1527
- // eslint-disable-next-line arrow-body-style
1528
- const prepareVerificationRequest = ({ keysData: _keysData, disableRecryption: _disableRecryption, crypto: _crypto, util: _util } = {}) => {
1529
- const webCrypto = new WebCrypto({ crypto: _crypto, util: _util });
1530
- const disableRecryption = isTrue(_disableRecryption);
1531
-
1532
- return async (requestPath, { method: rawMethod, body: rawBody, headers: rawHeaders, ...requestOptions } = {}) => {
1533
- let parsedBody = safeParse(rawBody);
1534
- const method = getRequestMethod(parsedBody, rawMethod);
1535
-
1536
- if (disableRecryption || stringEmpty(_keysData?.publicKey)) {
1537
- return [
1538
- requestPath,
1539
- prepareRequestOptions({
1540
- method,
1541
- body: parsedBody,
1542
- headers: rawHeaders,
1543
- ...requestOptions,
1544
- })
1545
- ]
1546
- }
1547
-
1548
- const {
1549
- verification: {
1550
- derivedHMACKey,
1551
- derivedSecretKey,
1552
- } = {},
1553
- ephemeral: {
1554
- publicKey: ephemeralPublicKey,
1555
- } = {},
1556
- } = _keysData ?? {};
1557
-
1558
- if (!derivedHMACKey || !ephemeralPublicKey) {
1559
- return undefined
1560
- }
1561
-
1562
- if (parsedBody && derivedSecretKey) {
1563
- parsedBody = await webCrypto.encryptMessage(JSON.stringify(parsedBody), derivedSecretKey);
1564
- }
1565
-
1566
- const timestamp = toUnixString();
1567
- const computedHMAC = await webCrypto.generateHMAC(
1568
- objectToSortedString({
1569
- body: parsedBody,
1570
- method,
1571
- timestamp,
1572
- ...normalizeURIParts(requestPath),
1573
- }),
1574
- derivedHMACKey
1575
- );
1576
-
1577
- return [
1578
- requestPath,
1579
- prepareRequestOptions({
1580
- method,
1581
- body: parsedBody,
1582
- headers: {
1583
- 'X-Authorization': `HMAC-SHA256 ${computedHMAC}`,
1584
- 'X-Authorization-Timestamp': timestamp,
1585
- 'X-Ephemeral-Key': ephemeralPublicKey,
1586
- 'X-Public-Key': _keysData.publicKey,
1587
- ...rawHeaders,
1588
- },
1589
- ...requestOptions,
1590
- }),
1591
- derivedSecretKey
1592
- ]
1593
- }
1594
- };
1595
-
1596
- const processVerificationResponse = ({ keysData, disableRecryption: _disableRecryption, crypto: _crypto, util: _util } = {}) => {
1597
- const webCrypto = new WebCrypto({ crypto: _crypto, util: _util });
1598
- const disableRecryption = isTrue(_disableRecryption);
1599
-
1600
- return async (encryptedResponse, _derivedSecretKey) => {
1601
- const derivedSecretKey = _derivedSecretKey ?? keysData.verification?.derivedSecretKey;
1602
- if (disableRecryption || !encryptedResponse || !derivedSecretKey) {
1603
- return encryptedResponse
1604
- }
1605
-
1606
- const decryptedMessage = await webCrypto.decryptMessage(encryptedResponse, derivedSecretKey);
1607
- return safeParse(decryptedMessage)
1608
- }
1609
- };
1610
-
1611
- class VerificationError extends Error {
1612
- constructor(message, _data) {
1613
- super(message);
1614
- const { code = 500, ...data } = _data ?? {};
1615
- this.code = code;
1616
- this.data = data;
1617
- this.name = 'VerificationError';
1618
- Object.setPrototypeOf(this, VerificationError.prototype);
1619
- }
1620
- }
1621
-
1622
- const getChunkedRawBody = async (readable) => {
1623
- if (readable?.rawBody) {
1624
- return readable.rawBody
1625
- }
1626
-
1627
- // TODO: move to req.text() after next 13.5: https://github.com/vercel/next.js/discussions/13405
1628
- try {
1629
- const getRawBody = (await Promise.resolve().then(function () { return index; })).default;
1630
- return getRawBody(readable)
1631
- } catch (error) {
1632
- console.error(`Failed to import 'raw-body', please ensure the dependency is installed`);
1633
- throw error
1634
- }
1635
- };
1636
-
1637
- // TODO: explore returning mutated request object adding on req.rawBody??
1638
- const ensureRawBody = async (req) => (
1639
- getChunkedRawBody(req)
1640
- .then((_rawBody) => _rawBody.toString())
1641
- .catch((error) => {
1642
- console.error(`Error getting raw body for '${req?.url}'`, error);
1643
- throw error
1644
- })
1645
- );
1646
-
1647
- const getVerificationHelpers = ({ keyPairs, crypto: _crypto, util: _util } = {}) => {
1648
- const webCrypto = new WebCrypto({ crypto: _crypto, util: _util });
1649
-
1650
- return async (req, params) => {
1651
- const {
1652
- 'x-public-key': apiKey,
1653
- 'x-ephemeral-key': ephemeralPublicKey,
1654
- 'x-authorization-timestamp': customAuthTimestamp,
1655
- 'x-authorization': customAuth,
1656
- } = req.headers ?? {};
1657
-
1658
- const { uri: _uri, disableRecryption: _disableRecryption } = params ?? {};
1659
- const uri = _uri ?? req.url;
1660
- const disableRecryption = isTrue(_disableRecryption);
1661
-
1662
- let isVerifiable;
1663
- try {
1664
- const [authProtocol, authSignature] = ensureString(customAuth).split(' ');
1665
- isVerifiable = isTrue(
1666
- apiKey &&
1667
- ephemeralPublicKey &&
1668
- keyPairs?.shared &&
1669
- customAuthTimestamp &&
1670
- authSignature &&
1671
- (authProtocol === 'HMAC-SHA256')
1672
- );
1673
-
1674
- let verificationKeys;
1675
- const rawBody = await ensureRawBody(req);
1676
-
1677
- // NOTE: requestBody should be wind up decrypted when isVerifiable (unless disableRecryption, then will pass through)
1678
- let requestBody = safeParse(rawBody);
1679
-
1680
- // TEMP!!! remove isVerifiable check once webwidget moved to react
1681
- if (isVerifiable) {
1682
- if (
1683
- !apiKey ||
1684
- !ephemeralPublicKey ||
1685
- !customAuthTimestamp ||
1686
- !authSignature ||
1687
- !keyPairs?.shared ||
1688
- (apiKey !== keyPairs.shared.publicKey) ||
1689
- (authProtocol !== 'HMAC-SHA256')
1690
- ) {
1691
- throw new VerificationError('Invalid or missing authorization', { code: 401 })
1692
- }
1693
-
1694
- verificationKeys = await webCrypto.getVerificationKeys({
1695
- publicKey: ephemeralPublicKey,
1696
- privateKey: keyPairs.shared.privateKey,
1697
- });
1698
-
1699
- if (!verificationKeys) {
1700
- throw new VerificationError('Invalid or missing verification', { code: 412 })
1701
- }
1702
-
1703
- const verificationPayload = objectToSortedString({
1704
- method: getRequestMethod(rawBody, req.method),
1705
- timestamp: customAuthTimestamp,
1706
- body: requestBody, // NOTE: requestBody should be encrypted when isVerifiable
1707
- ...normalizeURIParts(uri),
1708
- });
1709
-
1710
- const isValid = await webCrypto.verifyHMAC(
1711
- verificationPayload,
1712
- verificationKeys.derivedHMACKey,
1713
- authSignature
1714
- );
1715
-
1716
- if (!isValid) {
1717
- throw new VerificationError('Invalid or missing verification', { code: 412 })
1718
- }
1719
-
1720
- if (!disableRecryption && requestBody) {
1721
- const decryptedMessage = await webCrypto.decryptMessage(requestBody, verificationKeys.derivedSecretKey);
1722
- requestBody = safeParse(decryptedMessage);
1723
- }
1724
- }
1725
-
1726
- const processResponse = async (response) => {
1727
- if (!response || disableRecryption || !isVerifiable || !verificationKeys?.derivedSecretKey) {
1728
- return response
1729
- }
1730
-
1731
- return webCrypto.encryptMessage(JSON.stringify(response), verificationKeys.derivedSecretKey)
1732
- };
1733
-
1734
- return { rawBody, requestBody, processResponse }
1735
- } catch (error) {
1736
- console.error(`Error handling request verification for '${uri}'`, { error, isVerifiable, disableRecryption });
1737
- throw error
1738
- }
1739
- }
1740
- };
1741
-
1742
- /*!
1743
- * raw-body
1744
- * Copyright(c) 2013-2014 Jonathan Ong
1745
- * Copyright(c) 2014-2022 Douglas Christopher Wilson
1746
- * MIT Licensed
1747
- */
1748
-
1749
- /**
1750
- * Module dependencies.
1751
- * @private
1752
- */
1753
-
1754
- var asyncHooks = tryRequireAsyncHooks();
1755
- var bytes = require('bytes');
1756
- var createError = require('http-errors');
1757
- var iconv = require('iconv-lite');
1758
- var unpipe = require('unpipe');
1759
-
1760
- /**
1761
- * Module exports.
1762
- * @public
1763
- */
1764
-
1765
- module.exports = getRawBody;
1766
-
1767
- /**
1768
- * Module variables.
1769
- * @private
1770
- */
1771
-
1772
- var ICONV_ENCODING_MESSAGE_REGEXP = /^Encoding not recognized: /;
1773
-
1774
- /**
1775
- * Get the decoder for a given encoding.
1776
- *
1777
- * @param {string} encoding
1778
- * @private
1779
- */
1780
-
1781
- function getDecoder (encoding) {
1782
- if (!encoding) return null
1783
-
1784
- try {
1785
- return iconv.getDecoder(encoding)
1786
- } catch (e) {
1787
- // error getting decoder
1788
- if (!ICONV_ENCODING_MESSAGE_REGEXP.test(e.message)) throw e
1789
-
1790
- // the encoding was not found
1791
- throw createError(415, 'specified encoding unsupported', {
1792
- encoding: encoding,
1793
- type: 'encoding.unsupported'
1794
- })
1795
- }
1796
- }
1797
-
1798
- /**
1799
- * Get the raw body of a stream (typically HTTP).
1800
- *
1801
- * @param {object} stream
1802
- * @param {object|string|function} [options]
1803
- * @param {function} [callback]
1804
- * @public
1805
- */
1806
-
1807
- function getRawBody (stream, options, callback) {
1808
- var done = callback;
1809
- var opts = options || {};
1810
-
1811
- // light validation
1812
- if (stream === undefined) {
1813
- throw new TypeError('argument stream is required')
1814
- } else if (typeof stream !== 'object' || stream === null || typeof stream.on !== 'function') {
1815
- throw new TypeError('argument stream must be a stream')
1816
- }
1817
-
1818
- if (options === true || typeof options === 'string') {
1819
- // short cut for encoding
1820
- opts = {
1821
- encoding: options
1822
- };
1823
- }
1824
-
1825
- if (typeof options === 'function') {
1826
- done = options;
1827
- opts = {};
1828
- }
1829
-
1830
- // validate callback is a function, if provided
1831
- if (done !== undefined && typeof done !== 'function') {
1832
- throw new TypeError('argument callback must be a function')
1833
- }
1834
-
1835
- // require the callback without promises
1836
- if (!done && !global.Promise) {
1837
- throw new TypeError('argument callback is required')
1838
- }
1839
-
1840
- // get encoding
1841
- var encoding = opts.encoding !== true
1842
- ? opts.encoding
1843
- : 'utf-8';
1844
-
1845
- // convert the limit to an integer
1846
- var limit = bytes.parse(opts.limit);
1847
-
1848
- // convert the expected length to an integer
1849
- var length = opts.length != null && !isNaN(opts.length)
1850
- ? parseInt(opts.length, 10)
1851
- : null;
1852
-
1853
- if (done) {
1854
- // classic callback style
1855
- return readStream(stream, encoding, length, limit, wrap(done))
1856
- }
1857
-
1858
- return new Promise(function executor (resolve, reject) {
1859
- readStream(stream, encoding, length, limit, function onRead (err, buf) {
1860
- if (err) return reject(err)
1861
- resolve(buf);
1862
- });
1863
- })
1864
- }
1865
-
1866
- /**
1867
- * Halt a stream.
1868
- *
1869
- * @param {Object} stream
1870
- * @private
1871
- */
1872
-
1873
- function halt (stream) {
1874
- // unpipe everything from the stream
1875
- unpipe(stream);
1876
-
1877
- // pause stream
1878
- if (typeof stream.pause === 'function') {
1879
- stream.pause();
1880
- }
1881
- }
1882
-
1883
- /**
1884
- * Read the data from the stream.
1885
- *
1886
- * @param {object} stream
1887
- * @param {string} encoding
1888
- * @param {number} length
1889
- * @param {number} limit
1890
- * @param {function} callback
1891
- * @public
1892
- */
1893
-
1894
- function readStream (stream, encoding, length, limit, callback) {
1895
- var complete = false;
1896
- var sync = true;
1897
-
1898
- // check the length and limit options.
1899
- // note: we intentionally leave the stream paused,
1900
- // so users should handle the stream themselves.
1901
- if (limit !== null && length !== null && length > limit) {
1902
- return done(createError(413, 'request entity too large', {
1903
- expected: length,
1904
- length: length,
1905
- limit: limit,
1906
- type: 'entity.too.large'
1907
- }))
1908
- }
1909
-
1910
- // streams1: assert request encoding is buffer.
1911
- // streams2+: assert the stream encoding is buffer.
1912
- // stream._decoder: streams1
1913
- // state.encoding: streams2
1914
- // state.decoder: streams2, specifically < 0.10.6
1915
- var state = stream._readableState;
1916
- if (stream._decoder || (state && (state.encoding || state.decoder))) {
1917
- // developer error
1918
- return done(createError(500, 'stream encoding should not be set', {
1919
- type: 'stream.encoding.set'
1920
- }))
1921
- }
1922
-
1923
- if (typeof stream.readable !== 'undefined' && !stream.readable) {
1924
- return done(createError(500, 'stream is not readable', {
1925
- type: 'stream.not.readable'
1926
- }))
1927
- }
1928
-
1929
- var received = 0;
1930
- var decoder;
1931
-
1932
- try {
1933
- decoder = getDecoder(encoding);
1934
- } catch (err) {
1935
- return done(err)
1936
- }
1937
-
1938
- var buffer = decoder
1939
- ? ''
1940
- : [];
1941
-
1942
- // attach listeners
1943
- stream.on('aborted', onAborted);
1944
- stream.on('close', cleanup);
1945
- stream.on('data', onData);
1946
- stream.on('end', onEnd);
1947
- stream.on('error', onEnd);
1948
-
1949
- // mark sync section complete
1950
- sync = false;
1951
-
1952
- function done () {
1953
- var args = new Array(arguments.length);
1954
-
1955
- // copy arguments
1956
- for (var i = 0; i < args.length; i++) {
1957
- args[i] = arguments[i];
1958
- }
1959
-
1960
- // mark complete
1961
- complete = true;
1962
-
1963
- if (sync) {
1964
- process.nextTick(invokeCallback);
1965
- } else {
1966
- invokeCallback();
1967
- }
1968
-
1969
- function invokeCallback () {
1970
- cleanup();
1971
-
1972
- if (args[0]) {
1973
- // halt the stream on error
1974
- halt(stream);
1975
- }
1976
-
1977
- callback.apply(null, args);
1978
- }
1979
- }
1980
-
1981
- function onAborted () {
1982
- if (complete) return
1983
-
1984
- done(createError(400, 'request aborted', {
1985
- code: 'ECONNABORTED',
1986
- expected: length,
1987
- length: length,
1988
- received: received,
1989
- type: 'request.aborted'
1990
- }));
1991
- }
1992
-
1993
- function onData (chunk) {
1994
- if (complete) return
1995
-
1996
- received += chunk.length;
1997
-
1998
- if (limit !== null && received > limit) {
1999
- done(createError(413, 'request entity too large', {
2000
- limit: limit,
2001
- received: received,
2002
- type: 'entity.too.large'
2003
- }));
2004
- } else if (decoder) {
2005
- buffer += decoder.write(chunk);
2006
- } else {
2007
- buffer.push(chunk);
2008
- }
2009
- }
2010
-
2011
- function onEnd (err) {
2012
- if (complete) return
2013
- if (err) return done(err)
2014
-
2015
- if (length !== null && received !== length) {
2016
- done(createError(400, 'request size did not match content length', {
2017
- expected: length,
2018
- length: length,
2019
- received: received,
2020
- type: 'request.size.invalid'
2021
- }));
2022
- } else {
2023
- var string = decoder
2024
- ? buffer + (decoder.end() || '')
2025
- : Buffer.concat(buffer);
2026
- done(null, string);
2027
- }
2028
- }
2029
-
2030
- function cleanup () {
2031
- buffer = null;
2032
-
2033
- stream.removeListener('aborted', onAborted);
2034
- stream.removeListener('data', onData);
2035
- stream.removeListener('end', onEnd);
2036
- stream.removeListener('error', onEnd);
2037
- stream.removeListener('close', cleanup);
2038
- }
2039
- }
2040
-
2041
- /**
2042
- * Try to require async_hooks
2043
- * @private
2044
- */
2045
-
2046
- function tryRequireAsyncHooks () {
2047
- try {
2048
- return require('async_hooks')
2049
- } catch (e) {
2050
- return {}
2051
- }
2052
- }
2053
-
2054
- /**
2055
- * Wrap function with async resource, if possible.
2056
- * AsyncResource.bind static method backported.
2057
- * @private
2058
- */
2059
-
2060
- function wrap (fn) {
2061
- var res;
2062
-
2063
- // create anonymous resource
2064
- if (asyncHooks.AsyncResource) {
2065
- res = new asyncHooks.AsyncResource(fn.name || 'bound-anonymous-fn');
2066
- }
2067
-
2068
- // incompatible node.js
2069
- if (!res || !res.runInAsyncScope) {
2070
- return fn
2071
- }
2072
-
2073
- // return bound function
2074
- return res.runInAsyncScope.bind(res, fn, null)
2075
- }
2076
-
2077
- var index = /*#__PURE__*/Object.freeze({
2078
- __proto__: null
2079
- });
2080
-
2081
- exports.CONTENT_TYPES = CONTENT_TYPES;
2082
- exports.CUSTOM_FORMATS = CUSTOM_FORMATS;
2083
- exports.DATE_FORMAT_LONG = DATE_FORMAT_LONG;
2084
- exports.DATE_FORMAT_MED = DATE_FORMAT_MED;
2085
- exports.DATE_FORMAT_ORGANIZED = DATE_FORMAT_ORGANIZED;
2086
- exports.DATE_FORMAT_TRACKING = DATE_FORMAT_TRACKING;
2087
- exports.HEADER_CONTENT_TYPE = HEADER_CONTENT_TYPE;
2088
- exports.LOCALE_FORMATS = LOCALE_FORMATS;
2089
- exports.TIME_FORMAT_AMPM = TIME_FORMAT_AMPM;
2090
- exports.TIME_FORMAT_LONG = TIME_FORMAT_LONG;
2091
- exports.VerificationError = VerificationError;
2092
- exports.WebCrypto = WebCrypto;
2093
- exports.appendQuery = appendQuery;
2094
- exports.arrayEmpty = arrayEmpty;
2095
- exports.arrayIncludesAll = arrayIncludesAll;
2096
- exports.arrayNotEmpty = arrayNotEmpty;
2097
- exports.arrayRandom = arrayRandom;
2098
- exports.arrayRandomItem = arrayRandomItem;
2099
- exports.arrayToObject = arrayToObject;
2100
- exports.arraysMatch = arraysMatch;
2101
- exports.camelCase = camelCase;
2102
- exports.capitalize = capitalize;
2103
- exports.cleanObject = cleanObject;
2104
- exports.combineCommas = combineCommas;
2105
- exports.compareDate = compareDate;
2106
- exports.compareEntryKeys = compareEntryKeys;
2107
- exports.compareNumber = compareNumber;
2108
- exports.compareString = compareString;
2109
- exports.convertDateTime = convertDateTime;
2110
- exports.convertDuration = convertDuration;
2111
- exports.convertFormatted = convertFormatted;
2112
- exports.dashcase = dashcase;
2113
- exports.ensureAlphaNumeric = ensureAlphaNumeric;
2114
- exports.ensureArray = ensureArray;
2115
- exports.ensureArraySet = ensureArraySet;
2116
- exports.ensureDateTime = ensureDateTime;
2117
- exports.ensureExtension = ensureExtension;
2118
- exports.ensureLeadingSlash = ensureLeadingSlash;
2119
- exports.ensureNumeric = ensureNumeric;
2120
- exports.ensureNumericConstrained = ensureNumericConstrained;
2121
- exports.ensureNumericNegatable = ensureNumericNegatable;
2122
- exports.ensureNumericOnly = ensureNumericOnly;
2123
- exports.ensureObject = ensureObject;
2124
- exports.ensureRawBody = ensureRawBody;
2125
- exports.ensureString = ensureString;
2126
- exports.ensureStringAscii = ensureStringAscii;
2127
- exports.ensureStringClean = ensureStringClean;
2128
- exports.ensureStringOnly = ensureStringOnly;
2129
- exports.ensureTrailingSlash = ensureTrailingSlash;
2130
- exports.findLastMatch = findLastMatch;
2131
- exports.formatRequiredTimestamps = formatRequiredTimestamps;
2132
- exports.formatTimestamps = formatTimestamps;
2133
- exports.freeze = freeze;
2134
- exports.getCurrentDateTime = getCurrentDateTime;
2135
- exports.getDateTimeFormat = getDateTimeFormat;
2136
- exports.getDateTimeFromString = getDateTimeFromString;
2137
- exports.getDaysBetween = getDaysBetween;
2138
- exports.getDaysInMonth = getDaysInMonth;
2139
- exports.getDuration = getDuration;
2140
- exports.getKeysData = getKeysData;
2141
- exports.getRequestMethod = getRequestMethod;
2142
- exports.getVerificationHelpers = getVerificationHelpers;
2143
- exports.getWeeksBetween = getWeeksBetween;
2144
- exports.isArray = isArray;
2145
- exports.isHTML = isHTML;
2146
- exports.isNumericNegatable = isNumericNegatable;
2147
- exports.isNumericOnly = isNumericOnly;
2148
- exports.isSameDay = isSameDay;
2149
- exports.isSameHour = isSameHour;
2150
- exports.isSet = isSet;
2151
- exports.isTrue = isTrue;
2152
- exports.isType = isType;
2153
- exports.isTypeEnhanced = isTypeEnhanced;
2154
- exports.lowercase = lowercase;
2155
- exports.nextRandom = nextRandom;
2156
- exports.normalizeArray = normalizeArray;
2157
- exports.normalizeShopifyId = normalizeShopifyId;
2158
- exports.normalizeURIParts = normalizeURIParts;
2159
- exports.nullable = nullable;
2160
- exports.objectContainsAnyValue = objectContainsAnyValue;
2161
- exports.objectEmpty = objectEmpty;
2162
- exports.objectNotEmpty = objectNotEmpty;
2163
- exports.objectToSortedString = objectToSortedString;
2164
- exports.parseKeywordGroups = parseKeywordGroups;
2165
- exports.parseKeywords = parseKeywords;
2166
- exports.parseProject = parseProject;
2167
- exports.parseProjectName = parseProjectName;
2168
- exports.prepareRequestOptions = prepareRequestOptions;
2169
- exports.prepareVerificationRequest = prepareVerificationRequest;
2170
- exports.processVerificationResponse = processVerificationResponse;
2171
- exports.querystringToObject = querystringToObject;
2172
- exports.random = random;
2173
- exports.removeLeadingSlash = removeLeadingSlash;
2174
- exports.removeLeadingTrailingQuotes = removeLeadingTrailingQuotes;
2175
- exports.removeLeadingTrailingSlash = removeLeadingTrailingSlash;
2176
- exports.removeTrailingSlash = removeTrailingSlash;
2177
- exports.replaceSpaces = replaceSpaces;
2178
- exports.safeParse = safeParse;
2179
- exports.safeTrim = safeTrim;
2180
- exports.setDateTimeLocale = setDateTimeLocale;
2181
- exports.setDateTimeZone = setDateTimeZone;
2182
- exports.slugify = slugify;
2183
- exports.snakecase = snakecase;
2184
- exports.splitCommas = splitCommas;
2185
- exports.stringEmpty = stringEmpty;
2186
- exports.stringEmptyOnly = stringEmptyOnly;
2187
- exports.stringNotEmpty = stringNotEmpty;
2188
- exports.stringNotEmptyOnly = stringNotEmptyOnly;
2189
- exports.titleCase = titleCase;
2190
- exports.toFormatted = toFormatted;
2191
- exports.toISOString = toISOString;
2192
- exports.toJSDate = toJSDate;
2193
- exports.toUTCString = toUTCString;
2194
- exports.toUnixInteger = toUnixInteger;
2195
- exports.toUnixString = toUnixString;
2196
- exports.undashcase = undashcase;
2197
- exports.unsnakecase = unsnakecase;
2198
- exports.uppercase = uppercase;
2199
- exports.wrappedEncode = wrappedEncode;
2200
- exports.zencase = zencase;
2201
-
2202
- Object.defineProperty(exports, '__esModule', { value: true });
2203
-
2204
- }));
1
+ /**
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2021-present agnoStack, Inc. and Adam Grohs
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self)["@agnostack/verifyd"]={})}(this,(function(e){"use strict";const t=(e,t)=>null!=e&&typeof e===t,r=(e,r)=>(t(e,"string")&&(r&&(e=(e=>(t(e,"string")&&(e=e.trim()),e))(e)),(e.startsWith("{")||e.startsWith("[")||e.startsWith('"'))&&(e=JSON.parse(e))),e),n=e=>e?`${e}`:"",i=(e=[])=>e?Array.isArray(e)?e:[e]:[],o=e=>!(e=>{const t=n(e);return t.length>0&&!["null","undefined"].includes(t)})(e),a=([e],[t])=>((e,t)=>o(e)?1:o(t)?-1:n(e).localeCompare(t))(e,t),s=(e,t="")=>Object.entries((e=>e??{})(e)).sort(a).map((([e,t])=>`${e}=${t}`)).join(t),c=e=>`/${(e=>n(e).replace(/^\//,""))(e)}`,u=(e,t)=>{const r=n(e);return![...i(t),"","false"].includes(r)},y="xyz.com",l=["shop","host"],d="Content-Type",p={APPLICATION_JSON:"application/json"},f=(e,t)=>n(t??(e?"POST":"GET")).toUpperCase(),h=({method:e,body:t,headers:n,...i}={})=>{const o=f(t,e),a={[d]:p.APPLICATION_JSON,...n};let s=t;return s&&a[d].startsWith(p.APPLICATION_JSON)&&(s=JSON.stringify(r(s))),{method:o,headers:a,...s&&"GET"!==o&&{body:s},...i}},g=e=>{const t=(e=>{if(o(e))return;return/^(https?:\/\/).+/i.test(e)?new URL(e):new URL(`https://${y}${c(e)}`)})(e);if(t)return l.forEach((e=>t.searchParams.delete(e))),{...t.hostname!==y&&{origin:t.origin},pathname:t.pathname,search:t.search}};var m,w=((m=function(){return w}).toString=m.toLocaleString=m[Symbol.toPrimitive]=function(){return""},m.valueOf=function(){return!1},new Proxy(Object.freeze(m),{get:function(e,t){return e.hasOwnProperty(t)?e[t]:w}}));let b=global.window,v=e=>!function(e){return e===w}(e),K=void 0!==b?b:w;class A{constructor({crypto:e,util:t}={}){this._crypto=e??{},this._util=t??{}}get subtle(){return this._crypto?.subtle}async getWebCrypto(){if(!this._crypto?.subtle)try{this._crypto=(await import("isomorphic-webcrypto")).default}catch(e){try{this._crypto=(await import("crypto")).default}catch(e){throw console.error("Failed to import node crypto, ensure 'isomorphic-webcrypto' (or node 'crypto') is installed and/or pass in implementation via 'new WebCrypto({ crypto })'"),e}}if(!this._crypto?.subtle)throw new Error("Invalid crypto, missing subtle");return this._crypto}async getTextDecoder(){if(this._util?.TextDecoder)return this._util.TextDecoder;if(v(K)&&"function"==typeof K?.TextDecoder)return K.TextDecoder;try{const e=(await import("util")).TextDecoder;return this._util.TextDecoder=e,e}catch(e){throw console.error("Failed to import 'utils.TextDecoder', ensure 'util' is available and/or pass in implementation via 'new WebCrypto({ util })'"),e}}async getTextEncoder(){if(this._util?.TextEncoder)return this._util.TextEncoder;if(v(K)&&"function"==typeof K?.TextEncoder)return K.TextEncoder;try{const e=(await import("util")).TextEncoder;return this._util.TextEncoder=e,e}catch(e){throw console.error("Failed to import 'utils.TextEncoder', ensure 'util' is available and/or pass in implementation via 'new WebCrypto({ util })'"),e}}timingSafeEqual(e,t){if(null==e||null==t||e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e[n]^t[n];return 0===r}stringToHex(e){return Array.from(n(e),(e=>e.charCodeAt(0).toString(16).padStart(2,"0"))).join("")}hexToString(e){return i(n(e).match(/.{1,2}/g)).map((e=>String.fromCharCode(parseInt(e,16)))).join("")}async arrayBufferToString(e){const t=new Uint8Array(e);return(new(await this.getTextDecoder())).decode(t)}arrayToArrayBuffer(e){return null!=ArrayBuffer.from?ArrayBuffer.from(e):new Uint8Array(e).buffer}ensureArrayBuffer(e){return e instanceof ArrayBuffer?e:this.arrayToArrayBuffer(e)}async generateKeyPair(){const e=await this.getWebCrypto();return await e.subtle.generateKey({name:"ECDH",namedCurve:"P-256"},!0,["deriveKey"])}getKeyOperations(e){switch(e){case"private":case"privateKey":return["deriveKey"];case"secret":case"secretKey":case"sharedSecret":return["encrypt","decrypt"];default:return[]}}getKeyAlgorythm(e){switch(e){case"derivedKey":case"derived":case"secret":case"secretKey":case"sharedSecret":return{name:"AES-GCM"};default:return{name:"ECDH",namedCurve:"P-256"}}}async generateHMAC(e,t){if(!e||!t)return;const r=await this.getWebCrypto(),n=await this.getTextEncoder(),i=await r.subtle.sign("HMAC",t,(new n).encode(e));return this.stringToHex(this.arrayBufferToString(i))}async verifyHMAC(e,t,r){const n=await this.generateHMAC(e,t);return this.timingSafeEqual(n,r)}async getStorableKey(e){const t=await this.getWebCrypto(),r=await t.subtle.exportKey("jwk",e);return this.stringToHex(JSON.stringify(r))}async restoreStorableKey(e,t){if(!t)return;const r=await this.getWebCrypto(),n=JSON.parse(this.hexToString(t)||"{}");var i;return(i=n)&&Object.keys(i).length?r.subtle.importKey("jwk",n,this.getKeyAlgorythm(e),!0,this.getKeyOperations(e)):void 0}async getStorableKeyPair(e){const t={};for(const[r,n]of Object.entries(e))t[r]=await this.getStorableKey(n);return t}async restoreStorableKeyPair(e){const t={};for(const[r,n]of Object.entries(e))t[r]=await this.restoreStorableKey(r,n);return t}async deriveSharedKey({publicKey:e,privateKey:t}){if(!e||!t)return;const r=await this.getWebCrypto();return await r.subtle.deriveKey({name:"ECDH",public:e},t,{name:"AES-GCM",length:256},!0,["encrypt","decrypt"])}async deriveHMACKey({publicKey:e,privateKey:t}){if(!e||!t)return;const r=await this.getWebCrypto();return await r.subtle.deriveKey({name:"ECDH",public:e},t,{name:"HMAC",hash:{name:"SHA-256"},length:256},!0,["sign","verify"])}async getVerificationKeys({publicKey:e,privateKey:t}){if(!e||!t)return{};const r=await this.restoreStorableKeyPair({publicKey:e,privateKey:t}),n=await this.deriveHMACKey(r);return{derivedSecretKey:await this.deriveSharedKey(r),derivedHMACKey:n}}async encryptMessage(e,t){if(!e||!t)return;const r=await this.getWebCrypto(),n=r.getRandomValues(new Uint8Array(12)),i=(new(await this.getTextEncoder())).encode(e),o=await r.subtle.encrypt({name:"AES-GCM",iv:n},t,i),a=new Uint8Array([...n,...new Uint8Array(o)]);return Array.from(a)}async decryptMessage(e,t){if(!e||!t)return;const r=await this.getWebCrypto(),n=this.ensureArrayBuffer(e),i=n.slice(0,12),o=n.slice(12),a=await r.subtle.decrypt({name:"AES-GCM",iv:i},t,o);return(new(await this.getTextDecoder())).decode(a)}}class S extends Error{constructor(e,t){super(e);const{code:r=500,...n}=t??{};this.code=r,this.data=n,this.name="VerificationError",Object.setPrototypeOf(this,S.prototype)}}const T=async e=>(async e=>{if(e?.rawBody)return e.rawBody;try{return(0,(await Promise.resolve().then((function(){return M}))).default)(e)}catch(e){throw console.error("Failed to import 'raw-body', please ensure the dependency is installed"),e}})(e).then((e=>e.toString())).catch((t=>{throw console.error(`Error getting raw body for '${e?.url}'`,t),t}));var C=function(){try{return require("async_hooks")}catch(e){return{}}}(),E=require("bytes"),x=require("http-errors"),O=require("iconv-lite"),P=require("unpipe");module.exports=function(e,t,r){var n=r,i=t||{};if(void 0===e)throw new TypeError("argument stream is required");if("object"!=typeof e||null===e||"function"!=typeof e.on)throw new TypeError("argument stream must be a stream");!0!==t&&"string"!=typeof t||(i={encoding:t});"function"==typeof t&&(n=t,i={});if(void 0!==n&&"function"!=typeof n)throw new TypeError("argument callback must be a function");if(!n&&!global.Promise)throw new TypeError("argument callback is required");var o=!0!==i.encoding?i.encoding:"utf-8",a=E.parse(i.limit),s=null==i.length||isNaN(i.length)?null:parseInt(i.length,10);if(n)return H(e,o,s,a,function(e){var t;C.AsyncResource&&(t=new C.AsyncResource(e.name||"bound-anonymous-fn"));if(!t||!t.runInAsyncScope)return e;return t.runInAsyncScope.bind(t,e,null)}(n));return new Promise((function(t,r){H(e,o,s,a,(function(e,n){if(e)return r(e);t(n)}))}))};var _=/^Encoding not recognized: /;function H(e,t,r,n,i){var o=!1,a=!0;if(null!==n&&null!==r&&r>n)return l(x(413,"request entity too large",{expected:r,length:r,limit:n,type:"entity.too.large"}));var s=e._readableState;if(e._decoder||s&&(s.encoding||s.decoder))return l(x(500,"stream encoding should not be set",{type:"stream.encoding.set"}));if(void 0!==e.readable&&!e.readable)return l(x(500,"stream is not readable",{type:"stream.not.readable"}));var c,u=0;try{c=function(e){if(!e)return null;try{return O.getDecoder(e)}catch(t){if(!_.test(t.message))throw t;throw x(415,"specified encoding unsupported",{encoding:e,type:"encoding.unsupported"})}}(t)}catch(e){return l(e)}var y=c?"":[];function l(){for(var t=new Array(arguments.length),r=0;r<t.length;r++)t[r]=arguments[r];function n(){h(),t[0]&&function(e){P(e),"function"==typeof e.pause&&e.pause()}(e),i.apply(null,t)}o=!0,a?process.nextTick(n):n()}function d(){o||l(x(400,"request aborted",{code:"ECONNABORTED",expected:r,length:r,received:u,type:"request.aborted"}))}function p(e){o||(u+=e.length,null!==n&&u>n?l(x(413,"request entity too large",{limit:n,received:u,type:"entity.too.large"})):c?y+=c.write(e):y.push(e))}function f(e){if(!o){if(e)return l(e);if(null!==r&&u!==r)l(x(400,"request size did not match content length",{expected:r,length:r,received:u,type:"request.size.invalid"}));else l(null,c?y+(c.end()||""):Buffer.concat(y))}}function h(){y=null,e.removeListener("aborted",d),e.removeListener("data",p),e.removeListener("end",f),e.removeListener("error",f),e.removeListener("close",h)}e.on("aborted",d),e.on("close",h),e.on("data",p),e.on("end",f),e.on("error",f),a=!1}var M=Object.freeze({__proto__:null});e.CONTENT_TYPES=p,e.HEADER_CONTENT_TYPE=d,e.VerificationError=S,e.WebCrypto=A,e.ensureRawBody=T,e.getKeysData=async(e,{crypto:t,util:r}={})=>{const n=new A({crypto:t,util:r}),i=await n.getStorableKeyPair(await n.generateKeyPair(e));return{publicKey:e,ephemeral:i,verification:await n.getVerificationKeys({publicKey:e,privateKey:i.privateKey})}},e.getRequestMethod=f,e.getVerificationHelpers=({keyPairs:e,crypto:t,util:i}={})=>{const o=new A({crypto:t,util:i});return async(t,i)=>{const{"x-public-key":a,"x-ephemeral-key":c,"x-authorization-timestamp":y,"x-authorization":l}=t.headers??{},{uri:d,disableRecryption:p}=i??{},h=d??t.url,m=u(p);let w;try{const[i,d]=n(l).split(" ");let p;w=u(a&&c&&e?.shared&&y&&d&&"HMAC-SHA256"===i);const b=await T(t);let v=r(b);if(w){if(!(a&&c&&y&&d&&e?.shared&&a===e.shared.publicKey&&"HMAC-SHA256"===i))throw new S("Invalid or missing authorization",{code:401});if(p=await o.getVerificationKeys({publicKey:c,privateKey:e.shared.privateKey}),!p)throw new S("Invalid or missing verification",{code:412});const n=s({method:f(b,t.method),timestamp:y,body:v,...g(h)});if(!await o.verifyHMAC(n,p.derivedHMACKey,d))throw new S("Invalid or missing verification",{code:412});if(!m&&v){const e=await o.decryptMessage(v,p.derivedSecretKey);v=r(e)}}return{rawBody:b,requestBody:v,processResponse:async e=>e&&!m&&w&&p?.derivedSecretKey?o.encryptMessage(JSON.stringify(e),p.derivedSecretKey):e}}catch(e){throw console.error(`Error handling request verification for '${h}'`,{error:e,isVerifiable:w,disableRecryption:m}),e}}},e.normalizeURIParts=g,e.prepareRequestOptions=h,e.prepareVerificationRequest=({keysData:e,disableRecryption:t,crypto:n,util:i}={})=>{const a=new A({crypto:n,util:i}),c=u(t);return async(t,{method:n,body:i,headers:u,...y}={})=>{let l=r(i);const d=f(l,n);if(c||o(e?.publicKey))return[t,h({method:d,body:l,headers:u,...y})];const{verification:{derivedHMACKey:p,derivedSecretKey:m}={},ephemeral:{publicKey:w}={}}=e??{};if(!p||!w)return;l&&m&&(l=await a.encryptMessage(JSON.stringify(l),m));const b=(()=>{const e=(new Date).getTime();return Math.floor(e/1e3).toString()})(),v=await a.generateHMAC(s({body:l,method:d,timestamp:b,...g(t)}),p);return[t,h({method:d,body:l,headers:{"X-Authorization":`HMAC-SHA256 ${v}`,"X-Authorization-Timestamp":b,"X-Ephemeral-Key":w,"X-Public-Key":e.publicKey,...u},...y}),m]}},e.processVerificationResponse=({keysData:e,disableRecryption:t,crypto:n,util:i}={})=>{const o=new A({crypto:n,util:i}),a=u(t);return async(t,n)=>{const i=n??e.verification?.derivedSecretKey;if(a||!t||!i)return t;const s=await o.decryptMessage(t,i);return r(s)}},Object.defineProperty(e,"__esModule",{value:!0})}));
2205
26
  //# sourceMappingURL=index.js.map