@selkirk-systems/fetch 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Fetch.js CHANGED
@@ -438,7 +438,8 @@ export const putJsonInCache = (cache, url, json) => {
438
438
  const response = new Response(JSON.stringify(json), responseObjectJson);
439
439
  return cache.put(url, response);
440
440
  };
441
- let _cache = null;
441
+ const _caches = {};
442
+ const _updatingCache = {};
442
443
  const DATA_METHODS = {
443
444
  "GET": null,
444
445
  "PATCH": null,
@@ -446,25 +447,84 @@ const DATA_METHODS = {
446
447
  "PUT": null
447
448
  };
448
449
  const _fetch = (url, options = {}) => {
449
- if (self) {
450
+ const cacheName = getCacheNameFromUrl(url);
451
+
452
+ //HANDLE: Service worker BS, environment is different don't cache if were in a service worker.
453
+ if (!self || !cacheName) {
450
454
  return Fetch(url, options);
451
455
  }
452
- function cacheResponse([response, isAbort]) {
456
+ let _cache = _caches[cacheName];
457
+ async function cacheResponse([response, isAbort]) {
453
458
  const status = response.status.code;
454
459
  const headers = response.request.headers;
455
460
  const method = response.request.method;
456
461
  if (status >= 200 && status < 400 && headers.get('content-type') === "application/json") {
457
- if (DATA_METHODS.hasOwnProperty(method) || method === "GET") {
462
+ if (DATA_METHODS.hasOwnProperty(method)) {
458
463
  const data = serializeData(response.data);
464
+
465
+ //HANDLE: List/Array like responses
459
466
  if (data.page && !data.items || data.items && data.items.length === 0) {
460
467
  deleteFromCache(_cache, url, response);
461
468
  return [response, isAbort];
462
469
  }
470
+
471
+ //HANDLE: Record like response
472
+ if (data.id) {
473
+ let uuid = getUUID(url.toString());
474
+ let matchOptions = {};
475
+ if (method === "POST") {
476
+ uuid = {
477
+ id: data.id,
478
+ shortPath: url.toString()
479
+ };
480
+ }
481
+
482
+ /**
483
+ * POST new records are hard, what cached list do we put the new record in? what about sort? blag. Don't have a good answer yet
484
+ * For now, new records will be put in the first cached entry that matches at the 0 index, this 'should' be page=0 cached entries
485
+ * and the records should all show up in UI at the top. This is the most common UX for adding records to a list,
486
+ * one day some how figure out a less fragile robust method.
487
+ *
488
+ * Perhaps we should just refetch all the matched cache urls... but this could be huge and slow and network heavy...
489
+ */
490
+ await cacheFindAllLike({
491
+ cache: _cache,
492
+ url: uuid.shortPath,
493
+ property: "id",
494
+ value: data.id,
495
+ method: method
496
+ }).then(async matches => {
497
+ if (matches.length) {
498
+ const finalOptions = {
499
+ ...responseObjectJson
500
+ };
501
+ finalOptions.headers['Time-Cached'] = new Date().getTime();
502
+ await matches.forEach(async match => {
503
+ const items = extractDataArray(match.data);
504
+
505
+ //This is where we put the new record on the top of the cached list, even if a new network request would place it somewhere
506
+ // else based on sort etc.
507
+ if (method === "POST") {
508
+ items.unshift(data);
509
+ } else {
510
+ items[match.matchIndex] = data;
511
+ }
512
+ const responseObj = new Response(JSON.stringify(match.data), finalOptions);
513
+ dispatch(UPDATE_CACHE, {
514
+ url: new URL(match.request.url),
515
+ response: match.response
516
+ });
517
+ return _cache.put(match.request.url, responseObj);
518
+ });
519
+ }
520
+ });
521
+ return [response, isAbort];
522
+ }
463
523
  const finalOptions = {
464
524
  ...responseObjectJson
465
525
  };
466
526
  finalOptions.headers['Time-Cached'] = new Date().getTime();
467
- const responseObj = new Response(JSON.stringify(response), finalOptions);
527
+ const responseObj = new Response(JSON.stringify(response.data), finalOptions);
468
528
  _cache.put(url, responseObj);
469
529
  dispatch(UPDATE_CACHE, {
470
530
  url: url,
@@ -481,9 +541,37 @@ const _fetch = (url, options = {}) => {
481
541
  }
482
542
  function cacheMatch() {
483
543
  //HANDLE: Data updates, always return fresh data
484
- if (options.method && DATA_METHODS.hasOwnProperty(options.method)) {
544
+ if (options.skipCache || options.method && DATA_METHODS.hasOwnProperty(options.method)) {
485
545
  return Fetch(url, options).then(cacheResponse);
486
546
  }
547
+ const uuid = getUUID(url.toString());
548
+
549
+ //HANDLE: individual records, if we are looking for a record check cache for any array or search results with this record
550
+ if (uuid.id) {
551
+ return cacheFindLike({
552
+ cache: _cache,
553
+ url: uuid.shortPath,
554
+ property: "id",
555
+ value: uuid.id
556
+ }).then(match => {
557
+ if (match) {
558
+ //Behind the scenes still fetch the record and update the cache but don't make the user wait for it.
559
+ Fetch(url, options).then(cacheResponse);
560
+ const responseObj = new Response(JSON.stringify(match.match));
561
+ return Promise.resolve([{
562
+ request: null,
563
+ response: responseObj,
564
+ data: match.match,
565
+ status: {
566
+ code: 200,
567
+ text: '',
568
+ isAbort: false
569
+ }
570
+ }, false]);
571
+ }
572
+ return Fetch(url, options).then(cacheResponse);
573
+ });
574
+ }
487
575
  return _cache.match(url).then(response => {
488
576
  if (response) {
489
577
  const timeCached = response.headers.get('Time-Cached');
@@ -496,7 +584,7 @@ const _fetch = (url, options = {}) => {
496
584
  return Promise.resolve([{
497
585
  request: null,
498
586
  response: response,
499
- data: obj.data,
587
+ data: obj,
500
588
  status: {
501
589
  code: response.status,
502
590
  text: response.statusText,
@@ -511,7 +599,8 @@ const _fetch = (url, options = {}) => {
511
599
  if (_cache) {
512
600
  return cacheMatch();
513
601
  }
514
- return caches.open('fetch').then(cache => {
602
+ return caches.open(cacheName).then(cache => {
603
+ _caches[cacheName] = cache;
515
604
  _cache = cache;
516
605
  }).then(cacheMatch);
517
606
  };
@@ -522,6 +611,119 @@ function deleteFromCache(_cache, url, response) {
522
611
  response: response
523
612
  });
524
613
  }
614
+ function cacheFindAllLike(options) {
615
+ const finalOptions = {
616
+ ...options,
617
+ findAll: true
618
+ };
619
+ return cacheFindLike(finalOptions);
620
+ }
621
+ function cacheFindLike(options) {
622
+ const cache = options.cache;
623
+ const url = options.url;
624
+ const property = options.property;
625
+ const value = options.value;
626
+ const findAll = options.findAll;
627
+ const matchOptions = options.matchOptions;
628
+ const method = options.method || "GET";
629
+ return cache.keys().then(keys => {
630
+ const matchingRequests = [];
631
+ for (let i = 0; i < keys.length; i++) {
632
+ const request = keys[i];
633
+ if (looksLikeResultListUrl(request.url, url)) {
634
+ matchingRequests.push(cache.match(request, matchOptions).then(match => {
635
+ match._request = request;
636
+ return match;
637
+ }));
638
+ }
639
+ }
640
+ return Promise.all(matchingRequests).then(responses => {
641
+ let count = 0;
642
+ const ret = [];
643
+ const checkCache = async index => {
644
+ if (index >= responses.length) return ret;
645
+ return serializeResponse(responses[index]).then(json => {
646
+ const items = extractDataArray(json);
647
+ const response = responses[count];
648
+ const retObj = {
649
+ request: response._request,
650
+ response: response,
651
+ data: json,
652
+ match: json,
653
+ matchIndex: -1,
654
+ status: {
655
+ code: response.status,
656
+ text: response.statusText,
657
+ isAbort: false
658
+ }
659
+ };
660
+ if (method === "POST") {
661
+ return findAll ? [retObj] : retObj;
662
+ }
663
+ const matchIndex = items.findIndex(item => item[property] === value);
664
+ count++;
665
+ if (matchIndex >= 0) {
666
+ retObj.match = items[matchIndex];
667
+ retObj.matchIndex = matchIndex;
668
+ if (!findAll) {
669
+ return retObj;
670
+ } else {
671
+ ret.push(retObj);
672
+ }
673
+ return checkCache(count);
674
+ }
675
+ if (count >= responses.length) {
676
+ return findAll ? ret : null;
677
+ }
678
+ return checkCache(count);
679
+ });
680
+ };
681
+ if (!responses || !responses.length) return findAll ? ret : null;
682
+ return checkCache(count);
683
+ });
684
+ });
685
+ }
686
+
687
+ /**
688
+ * Check if request.url looks like a list result type url, if it contains a UUID it is likely not.
689
+ * @param {*} requestURL
690
+ * @param {*} matchURL
691
+ * @returns
692
+ */
693
+ function looksLikeResultListUrl(requestURL, matchURL) {
694
+ const uuid = getUUID(requestURL.toString());
695
+ return !uuid.id && requestURL.indexOf(matchURL) >= 0;
696
+ }
697
+ function extractDataArray(obj) {
698
+ for (var prop in obj._embedded) {
699
+ if (!obj._embedded.hasOwnProperty(prop)) continue;
700
+ return obj._embedded[prop];
701
+ }
702
+ return [obj];
703
+ }
704
+ async function serializeResponse(response) {
705
+ return response.text().then(data => {
706
+ return JSON.parse(data);
707
+ });
708
+ }
709
+ function getUUID(str) {
710
+ const UUID_REG_EXP = /.+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*?/;
711
+ const match = UUID_REG_EXP.exec(str);
712
+ let id, shortPath;
713
+ if (match && match[1]) {
714
+ id = match[1];
715
+ shortPath = str.split(`/${id}`)[0];
716
+ }
717
+ return {
718
+ id: id,
719
+ shortPath: shortPath
720
+ };
721
+ }
722
+ function getCacheNameFromUrl(url) {
723
+ const API_REG_EXP = /p\/.+com\/(.*?)\//;
724
+ const matchArray = url.toString().match(API_REG_EXP);
725
+ return matchArray && matchArray.length >= 1 ? matchArray[1] : null;
726
+ }
525
727
  function expiredCache(timeCached) {
526
728
  const now = new Date().getTime();
527
729
  return Math.abs(now - timeCached) >= CACHED_EXPIRY_TIMESTAMP;
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as Download } from './Download';
2
2
  export { default as fetch, applyMiddleware, OnOKResponse } from './Fetch';
3
+ export * from "./utils/FetchUtils";
3
4
  export * from "./constants/FetchConstants";
@@ -0,0 +1,74 @@
1
+ import Fetch from '../Fetch';
2
+ export const createUrlParams = (params, existingParams) => {
3
+ //SORT the params alphabetically, very important for cache lookups as the search a params in different order will cause it to not be matched.
4
+ params = Object.keys(params).sort().reduce((obj, key) => {
5
+ obj[key] = params[key];
6
+ return obj;
7
+ }, {});
8
+ const searchParams = existingParams ? existingParams : new URLSearchParams();
9
+ for (var prop in params) {
10
+ if (!params.hasOwnProperty(prop) || params[prop] === undefined || params[prop] === null) continue;
11
+ if (Array.isArray(params[prop])) {
12
+ if (prop === "sort") {
13
+ searchParams.delete("sort");
14
+ }
15
+ for (let i = 0; i < params[prop].length; i++) {
16
+ const e = params[prop][i];
17
+ if (searchParams.has(prop) && prop !== "sort") {
18
+ searchParams.set(prop, e);
19
+ } else {
20
+ searchParams.append(prop, e);
21
+ }
22
+ }
23
+ continue;
24
+ }
25
+ if (searchParams.has(prop)) {
26
+ searchParams.set(prop, params[prop]);
27
+ } else {
28
+ searchParams.append(prop, params[prop]);
29
+ }
30
+ }
31
+ return searchParams;
32
+ };
33
+ export async function fetchPagedURL(url, callback, options) {
34
+ const finalOptions = {
35
+ skipCache: false,
36
+ ...options
37
+ };
38
+ callback = callback ? callback : () => {};
39
+ let ret = [];
40
+ const proxyUrl = url.toString().split("/p/");
41
+ const [response] = await Fetch(url, finalOptions);
42
+ const {
43
+ data,
44
+ status
45
+ } = response;
46
+ const size = new TextEncoder().encode(JSON.stringify(data)).length;
47
+ const kiloBytes = size / 1024;
48
+ const megaBytes = kiloBytes / 1024;
49
+ if (status.code > 299 || status.code < 200) {
50
+ return [response, null];
51
+ }
52
+ const results = {
53
+ page: data.page.number + 1,
54
+ totalPages: data.page.totalPages,
55
+ totalElements: data.page.totalElements,
56
+ pageSize: data.page.size,
57
+ kiloBytes: kiloBytes,
58
+ megaBytes: megaBytes
59
+ };
60
+ callback(results);
61
+ data.items = data._embedded[[Object.keys(data._embedded)[0]]];
62
+ ret = ret.concat(data.items || []);
63
+ if (data._links && data._links.next) {
64
+ const nextUrl = new URL(`${proxyUrl[0]}/p/${data._links.next.href}`);
65
+ nextUrl.search = createUrlParams({}, nextUrl.searchParams);
66
+ const [err, nextPage] = await fetchPagedURL(nextUrl, callback, options);
67
+ if (err) return [err, nextPage];
68
+ ret = ret.concat(nextPage);
69
+ }
70
+ return [null, ret];
71
+ }
72
+ function timeout(ms) {
73
+ return new Promise(resolve => setTimeout(resolve, ms));
74
+ }
package/lib/Fetch.js CHANGED
@@ -574,7 +574,12 @@ export const putJsonInCache = ( cache, url, json ) => {
574
574
 
575
575
  }
576
576
 
577
- let _cache = null;
577
+ const _caches = {};
578
+
579
+ const _updatingCache = {
580
+
581
+ }
582
+
578
583
  const DATA_METHODS = {
579
584
  "GET": null,
580
585
  "PATCH": null,
@@ -582,13 +587,20 @@ const DATA_METHODS = {
582
587
  "PUT": null
583
588
  }
584
589
 
590
+
591
+
585
592
  const _fetch = ( url, options = {} ) => {
586
593
 
587
- if ( self ) {
594
+ const cacheName = getCacheNameFromUrl( url );
595
+
596
+ //HANDLE: Service worker BS, environment is different don't cache if were in a service worker.
597
+ if ( !self || !cacheName ) {
588
598
  return Fetch( url, options );
589
599
  }
590
600
 
591
- function cacheResponse( [response, isAbort] ) {
601
+ let _cache = _caches[cacheName];
602
+
603
+ async function cacheResponse( [response, isAbort] ) {
592
604
 
593
605
  const status = response.status.code;
594
606
  const headers = response.request.headers;
@@ -597,10 +609,11 @@ const _fetch = ( url, options = {} ) => {
597
609
 
598
610
  if ( status >= 200 && status < 400 && headers.get( 'content-type' ) === "application/json" ) {
599
611
 
600
- if ( DATA_METHODS.hasOwnProperty( method ) || method === "GET" ) {
612
+ if ( DATA_METHODS.hasOwnProperty( method ) ) {
601
613
 
602
614
  const data = serializeData( response.data );
603
615
 
616
+ //HANDLE: List/Array like responses
604
617
  if ( data.page && !data.items || data.items && data.items.length === 0 ) {
605
618
 
606
619
  deleteFromCache( _cache, url, response );
@@ -609,10 +622,69 @@ const _fetch = ( url, options = {} ) => {
609
622
 
610
623
  }
611
624
 
625
+ //HANDLE: Record like response
626
+ if ( data.id ) {
627
+
628
+ let uuid = getUUID( url.toString() );
629
+ let matchOptions = {};
630
+
631
+ if ( method === "POST" ) {
632
+
633
+ uuid = {
634
+ id: data.id,
635
+ shortPath: url.toString()
636
+ }
637
+
638
+ }
639
+
640
+ /**
641
+ * POST new records are hard, what cached list do we put the new record in? what about sort? blag. Don't have a good answer yet
642
+ * For now, new records will be put in the first cached entry that matches at the 0 index, this 'should' be page=0 cached entries
643
+ * and the records should all show up in UI at the top. This is the most common UX for adding records to a list,
644
+ * one day some how figure out a less fragile robust method.
645
+ *
646
+ * Perhaps we should just refetch all the matched cache urls... but this could be huge and slow and network heavy...
647
+ */
648
+ await cacheFindAllLike( { cache: _cache, url: uuid.shortPath, property: "id", value: data.id, method: method } )
649
+ .then( async ( matches ) => {
650
+
651
+ if ( matches.length ) {
652
+
653
+ const finalOptions = { ...responseObjectJson };
654
+ finalOptions.headers['Time-Cached'] = new Date().getTime();
655
+
656
+ await matches.forEach( async match => {
657
+
658
+ const items = extractDataArray( match.data );
659
+
660
+ //This is where we put the new record on the top of the cached list, even if a new network request would place it somewhere
661
+ // else based on sort etc.
662
+ if ( method === "POST" ) {
663
+ items.unshift( data );
664
+ }
665
+ else {
666
+ items[match.matchIndex] = data;
667
+ }
668
+
669
+ const responseObj = new Response( JSON.stringify( match.data ), finalOptions );
670
+
671
+ dispatch( UPDATE_CACHE, { url: new URL( match.request.url ), response: match.response } );
672
+
673
+ return _cache.put( match.request.url, responseObj );
674
+
675
+ } )
676
+
677
+ }
678
+ } )
679
+
680
+ return [response, isAbort];
681
+
682
+ }
683
+
612
684
  const finalOptions = { ...responseObjectJson };
613
685
  finalOptions.headers['Time-Cached'] = new Date().getTime();
614
686
 
615
- const responseObj = new Response( JSON.stringify( response ), finalOptions );
687
+ const responseObj = new Response( JSON.stringify( response.data ), finalOptions );
616
688
  _cache.put( url, responseObj );
617
689
 
618
690
  dispatch( UPDATE_CACHE, { url: url, response: response } );
@@ -637,12 +709,45 @@ const _fetch = ( url, options = {} ) => {
637
709
  function cacheMatch() {
638
710
 
639
711
  //HANDLE: Data updates, always return fresh data
640
- if ( options.method && DATA_METHODS.hasOwnProperty( options.method ) ) {
712
+ if ( options.skipCache || options.method && DATA_METHODS.hasOwnProperty( options.method ) ) {
641
713
 
642
714
  return Fetch( url, options ).then( cacheResponse );
643
715
 
644
716
  }
645
717
 
718
+ const uuid = getUUID( url.toString() );
719
+
720
+ //HANDLE: individual records, if we are looking for a record check cache for any array or search results with this record
721
+ if ( uuid.id ) {
722
+
723
+ return cacheFindLike( { cache: _cache, url: uuid.shortPath, property: "id", value: uuid.id } ).then( ( match ) => {
724
+
725
+ if ( match ) {
726
+
727
+ //Behind the scenes still fetch the record and update the cache but don't make the user wait for it.
728
+ Fetch( url, options ).then( cacheResponse );
729
+
730
+ const responseObj = new Response( JSON.stringify( match.match ) );
731
+
732
+ return Promise.resolve( [{
733
+ request: null,
734
+ response: responseObj,
735
+ data: match.match,
736
+ status: {
737
+ code: 200,
738
+ text: '',
739
+ isAbort: false
740
+ },
741
+ }, false] )
742
+ }
743
+
744
+
745
+ return Fetch( url, options ).then( cacheResponse );
746
+
747
+ } )
748
+
749
+ }
750
+
646
751
 
647
752
  return _cache.match( url ).then( ( response ) => {
648
753
 
@@ -663,7 +768,7 @@ const _fetch = ( url, options = {} ) => {
663
768
  return Promise.resolve( [{
664
769
  request: null,
665
770
  response: response,
666
- data: obj.data,
771
+ data: obj,
667
772
  status: {
668
773
  code: response.status,
669
774
  text: response.statusText,
@@ -679,17 +784,21 @@ const _fetch = ( url, options = {} ) => {
679
784
  } )
680
785
  }
681
786
 
682
- if ( _cache ) {
683
787
 
788
+
789
+ if ( _cache ) {
684
790
  return cacheMatch()
685
791
  }
686
792
 
687
- return caches.open( 'fetch' ).then( ( cache ) => {
793
+
794
+ return caches.open( cacheName ).then( ( cache ) => {
795
+ _caches[cacheName] = cache;
688
796
  _cache = cache;
689
797
  } ).then( cacheMatch )
690
798
 
691
799
  }
692
800
 
801
+
693
802
  function deleteFromCache( _cache, url, response ) {
694
803
 
695
804
  _cache.delete( url );
@@ -697,10 +806,183 @@ function deleteFromCache( _cache, url, response ) {
697
806
 
698
807
  }
699
808
 
809
+ function cacheFindAllLike( options ) {
810
+
811
+ const finalOptions = {
812
+ ...options,
813
+ findAll: true
814
+ }
815
+
816
+ return cacheFindLike( finalOptions );
817
+ }
818
+
819
+ function cacheFindLike( options ) {
820
+
821
+ const cache = options.cache;
822
+ const url = options.url;
823
+ const property = options.property;
824
+ const value = options.value;
825
+ const findAll = options.findAll;
826
+ const matchOptions = options.matchOptions
827
+ const method = options.method || "GET";
828
+
829
+ return cache.keys().then( ( keys ) => {
830
+
831
+ const matchingRequests = [];
832
+
833
+ for ( let i = 0; i < keys.length; i++ ) {
834
+
835
+ const request = keys[i];
836
+
837
+ if ( looksLikeResultListUrl( request.url, url ) ) {
838
+
839
+ matchingRequests.push( cache.match( request, matchOptions ).then( ( match ) => {
840
+
841
+ match._request = request;
842
+
843
+ return match;
844
+ } ) )
845
+
846
+ }
847
+
848
+ }
849
+
850
+ return Promise.all( matchingRequests ).then( ( responses ) => {
851
+ let count = 0;
852
+
853
+ const ret = [];
854
+
855
+ const checkCache = async ( index ) => {
856
+
857
+ if ( index >= responses.length ) return ret;
858
+
859
+ return serializeResponse( responses[index] ).then( json => {
860
+
861
+ const items = extractDataArray( json );
862
+ const response = responses[count];
863
+
864
+ const retObj = {
865
+ request: response._request,
866
+ response: response,
867
+ data: json,
868
+ match: json,
869
+ matchIndex: -1,
870
+ status: {
871
+ code: response.status,
872
+ text: response.statusText,
873
+ isAbort: false
874
+ }
875
+ }
876
+
877
+ if ( method === "POST" ) {
878
+ return findAll ? [retObj] : retObj;
879
+ }
880
+
881
+ const matchIndex = items.findIndex( item => item[property] === value );
882
+
883
+ count++;
884
+
885
+ if ( matchIndex >= 0 ) {
886
+
887
+ retObj.match = items[matchIndex];
888
+ retObj.matchIndex = matchIndex;
889
+
890
+ if ( !findAll ) {
891
+ return retObj;
892
+ }
893
+ else {
894
+ ret.push( retObj );
895
+ }
896
+
897
+ return checkCache( count );
898
+ }
899
+
900
+
901
+ if ( count >= responses.length ) {
902
+ return findAll ? ret : null;
903
+ }
904
+
905
+ return checkCache( count );
906
+
907
+ } )
908
+ }
909
+
910
+ if ( !responses || !responses.length ) return findAll ? ret : null;
911
+
912
+ return checkCache( count );
913
+ } )
914
+ } );
915
+
916
+
917
+ }
918
+
919
+ /**
920
+ * Check if request.url looks like a list result type url, if it contains a UUID it is likely not.
921
+ * @param {*} requestURL
922
+ * @param {*} matchURL
923
+ * @returns
924
+ */
925
+ function looksLikeResultListUrl( requestURL, matchURL ) {
926
+
927
+ const uuid = getUUID( requestURL.toString() );
928
+
929
+ return !uuid.id && requestURL.indexOf( matchURL ) >= 0;
930
+ }
931
+
932
+
933
+ function extractDataArray( obj ) {
934
+ for ( var prop in obj._embedded ) {
935
+ if ( !obj._embedded.hasOwnProperty( prop ) ) continue;
936
+ return obj._embedded[prop];
937
+ }
938
+
939
+ return [obj];
940
+ }
941
+
942
+ async function serializeResponse( response ) {
943
+
944
+ return response.text().then( data => {
945
+ return JSON.parse( data );
946
+ } )
947
+
948
+ }
949
+
950
+ function getUUID( str ) {
951
+
952
+ const UUID_REG_EXP = /.+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}).*?/;
953
+ const match = UUID_REG_EXP.exec( str );
954
+
955
+ let id, shortPath;
956
+
957
+ if ( match && match[1] ) {
958
+ id = match[1];
959
+ shortPath = str.split( `/${id}` )[0];
960
+ }
961
+
962
+ return {
963
+ id: id,
964
+ shortPath: shortPath
965
+ }
966
+
967
+ }
968
+
969
+
970
+
971
+ function getCacheNameFromUrl( url ) {
972
+
973
+ const API_REG_EXP = /p\/.+com\/(.*?)\//;
974
+ const matchArray = url.toString().match( API_REG_EXP );
975
+
976
+ return matchArray && matchArray.length >= 1 ? matchArray[1] : null;
977
+
978
+ }
979
+
700
980
  function expiredCache( timeCached ) {
981
+
701
982
  const now = new Date().getTime();
702
983
 
703
984
  return Math.abs( now - timeCached ) >= CACHED_EXPIRY_TIMESTAMP;
985
+
704
986
  }
705
987
 
706
988
  export default _fetch;
package/lib/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  export { default as Download } from './Download';
2
2
  export { default as fetch, applyMiddleware, OnOKResponse } from './Fetch';
3
+
4
+ export * from "./utils/FetchUtils";
3
5
  export * from "./constants/FetchConstants";
@@ -0,0 +1,118 @@
1
+ import Fetch from '../Fetch';
2
+
3
+
4
+
5
+ export const createUrlParams = ( params, existingParams ) => {
6
+
7
+
8
+ //SORT the params alphabetically, very important for cache lookups as the search a params in different order will cause it to not be matched.
9
+ params = Object.keys( params ).sort().reduce(
10
+ ( obj, key ) => {
11
+ obj[key] = params[key];
12
+ return obj;
13
+ },
14
+ {}
15
+ );
16
+
17
+ const searchParams = existingParams ? existingParams : new URLSearchParams();
18
+
19
+ for ( var prop in params ) {
20
+ if ( !params.hasOwnProperty( prop ) || params[prop] === undefined || params[prop] === null ) continue;
21
+
22
+ if ( Array.isArray( params[prop] ) ) {
23
+
24
+ if ( prop === "sort" ) {
25
+ searchParams.delete( "sort" );
26
+ }
27
+
28
+ for ( let i = 0; i < params[prop].length; i++ ) {
29
+ const e = params[prop][i];
30
+ if ( searchParams.has( prop ) && prop !== "sort" ) {
31
+ searchParams.set( prop, e );
32
+ }
33
+ else {
34
+ searchParams.append( prop, e );
35
+ }
36
+ }
37
+ continue;
38
+ }
39
+
40
+ if ( searchParams.has( prop ) ) {
41
+ searchParams.set( prop, params[prop] );
42
+ }
43
+ else {
44
+ searchParams.append( prop, params[prop] );
45
+ }
46
+ }
47
+
48
+
49
+ return searchParams;
50
+
51
+
52
+ }
53
+
54
+
55
+ export async function fetchPagedURL( url, callback, options ) {
56
+
57
+ const finalOptions = {
58
+ skipCache: false,
59
+ ...options
60
+ }
61
+
62
+ callback = callback ? callback : () => { };
63
+
64
+ let ret = [];
65
+
66
+ const proxyUrl = url.toString().split( "/p/" );
67
+
68
+ const [response] = await Fetch( url, finalOptions );
69
+ const { data, status } = response;
70
+
71
+ const size = new TextEncoder().encode( JSON.stringify( data ) ).length;
72
+ const kiloBytes = size / 1024;
73
+ const megaBytes = kiloBytes / 1024;
74
+
75
+ if ( status.code > 299 || status.code < 200 ) {
76
+ return [response, null];
77
+ }
78
+
79
+ const results = {
80
+ page: data.page.number + 1,
81
+ totalPages: data.page.totalPages,
82
+ totalElements: data.page.totalElements,
83
+ pageSize: data.page.size,
84
+ kiloBytes: kiloBytes,
85
+ megaBytes: megaBytes
86
+ }
87
+
88
+ callback( results );
89
+
90
+ data.items = data._embedded[[Object.keys( data._embedded )[0]]];
91
+
92
+ ret = ret.concat( data.items || [] );
93
+
94
+ if ( data._links && data._links.next ) {
95
+
96
+ const nextUrl = new URL( `${proxyUrl[0]}/p/${data._links.next.href}` );
97
+
98
+ nextUrl.search = createUrlParams( {}, nextUrl.searchParams );
99
+
100
+ const [err, nextPage] = await fetchPagedURL( nextUrl, callback, options );
101
+
102
+ if ( err ) return [err, nextPage];
103
+
104
+ ret = ret.concat( nextPage );
105
+
106
+ }
107
+
108
+ return [null, ret];
109
+
110
+ }
111
+
112
+
113
+ function timeout( ms ) {
114
+
115
+ return new Promise( resolve => setTimeout( resolve, ms ) );
116
+
117
+ }
118
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selkirk-systems/fetch",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Abortable fetch library",
5
5
  "keywords": [],
6
6
  "author": "Marcos Bernal <mbernal@selkirksystems.com>",
@@ -36,5 +36,5 @@
36
36
  "peerDependencies": {
37
37
  "@selkirk-systems/state-management": ">=1.0.0"
38
38
  },
39
- "gitHead": "5898e1276808bd3fe25d44f3935befe00c54513e"
39
+ "gitHead": "d981c2a39edb04be17105f9c328942380ccb32f7"
40
40
  }