canada-api 1.1.6 → 2.0.4

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 National Defence
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,92 +1,104 @@
1
1
  # canada-api
2
2
 
3
+ [![NPM Version](https://img.shields.io/npm/v/canada-api?branch=main)](https://www.npmjs.com/package/canada-api) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/dnd-mdn/canada-api/blob/main/LICENSE.md)
4
+
3
5
  Cross platform API for fetching public data from [canada.ca](https://www.canada.ca).
4
6
 
5
7
  ## Install
6
8
  ### Browsers
7
9
 
8
- <script src="https://cdn.jsdelivr.net/npm/canada-api@1.1.6"></script>
10
+ ```html
11
+ <script src="https://cdn.jsdelivr.net/npm/canada-api@2.0.4"></script>
12
+ <script>
13
+ ca.meta("en/department-national-defence").then(meta => {
14
+ console.log(meta)
15
+ })
16
+ </script>
17
+ ```
9
18
 
10
19
  ### Nodejs
11
20
 
12
- npm install canada-api
21
+ ```javascript
22
+ const ca = require("canada-api")
23
+ ca.meta("en/department-national-defence")
24
+ ```
13
25
 
14
- ## Usage
15
- ### Browsers
16
26
 
17
- <script>
18
- ca.meta("en/department-national-defence")
19
- </script>
20
27
 
21
- ### Nodejs
28
+ ## Core API
22
29
 
23
- const ca = require("canada-api")
24
- ca.meta("en/department-national-defence")
30
+ ### `ca.fetch(url[, options])`
25
31
 
26
- ## API
27
- ### ca.children(node)
32
+ - `url` {string|URL} absolute URL
33
+ - `options` {Object} fetch [options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options) (Some limitations based on implementation)
34
+ - `jobOptions` {Object} rate limiter [job options](https://github.com/SGrondin/bottleneck#job-options)
35
+ - Returns: {Promise} Fulfills with {Response} upon success
28
36
 
29
- Uses sitemaps to fetch an array of child nodes
37
+ Uses [cross-fetch](https://github.com/lquixada/cross-fetch#readme) for a universal [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Calls are rate limited to avoid hitting request limits that result in throttling. Throws an {Error} if the request does not complete successfully or if the destination URL is not on [canada.ca](https://www.canada.ca).
30
38
 
31
- ca.children("/en/department-national-defence/maple-leaf")
32
- ca.children("/fr/ministere-defense-nationale/feuille-derable")
33
39
 
34
- Returns: `Promise` Resolves: node object with `children` property
40
+ ### `ca.fetch.limiter`
35
41
 
36
- Only for page nodes, other types return an error `Not Found`
42
+ - {Bottleneck}
37
43
 
38
- ### ca.meta(node)
44
+ The [Rate limiter](https://github.com/SGrondin/bottleneck#readme) used in `ca.fetch()`.
39
45
 
40
- Fetches metadata of a node
41
46
 
42
- ca.meta("/en/department-national-defence/maple-leaf")
43
- ca.meta("/content/dam/dnd-mdn/images/maple-leaf/ml-logo.jpg")
47
+ ### `ca.normalize(url[, type])`
44
48
 
45
- Returns: `Promise` Resolves: node object with `meta` property
49
+ - `url` {string|URL} node URL
50
+ - `type` {string} Possible values `'path'`, `'children'`, `'content'` or `'meta'`. **Default**: `'path'`
51
+ - Returns: {string} Normalized path or URL
46
52
 
47
- ### ca.content(node)
53
+ Validates and formats [canada.ca](https://www.canada.ca) URLs based on type. URLs can take many forms, so not all options will be valid. Throws an {Error} if the URL is invalid, or the type requested is not possible.
48
54
 
49
- Fetches content of a node, as text or json
50
55
 
51
- ca.content("/en/department-national-defence/maple-leaf")
52
- ca.content("/content/dam/dnd-mdn/documents/json/maple-en.json")
56
+ ### `ca.normalize.baseURL`
53
57
 
54
- Returns: `Promise` Resolves: node object with `content` property
58
+ - {string}
55
59
 
56
- ### ca.normalizeNode
57
- ### ca.normalizePath
60
+ Base URL used for resolving relative URLs as well as URL validation. Value is `'https://www.canada.ca'`.
58
61
 
59
62
 
60
- ## Arguments
61
63
 
62
- All functions take a node argument which accepts a couple types
64
+ ## Basic API
63
65
 
64
- ### path string
66
+ ### `ca.children(url[, options])`
65
67
 
66
- Valid path strings include:
68
+ - `url` {string|URL} absolute or relative URL
69
+ - `options` {Object} fetch [options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options)
70
+ - `jobOptions` {Object} rate limiter [job options](https://github.com/SGrondin/bottleneck#job-options)
71
+ - `rawContent` {boolean} Fulfills with unmodified {string} **Default:** `false`
72
+ - Returns: {Promise} Fulfills with {Object[]} of child nodes
67
73
 
68
- en/department-national-defence
69
- /en/department-national-defence.html
70
- /en/department-national-defence?param=ignored
71
-
72
- https://www.canada.ca/en/department-national-defence.html
74
+ Parses sitemaps to get a list of child nodes.
73
75
 
74
- content/canadasite/department-national-defence
75
- /content/canadasite/department-national-defence/
76
76
 
77
- content/dam/dnd-mdn/documents/json/maple-en.json
78
- /content/dam/dnd-mdn/images/maple-leaf/ml-logo.jpg
77
+ ### `ca.content(url[, options])`
79
78
 
80
- ### node object
79
+ - `url` {string|URL} absolute or relative URL
80
+ - `options` {Object} fetch [options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options)
81
+ - `jobOptions` {Object} rate limiter [job options](https://github.com/SGrondin/bottleneck#job-options)
82
+ - `rawContent` {boolean} Fulfills with unmodified {string} **Default:** `false`
83
+ - Returns: {Promise} Fulfills with {string|Object}
81
84
 
82
- A node object can passed and it will be extended with the addition properties
85
+ Retrieves the document contents. The result depends on the `content-type` header of {Response}:
86
+ - `'application/json'` parses response and fulfills with {Object}
87
+ - `'text/html'` compresses whitespace and fulfills with {string}
88
+ - Other types are fulfilled as {string} with no modification
83
89
 
84
- var node = { path: '/en/department-national-defence/maple-leaf' }
85
90
 
86
- Promise.all([
87
- ca.meta(node),
88
- ca.content(node)
89
- ]).then(function() {
90
- // object exented with meta and content properties
91
- console.log(node)
92
- })
91
+ ### `ca.meta(url[, options])`
92
+
93
+ - `url` {string|URL} absolute or relative URL
94
+ - `options` {Object} fetch [options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options)
95
+ - `jobOptions` {Object} rate limiter [job options](https://github.com/SGrondin/bottleneck#job-options)
96
+ - `rawContent` {boolean} Fulfills with unmodified {string} **Default:** `false`
97
+ - Returns: {Promise} Fulfills with {Object} with metadata properties
98
+
99
+ Nodes contain a variety of metadata properties that can be accessed through a public API. Some properties are reformatted for consistency if `rawContent` option is `false`. A separate document will be created as a reference for the most useful ones.
100
+
101
+
102
+ ## Extended API
103
+
104
+ TBD
package/dist/ca.js CHANGED
@@ -1 +1 @@
1
- !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("cross-fetch")):"function"==typeof define&&define.amd?define("ca",["cross-fetch"],e):"object"==typeof exports?exports.ca=e(require("cross-fetch")):t.ca=e(t.fetch)}("undefined"!=typeof self?self:this,(t=>(()=>{var e={209:function(t,e,s){t.exports=function(){"use strict";var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==s.g?s.g:"undefined"!=typeof self?self:{};var e,r,i={load:function(t,e,s={}){var r,i,n;for(r in e)n=e[r],s[r]=null!=(i=t[r])?i:n;return s},overwrite:function(t,e,s={}){var r,i;for(r in t)i=t[r],void 0!==e[r]&&(s[r]=i);return s}},n=class{constructor(t,e){this.incr=t,this.decr=e,this._first=null,this._last=null,this.length=0}push(t){var e;this.length++,"function"==typeof this.incr&&this.incr(),e={value:t,prev:this._last,next:null},null!=this._last?(this._last.next=e,this._last=e):this._first=this._last=e}shift(){var t;if(null!=this._first)return this.length--,"function"==typeof this.decr&&this.decr(),t=this._first.value,null!=(this._first=this._first.next)?this._first.prev=null:this._last=null,t}first(){if(null!=this._first)return this._first.value}getArray(){var t,e,s;for(t=this._first,s=[];null!=t;)s.push((e=t,t=t.next,e.value));return s}forEachShift(t){var e;for(e=this.shift();null!=e;)t(e),e=this.shift()}debug(){var t,e,s,r,i;for(t=this._first,i=[];null!=t;)i.push((e=t,t=t.next,{value:e.value,prev:null!=(s=e.prev)?s.value:void 0,next:null!=(r=e.next)?r.value:void 0}));return i}},o=class{constructor(t){if(this.instance=t,this._events={},null!=this.instance.on||null!=this.instance.once||null!=this.instance.removeAllListeners)throw new Error("An Emitter already exists for this object");this.instance.on=(t,e)=>this._addListener(t,"many",e),this.instance.once=(t,e)=>this._addListener(t,"once",e),this.instance.removeAllListeners=(t=null)=>null!=t?delete this._events[t]:this._events={}}_addListener(t,e,s){var r;return null==(r=this._events)[t]&&(r[t]=[]),this._events[t].push({cb:s,status:e}),this.instance}listenerCount(t){return null!=this._events[t]?this._events[t].length:0}async trigger(t,...e){var s,r;try{if("debug"!==t&&this.trigger("debug",`Event triggered: ${t}`,e),null==this._events[t])return;return this._events[t]=this._events[t].filter((function(t){return"none"!==t.status})),r=this._events[t].map((async t=>{var s,r;if("none"!==t.status){"once"===t.status&&(t.status="none");try{return"function"==typeof(null!=(r="function"==typeof t.cb?t.cb(...e):void 0)?r.then:void 0)?await r:r}catch(t){return s=t,this.trigger("error",s),null}}})),(await Promise.all(r)).find((function(t){return null!=t}))}catch(t){return s=t,this.trigger("error",s),null}}};e=n,r=o;var h,a,u=class extends Error{};a=i,h=u;var l,c,d=class{constructor(t,e,s,r,i,n,o,h){this.task=t,this.args=e,this.rejectOnDrop=i,this.Events=n,this._states=o,this.Promise=h,this.options=a.load(s,r),this.options.priority=this._sanitizePriority(this.options.priority),this.options.id===r.id&&(this.options.id=`${this.options.id}-${this._randomIndex()}`),this.promise=new this.Promise(((t,e)=>{this._resolve=t,this._reject=e})),this.retryCount=0}_sanitizePriority(t){var e;return(e=~~t!==t?5:t)<0?0:e>9?9:e}_randomIndex(){return Math.random().toString(36).slice(2)}doDrop({error:t,message:e="This job has been dropped by Bottleneck"}={}){return!!this._states.remove(this.options.id)&&(this.rejectOnDrop&&this._reject(null!=t?t:new h(e)),this.Events.trigger("dropped",{args:this.args,options:this.options,task:this.task,promise:this.promise}),!0)}_assertStatus(t){var e;if((e=this._states.jobStatus(this.options.id))!==t&&("DONE"!==t||null!==e))throw new h(`Invalid job status ${e}, expected ${t}. Please open an issue at https://github.com/SGrondin/bottleneck/issues`)}doReceive(){return this._states.start(this.options.id),this.Events.trigger("received",{args:this.args,options:this.options})}doQueue(t,e){return this._assertStatus("RECEIVED"),this._states.next(this.options.id),this.Events.trigger("queued",{args:this.args,options:this.options,reachedHWM:t,blocked:e})}doRun(){return 0===this.retryCount?(this._assertStatus("QUEUED"),this._states.next(this.options.id)):this._assertStatus("EXECUTING"),this.Events.trigger("scheduled",{args:this.args,options:this.options})}async doExecute(t,e,s,r){var i,n,o;0===this.retryCount?(this._assertStatus("RUNNING"),this._states.next(this.options.id)):this._assertStatus("EXECUTING"),n={args:this.args,options:this.options,retryCount:this.retryCount},this.Events.trigger("executing",n);try{if(o=await(null!=t?t.schedule(this.options,this.task,...this.args):this.task(...this.args)),e())return this.doDone(n),await r(this.options,n),this._assertStatus("DONE"),this._resolve(o)}catch(t){return i=t,this._onFailure(i,n,e,s,r)}}doExpire(t,e,s){var r,i;return this._states.jobStatus("RUNNING"===this.options.id)&&this._states.next(this.options.id),this._assertStatus("EXECUTING"),i={args:this.args,options:this.options,retryCount:this.retryCount},r=new h(`This job timed out after ${this.options.expiration} ms.`),this._onFailure(r,i,t,e,s)}async _onFailure(t,e,s,r,i){var n,o;if(s())return null!=(n=await this.Events.trigger("failed",t,e))?(o=~~n,this.Events.trigger("retry",`Retrying ${this.options.id} after ${o} ms`,e),this.retryCount++,r(o)):(this.doDone(e),await i(this.options,e),this._assertStatus("DONE"),this._reject(t))}doDone(t){return this._assertStatus("EXECUTING"),this._states.next(this.options.id),this.Events.trigger("done",t)}};c=i,l=u;var p;p=u;var _;_=n;var v,f,m,g,y,b="2.19.5",w={version:b},E=Object.freeze({version:b,default:w}),O=()=>console.log("You must import the full version of Bottleneck in order to use this feature."),x=()=>console.log("You must import the full version of Bottleneck in order to use this feature.");y=i,v=o,m=O,f=x,g=()=>console.log("You must import the full version of Bottleneck in order to use this feature.");var j,k,I=function(){class t{constructor(t={}){this.deleteKey=this.deleteKey.bind(this),this.limiterOptions=t,y.load(this.limiterOptions,this.defaults,this),this.Events=new v(this),this.instances={},this.Bottleneck=M,this._startAutoCleanup(),this.sharedConnection=null!=this.connection,null==this.connection&&("redis"===this.limiterOptions.datastore?this.connection=new m(Object.assign({},this.limiterOptions,{Events:this.Events})):"ioredis"===this.limiterOptions.datastore&&(this.connection=new f(Object.assign({},this.limiterOptions,{Events:this.Events}))))}key(t=""){var e;return null!=(e=this.instances[t])?e:(()=>{var e;return e=this.instances[t]=new this.Bottleneck(Object.assign(this.limiterOptions,{id:`${this.id}-${t}`,timeout:this.timeout,connection:this.connection})),this.Events.trigger("created",e,t),e})()}async deleteKey(t=""){var e,s;return s=this.instances[t],this.connection&&(e=await this.connection.__runCommand__(["del",...g.allKeys(`${this.id}-${t}`)])),null!=s&&(delete this.instances[t],await s.disconnect()),null!=s||e>0}limiters(){var t,e,s,r;for(t in s=[],e=this.instances)r=e[t],s.push({key:t,limiter:r});return s}keys(){return Object.keys(this.instances)}async clusterKeys(){var t,e,s,r,i,n,o,h,a;if(null==this.connection)return this.Promise.resolve(this.keys());for(n=[],t=null,a=`b_${this.id}-`.length,e="_settings".length;0!==t;)for([h,s]=await this.connection.__runCommand__(["scan",null!=t?t:0,"match",`b_${this.id}-*_settings`,"count",1e4]),t=~~h,r=0,o=s.length;r<o;r++)i=s[r],n.push(i.slice(a,-e));return n}_startAutoCleanup(){var t;return clearInterval(this.interval),"function"==typeof(t=this.interval=setInterval((async()=>{var t,e,s,r,i,n;for(e in i=Date.now(),r=[],s=this.instances){n=s[e];try{await n._store.__groupCheck__(i)?r.push(this.deleteKey(e)):r.push(void 0)}catch(e){t=e,r.push(n.Events.trigger("error",t))}}return r}),this.timeout/2)).unref?t.unref():void 0}updateSettings(t={}){if(y.overwrite(t,this.defaults,this),y.overwrite(t,t,this.limiterOptions),null!=t.timeout)return this._startAutoCleanup()}disconnect(t=!0){var e;if(!this.sharedConnection)return null!=(e=this.connection)?e.disconnect(t):void 0}}return t.prototype.defaults={timeout:3e5,connection:null,Promise,id:"group-key"},t}.call(t);k=i,j=o;var R,D,C,T,P,S,L,A,q,$=function(){class t{constructor(t={}){this.options=t,k.load(this.options,this.defaults,this),this.Events=new j(this),this._arr=[],this._resetPromise(),this._lastFlush=Date.now()}_resetPromise(){return this._promise=new this.Promise(((t,e)=>this._resolve=t))}_flush(){return clearTimeout(this._timeout),this._lastFlush=Date.now(),this._resolve(),this.Events.trigger("batch",this._arr),this._arr=[],this._resetPromise()}add(t){var e;return this._arr.push(t),e=this._promise,this._arr.length===this.maxSize?this._flush():null!=this.maxTime&&1===this._arr.length&&(this._timeout=setTimeout((()=>this._flush()),this.maxTime)),e}}return t.prototype.defaults={maxTime:null,maxSize:null,Promise},t}.call(t),N=(q=E)&&q.default||q,B=[].splice;A=i,T=class{constructor(t){this.Events=new r(this),this._length=0,this._lists=function(){var s,r,i;for(i=[],s=1,r=t;1<=r?s<=r:s>=r;1<=r?++s:--s)i.push(new e((()=>this.incr()),(()=>this.decr())));return i}.call(this)}incr(){if(0==this._length++)return this.Events.trigger("leftzero")}decr(){if(0==--this._length)return this.Events.trigger("zero")}push(t){return this._lists[t.options.priority].push(t)}queued(t){return null!=t?this._lists[t].length:this._length}shiftAll(t){return this._lists.forEach((function(e){return e.forEachShift(t)}))}getFirst(t=this._lists){var e,s,r;for(e=0,s=t.length;e<s;e++)if((r=t[e]).length>0)return r;return[]}shiftLastFrom(t){return this.getFirst(this._lists.slice(t).reverse()).shift()}},D=d,C=class{constructor(t,e,s){this.instance=t,this.storeOptions=e,this.clientId=this.instance._randomIndex(),c.load(s,s,this),this._nextRequest=this._lastReservoirRefresh=this._lastReservoirIncrease=Date.now(),this._running=0,this._done=0,this._unblockTime=0,this.ready=this.Promise.resolve(),this.clients={},this._startHeartbeat()}_startHeartbeat(){var t;return null==this.heartbeat&&(null!=this.storeOptions.reservoirRefreshInterval&&null!=this.storeOptions.reservoirRefreshAmount||null!=this.storeOptions.reservoirIncreaseInterval&&null!=this.storeOptions.reservoirIncreaseAmount)?"function"==typeof(t=this.heartbeat=setInterval((()=>{var t,e,s,r,i;if(r=Date.now(),null!=this.storeOptions.reservoirRefreshInterval&&r>=this._lastReservoirRefresh+this.storeOptions.reservoirRefreshInterval&&(this._lastReservoirRefresh=r,this.storeOptions.reservoir=this.storeOptions.reservoirRefreshAmount,this.instance._drainAll(this.computeCapacity())),null!=this.storeOptions.reservoirIncreaseInterval&&r>=this._lastReservoirIncrease+this.storeOptions.reservoirIncreaseInterval&&(({reservoirIncreaseAmount:t,reservoirIncreaseMaximum:s,reservoir:i}=this.storeOptions),this._lastReservoirIncrease=r,(e=null!=s?Math.min(t,s-i):t)>0))return this.storeOptions.reservoir+=e,this.instance._drainAll(this.computeCapacity())}),this.heartbeatInterval)).unref?t.unref():void 0:clearInterval(this.heartbeat)}async __publish__(t){return await this.yieldLoop(),this.instance.Events.trigger("message",t.toString())}async __disconnect__(t){return await this.yieldLoop(),clearInterval(this.heartbeat),this.Promise.resolve()}yieldLoop(t=0){return new this.Promise((function(e,s){return setTimeout(e,t)}))}computePenalty(){var t;return null!=(t=this.storeOptions.penalty)?t:15*this.storeOptions.minTime||5e3}async __updateSettings__(t){return await this.yieldLoop(),c.overwrite(t,t,this.storeOptions),this._startHeartbeat(),this.instance._drainAll(this.computeCapacity()),!0}async __running__(){return await this.yieldLoop(),this._running}async __queued__(){return await this.yieldLoop(),this.instance.queued()}async __done__(){return await this.yieldLoop(),this._done}async __groupCheck__(t){return await this.yieldLoop(),this._nextRequest+this.timeout<t}computeCapacity(){var t,e;return({maxConcurrent:t,reservoir:e}=this.storeOptions),null!=t&&null!=e?Math.min(t-this._running,e):null!=t?t-this._running:null!=e?e:null}conditionsCheck(t){var e;return null==(e=this.computeCapacity())||t<=e}async __incrementReservoir__(t){var e;return await this.yieldLoop(),e=this.storeOptions.reservoir+=t,this.instance._drainAll(this.computeCapacity()),e}async __currentReservoir__(){return await this.yieldLoop(),this.storeOptions.reservoir}isBlocked(t){return this._unblockTime>=t}check(t,e){return this.conditionsCheck(t)&&this._nextRequest-e<=0}async __check__(t){var e;return await this.yieldLoop(),e=Date.now(),this.check(t,e)}async __register__(t,e,s){var r,i;return await this.yieldLoop(),r=Date.now(),this.conditionsCheck(e)?(this._running+=e,null!=this.storeOptions.reservoir&&(this.storeOptions.reservoir-=e),i=Math.max(this._nextRequest-r,0),this._nextRequest=r+i+this.storeOptions.minTime,{success:!0,wait:i,reservoir:this.storeOptions.reservoir}):{success:!1}}strategyIsBlock(){return 3===this.storeOptions.strategy}async __submit__(t,e){var s,r,i;if(await this.yieldLoop(),null!=this.storeOptions.maxConcurrent&&e>this.storeOptions.maxConcurrent)throw new l(`Impossible to add a job having a weight of ${e} to a limiter having a maxConcurrent setting of ${this.storeOptions.maxConcurrent}`);return r=Date.now(),i=null!=this.storeOptions.highWater&&t===this.storeOptions.highWater&&!this.check(e,r),(s=this.strategyIsBlock()&&(i||this.isBlocked(r)))&&(this._unblockTime=r+this.computePenalty(),this._nextRequest=this._unblockTime+this.storeOptions.minTime,this.instance._dropAllQueued()),{reachedHWM:i,blocked:s,strategy:this.storeOptions.strategy}}async __free__(t,e){return await this.yieldLoop(),this._running-=e,this._done+=e,this.instance._drainAll(this.computeCapacity()),{running:this._running}}},P=()=>console.log("You must import the full version of Bottleneck in order to use this feature."),R=o,S=class{constructor(t){this.status=t,this._jobs={},this.counts=this.status.map((function(){return 0}))}next(t){var e,s;return s=(e=this._jobs[t])+1,null!=e&&s<this.status.length?(this.counts[e]--,this.counts[s]++,this._jobs[t]++):null!=e?(this.counts[e]--,delete this._jobs[t]):void 0}start(t){return 0,this._jobs[t]=0,this.counts[0]++}remove(t){var e;return null!=(e=this._jobs[t])&&(this.counts[e]--,delete this._jobs[t]),null!=e}jobStatus(t){var e;return null!=(e=this.status[this._jobs[t]])?e:null}statusJobs(t){var e,s,r,i;if(null!=t){if((s=this.status.indexOf(t))<0)throw new p(`status must be one of ${this.status.join(", ")}`);for(e in i=[],r=this._jobs)r[e]===s&&i.push(e);return i}return Object.keys(this._jobs)}statusCounts(){return this.counts.reduce(((t,e,s)=>(t[this.status[s]]=e,t)),{})}},L=class{constructor(t,e){this.schedule=this.schedule.bind(this),this.name=t,this.Promise=e,this._running=0,this._queue=new _}isEmpty(){return 0===this._queue.length}async _tryToRun(){var t,e,s,r,i,n,o;if(this._running<1&&this._queue.length>0)return this._running++,({task:o,args:t,resolve:i,reject:r}=this._queue.shift()),e=await async function(){try{return n=await o(...t),function(){return i(n)}}catch(t){return s=t,function(){return r(s)}}}(),this._running--,this._tryToRun(),e()}schedule(t,...e){var s,r,i;return i=r=null,s=new this.Promise((function(t,e){return i=t,r=e})),this._queue.push({task:t,args:e,resolve:i,reject:r}),this._tryToRun(),s}};var M=function(){class t{constructor(e={},...s){var r,i;this._addToQueue=this._addToQueue.bind(this),this._validateOptions(e,s),A.load(e,this.instanceDefaults,this),this._queues=new T(10),this._scheduled={},this._states=new S(["RECEIVED","QUEUED","RUNNING","EXECUTING"].concat(this.trackDoneStatus?["DONE"]:[])),this._limiter=null,this.Events=new R(this),this._submitLock=new L("submit",this.Promise),this._registerLock=new L("register",this.Promise),i=A.load(e,this.storeDefaults,{}),this._store=function(){if("redis"===this.datastore||"ioredis"===this.datastore||null!=this.connection)return r=A.load(e,this.redisStoreDefaults,{}),new P(this,i,r);if("local"===this.datastore)return r=A.load(e,this.localStoreDefaults,{}),new C(this,i,r);throw new t.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`)}.call(this),this._queues.on("leftzero",(()=>{var t;return null!=(t=this._store.heartbeat)&&"function"==typeof t.ref?t.ref():void 0})),this._queues.on("zero",(()=>{var t;return null!=(t=this._store.heartbeat)&&"function"==typeof t.unref?t.unref():void 0}))}_validateOptions(e,s){if(null==e||"object"!=typeof e||0!==s.length)throw new t.prototype.BottleneckError("Bottleneck v2 takes a single object argument. Refer to https://github.com/SGrondin/bottleneck#upgrading-to-v2 if you're upgrading from Bottleneck v1.")}ready(){return this._store.ready}clients(){return this._store.clients}channel(){return`b_${this.id}`}channel_client(){return`b_${this.id}_${this._store.clientId}`}publish(t){return this._store.__publish__(t)}disconnect(t=!0){return this._store.__disconnect__(t)}chain(t){return this._limiter=t,this}queued(t){return this._queues.queued(t)}clusterQueued(){return this._store.__queued__()}empty(){return 0===this.queued()&&this._submitLock.isEmpty()}running(){return this._store.__running__()}done(){return this._store.__done__()}jobStatus(t){return this._states.jobStatus(t)}jobs(t){return this._states.statusJobs(t)}counts(){return this._states.statusCounts()}_randomIndex(){return Math.random().toString(36).slice(2)}check(t=1){return this._store.__check__(t)}_clearGlobalState(t){return null!=this._scheduled[t]&&(clearTimeout(this._scheduled[t].expiration),delete this._scheduled[t],!0)}async _free(t,e,s,r){var i,n;try{if(({running:n}=await this._store.__free__(t,s.weight)),this.Events.trigger("debug",`Freed ${s.id}`,r),0===n&&this.empty())return this.Events.trigger("idle")}catch(t){return i=t,this.Events.trigger("error",i)}}_run(t,e,s){var r,i,n;return e.doRun(),r=this._clearGlobalState.bind(this,t),n=this._run.bind(this,t,e),i=this._free.bind(this,t,e),this._scheduled[t]={timeout:setTimeout((()=>e.doExecute(this._limiter,r,n,i)),s),expiration:null!=e.options.expiration?setTimeout((function(){return e.doExpire(r,n,i)}),s+e.options.expiration):void 0,job:e}}_drainOne(t){return this._registerLock.schedule((()=>{var e,s,r,i,n;return 0===this.queued()?this.Promise.resolve(null):(n=this._queues.getFirst(),({options:i,args:e}=r=n.first()),null!=t&&i.weight>t?this.Promise.resolve(null):(this.Events.trigger("debug",`Draining ${i.id}`,{args:e,options:i}),s=this._randomIndex(),this._store.__register__(s,i.weight,i.expiration).then((({success:t,wait:o,reservoir:h})=>{var a;return this.Events.trigger("debug",`Drained ${i.id}`,{success:t,args:e,options:i}),t?(n.shift(),(a=this.empty())&&this.Events.trigger("empty"),0===h&&this.Events.trigger("depleted",a),this._run(s,r,o),this.Promise.resolve(i.weight)):this.Promise.resolve(null)}))))}))}_drainAll(t,e=0){return this._drainOne(t).then((s=>{var r;return null!=s?(r=null!=t?t-s:t,this._drainAll(r,e+s)):this.Promise.resolve(e)})).catch((t=>this.Events.trigger("error",t)))}_dropAllQueued(t){return this._queues.shiftAll((function(e){return e.doDrop({message:t})}))}stop(e={}){var s,r;return e=A.load(e,this.stopDefaults),r=t=>{var e;return e=()=>{var e;return(e=this._states.counts)[0]+e[1]+e[2]+e[3]===t},new this.Promise(((t,s)=>e()?t():this.on("done",(()=>{if(e())return this.removeAllListeners("done"),t()}))))},s=e.dropWaitingJobs?(this._run=function(t,s){return s.doDrop({message:e.dropErrorMessage})},this._drainOne=()=>this.Promise.resolve(null),this._registerLock.schedule((()=>this._submitLock.schedule((()=>{var t,s,i;for(t in s=this._scheduled)i=s[t],"RUNNING"===this.jobStatus(i.job.options.id)&&(clearTimeout(i.timeout),clearTimeout(i.expiration),i.job.doDrop({message:e.dropErrorMessage}));return this._dropAllQueued(e.dropErrorMessage),r(0)}))))):this.schedule({priority:9,weight:0},(()=>r(1))),this._receive=function(s){return s._reject(new t.prototype.BottleneckError(e.enqueueErrorMessage))},this.stop=()=>this.Promise.reject(new t.prototype.BottleneckError("stop() has already been called")),s}async _addToQueue(e){var s,r,i,n,o,h,a;({args:s,options:n}=e);try{({reachedHWM:o,blocked:r,strategy:a}=await this._store.__submit__(this.queued(),n.weight))}catch(t){return i=t,this.Events.trigger("debug",`Could not queue ${n.id}`,{args:s,options:n,error:i}),e.doDrop({error:i}),!1}return r?(e.doDrop(),!0):o&&(null!=(h=a===t.prototype.strategy.LEAK?this._queues.shiftLastFrom(n.priority):a===t.prototype.strategy.OVERFLOW_PRIORITY?this._queues.shiftLastFrom(n.priority+1):a===t.prototype.strategy.OVERFLOW?e:void 0)&&h.doDrop(),null==h||a===t.prototype.strategy.OVERFLOW)?(null==h&&e.doDrop(),o):(e.doQueue(o,r),this._queues.push(e),await this._drainAll(),o)}_receive(e){return null!=this._states.jobStatus(e.options.id)?(e._reject(new t.prototype.BottleneckError(`A job with the same id already exists (id=${e.options.id})`)),!1):(e.doReceive(),this._submitLock.schedule(this._addToQueue,e))}submit(...t){var e,s,r,i,n,o,h;return"function"==typeof t[0]?(n=t,[s,...t]=n,[e]=B.call(t,-1),i=A.load({},this.jobDefaults)):(o=t,[i,s,...t]=o,[e]=B.call(t,-1),i=A.load(i,this.jobDefaults)),h=(...t)=>new this.Promise((function(e,r){return s(...t,(function(...t){return(null!=t[0]?r:e)(t)}))})),(r=new D(h,t,i,this.jobDefaults,this.rejectOnDrop,this.Events,this._states,this.Promise)).promise.then((function(t){return"function"==typeof e?e(...t):void 0})).catch((function(t){return Array.isArray(t)?"function"==typeof e?e(...t):void 0:"function"==typeof e?e(t):void 0})),this._receive(r)}schedule(...t){var e,s,r;return"function"==typeof t[0]?([r,...t]=t,s={}):[s,r,...t]=t,e=new D(r,t,s,this.jobDefaults,this.rejectOnDrop,this.Events,this._states,this.Promise),this._receive(e),e.promise}wrap(t){var e,s;return e=this.schedule.bind(this),(s=function(...s){return e(t.bind(this),...s)}).withOptions=function(s,...r){return e(s,t,...r)},s}async updateSettings(t={}){return await this._store.__updateSettings__(A.overwrite(t,this.storeDefaults)),A.overwrite(t,this.instanceDefaults,this),this}currentReservoir(){return this._store.__currentReservoir__()}incrementReservoir(t=0){return this._store.__incrementReservoir__(t)}}return t.default=t,t.Events=R,t.version=t.prototype.version=N.version,t.strategy=t.prototype.strategy={LEAK:1,OVERFLOW:2,OVERFLOW_PRIORITY:4,BLOCK:3},t.BottleneckError=t.prototype.BottleneckError=u,t.Group=t.prototype.Group=I,t.RedisConnection=t.prototype.RedisConnection=O,t.IORedisConnection=t.prototype.IORedisConnection=x,t.Batcher=t.prototype.Batcher=$,t.prototype.jobDefaults={priority:5,weight:1,expiration:null,id:"<no-id>"},t.prototype.storeDefaults={maxConcurrent:null,minTime:0,highWater:null,strategy:t.prototype.strategy.LEAK,penalty:null,reservoir:null,reservoirRefreshInterval:null,reservoirRefreshAmount:null,reservoirIncreaseInterval:null,reservoirIncreaseAmount:null,reservoirIncreaseMaximum:null},t.prototype.localStoreDefaults={Promise,timeout:null,heartbeatInterval:250},t.prototype.redisStoreDefaults={Promise,timeout:null,heartbeatInterval:5e3,clientTimeout:1e4,Redis:null,clientOptions:{},clusterNodes:null,clearDatastore:!1,connection:null},t.prototype.instanceDefaults={datastore:"local",connection:null,id:"<no-id>",rejectOnDrop:!0,trackDoneStatus:!1,Promise},t.prototype.stopDefaults={enqueueErrorMessage:"This limiter has been stopped and cannot accept new jobs.",dropWaitingJobs:!0,dropErrorMessage:"This limiter has been stopped."},t}.call(t);return M}()},268:e=>{"use strict";e.exports=t}},s={};function r(t){var i=s[t];if(void 0!==i)return i.exports;var n=s[t]={exports:{}};return e[t].call(n.exports,n,n.exports,r),n.exports}r.d=(t,e)=>{for(var s in e)r.o(e,s)&&!r.o(t,s)&&Object.defineProperty(t,s,{enumerable:!0,get:e[s]})},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var i={};return(()=>{"use strict";r.r(i),r.d(i,{children:()=>c,content:()=>p,limiter:()=>o,meta:()=>d,normalizeNode:()=>u,normalizePath:()=>a});var t=r(268),e=r(209);const s="www.canada.ca",n={Jan:"01",Feb:"02",Mar:"03",Apr:"04",May:"05",Jun:"06",Jul:"07",Aug:"08",Sep:"09",Oct:"10",Nov:"11",Dec:"12"},o=new e({reservoir:150,reservoirRefreshAmount:150,reservoirRefreshInterval:5e3,maxConcurrent:10,trackDoneStatus:!0});function h(t,e){return"https://"+s+t+e+"?_="+Date.now()}function a(t){if((t=new URL(t,"https://"+s)).hostname!==s)throw new Error("Invalid hostname "+t.hostname);if(t.pathname.startsWith("/content/dam/"))return t.pathname;if(t.pathname.startsWith("/content/canadasite/")&&(t.pathname=t.pathname.substr(19)),/^\/(en|fr)(\/|$)/.test(t.pathname))return t.pathname.replace(/\.[^\.]+$/,"").replace(/\/$/,"");throw new Error("Invalid path "+t)}function u(t){if("string"==typeof t)return{path:a(t)};if("object"==typeof t&&"string"==typeof t.path)return t.path=a(t.path),t;throw new Error("Invalid node")}function l(t){if(!t.ok)throw new Error(t.statusText);if(!t.url.includes(s))throw new Error("Redirect");if(t.url.includes("/404.html"))throw new Error("Not Found");return t}function c(e){return e=u(e),o.schedule({expiration:6e4,priority:4},(e=>t(e)),h(e.path,".sitemap.xml")).then(l).then((t=>t.text())).then((t=>(e.children=t.match(/<url>.*?<\/url>/g).map((t=>{let e=t.match(/<loc>([^<]+)<\/loc>/),s=t.match(/<lastmod>([^<]+)<\/lastmod>/);return{path:a(e[1]),lastmod:s?Date.parse(s[1]):null}})),e.children.length&&e.children[0].path===e.path&&(e.lastmod=e.children.shift().lastmod),e)))}function d(e){return e=u(e),o.schedule({expiration:3e4},(e=>t(e)),h(e.path,"/jcr:content.json")).then(l).then((t=>t.json())).then((t=>(Object.keys(t).forEach((e=>{"true"===t[e]?t[e]=!0:"false"===t[e]?t[e]=!1:e.endsWith("@TypeHint")?delete t[e]:"string"==typeof t[e]&&(t[e]=function(t){if(/^\d{4}-\d{2}-\d{2}$/.test(t))return Date.parse(t);let e=/^\w{3} (\w{3}) (\d{2}) (\d{4}) ([\d:]{8}) GMT([\-+]\d{4})$/.exec(t);return e?Date.parse(e[3]+"-"+n[e[1]]+"-"+e[2]+"T"+e[4]+e[5]):t}(t[e]))})),e.meta=t,e)))}function p(e){let s=(e=u(e)).path.startsWith("/content/dam/")?"":".html";return o.schedule({expiration:3e4},(e=>t(e)),h(e.path,s)).then(l).then((t=>t.headers.get("Content-Type").includes("application/json")?t.json():t.text())).then((t=>(e.content=t,e)))}})(),i})()));
1
+ !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("cross-fetch")):"function"==typeof define&&define.amd?define("ca",["cross-fetch"],e):"object"==typeof exports?exports.ca=e(require("cross-fetch")):t.ca=e(t.fetch)}("undefined"!=typeof self?self:this,(t=>(()=>{var e={209:function(t,e,s){t.exports=function(){"use strict";var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==s.g?s.g:"undefined"!=typeof self?self:{};var e,r,i={load:function(t,e,s={}){var r,i,n;for(r in e)n=e[r],s[r]=null!=(i=t[r])?i:n;return s},overwrite:function(t,e,s={}){var r,i;for(r in t)i=t[r],void 0!==e[r]&&(s[r]=i);return s}},n=class{constructor(t,e){this.incr=t,this.decr=e,this._first=null,this._last=null,this.length=0}push(t){var e;this.length++,"function"==typeof this.incr&&this.incr(),e={value:t,prev:this._last,next:null},null!=this._last?(this._last.next=e,this._last=e):this._first=this._last=e}shift(){var t;if(null!=this._first)return this.length--,"function"==typeof this.decr&&this.decr(),t=this._first.value,null!=(this._first=this._first.next)?this._first.prev=null:this._last=null,t}first(){if(null!=this._first)return this._first.value}getArray(){var t,e,s;for(t=this._first,s=[];null!=t;)s.push((e=t,t=t.next,e.value));return s}forEachShift(t){var e;for(e=this.shift();null!=e;)t(e),e=this.shift()}debug(){var t,e,s,r,i;for(t=this._first,i=[];null!=t;)i.push((e=t,t=t.next,{value:e.value,prev:null!=(s=e.prev)?s.value:void 0,next:null!=(r=e.next)?r.value:void 0}));return i}},o=class{constructor(t){if(this.instance=t,this._events={},null!=this.instance.on||null!=this.instance.once||null!=this.instance.removeAllListeners)throw new Error("An Emitter already exists for this object");this.instance.on=(t,e)=>this._addListener(t,"many",e),this.instance.once=(t,e)=>this._addListener(t,"once",e),this.instance.removeAllListeners=(t=null)=>null!=t?delete this._events[t]:this._events={}}_addListener(t,e,s){var r;return null==(r=this._events)[t]&&(r[t]=[]),this._events[t].push({cb:s,status:e}),this.instance}listenerCount(t){return null!=this._events[t]?this._events[t].length:0}async trigger(t,...e){var s,r;try{if("debug"!==t&&this.trigger("debug",`Event triggered: ${t}`,e),null==this._events[t])return;return this._events[t]=this._events[t].filter((function(t){return"none"!==t.status})),r=this._events[t].map((async t=>{var s,r;if("none"!==t.status){"once"===t.status&&(t.status="none");try{return"function"==typeof(null!=(r="function"==typeof t.cb?t.cb(...e):void 0)?r.then:void 0)?await r:r}catch(t){return s=t,this.trigger("error",s),null}}})),(await Promise.all(r)).find((function(t){return null!=t}))}catch(t){return s=t,this.trigger("error",s),null}}};e=n,r=o;var a,h,u=class extends Error{};h=i,a=u;var l,c,p=class{constructor(t,e,s,r,i,n,o,a){this.task=t,this.args=e,this.rejectOnDrop=i,this.Events=n,this._states=o,this.Promise=a,this.options=h.load(s,r),this.options.priority=this._sanitizePriority(this.options.priority),this.options.id===r.id&&(this.options.id=`${this.options.id}-${this._randomIndex()}`),this.promise=new this.Promise(((t,e)=>{this._resolve=t,this._reject=e})),this.retryCount=0}_sanitizePriority(t){var e;return(e=~~t!==t?5:t)<0?0:e>9?9:e}_randomIndex(){return Math.random().toString(36).slice(2)}doDrop({error:t,message:e="This job has been dropped by Bottleneck"}={}){return!!this._states.remove(this.options.id)&&(this.rejectOnDrop&&this._reject(null!=t?t:new a(e)),this.Events.trigger("dropped",{args:this.args,options:this.options,task:this.task,promise:this.promise}),!0)}_assertStatus(t){var e;if((e=this._states.jobStatus(this.options.id))!==t&&("DONE"!==t||null!==e))throw new a(`Invalid job status ${e}, expected ${t}. Please open an issue at https://github.com/SGrondin/bottleneck/issues`)}doReceive(){return this._states.start(this.options.id),this.Events.trigger("received",{args:this.args,options:this.options})}doQueue(t,e){return this._assertStatus("RECEIVED"),this._states.next(this.options.id),this.Events.trigger("queued",{args:this.args,options:this.options,reachedHWM:t,blocked:e})}doRun(){return 0===this.retryCount?(this._assertStatus("QUEUED"),this._states.next(this.options.id)):this._assertStatus("EXECUTING"),this.Events.trigger("scheduled",{args:this.args,options:this.options})}async doExecute(t,e,s,r){var i,n,o;0===this.retryCount?(this._assertStatus("RUNNING"),this._states.next(this.options.id)):this._assertStatus("EXECUTING"),n={args:this.args,options:this.options,retryCount:this.retryCount},this.Events.trigger("executing",n);try{if(o=await(null!=t?t.schedule(this.options,this.task,...this.args):this.task(...this.args)),e())return this.doDone(n),await r(this.options,n),this._assertStatus("DONE"),this._resolve(o)}catch(t){return i=t,this._onFailure(i,n,e,s,r)}}doExpire(t,e,s){var r,i;return this._states.jobStatus("RUNNING"===this.options.id)&&this._states.next(this.options.id),this._assertStatus("EXECUTING"),i={args:this.args,options:this.options,retryCount:this.retryCount},r=new a(`This job timed out after ${this.options.expiration} ms.`),this._onFailure(r,i,t,e,s)}async _onFailure(t,e,s,r,i){var n,o;if(s())return null!=(n=await this.Events.trigger("failed",t,e))?(o=~~n,this.Events.trigger("retry",`Retrying ${this.options.id} after ${o} ms`,e),this.retryCount++,r(o)):(this.doDone(e),await i(this.options,e),this._assertStatus("DONE"),this._reject(t))}doDone(t){return this._assertStatus("EXECUTING"),this._states.next(this.options.id),this.Events.trigger("done",t)}};c=i,l=u;var d;d=u;var _;_=n;var f,v,m,g,y,w="2.19.5",b={version:w},E=Object.freeze({version:w,default:b}),O=()=>console.log("You must import the full version of Bottleneck in order to use this feature."),j=()=>console.log("You must import the full version of Bottleneck in order to use this feature.");y=i,f=o,m=O,v=j,g=()=>console.log("You must import the full version of Bottleneck in order to use this feature.");var x,k,R=function(){class t{constructor(t={}){this.deleteKey=this.deleteKey.bind(this),this.limiterOptions=t,y.load(this.limiterOptions,this.defaults,this),this.Events=new f(this),this.instances={},this.Bottleneck=U,this._startAutoCleanup(),this.sharedConnection=null!=this.connection,null==this.connection&&("redis"===this.limiterOptions.datastore?this.connection=new m(Object.assign({},this.limiterOptions,{Events:this.Events})):"ioredis"===this.limiterOptions.datastore&&(this.connection=new v(Object.assign({},this.limiterOptions,{Events:this.Events}))))}key(t=""){var e;return null!=(e=this.instances[t])?e:(()=>{var e;return e=this.instances[t]=new this.Bottleneck(Object.assign(this.limiterOptions,{id:`${this.id}-${t}`,timeout:this.timeout,connection:this.connection})),this.Events.trigger("created",e,t),e})()}async deleteKey(t=""){var e,s;return s=this.instances[t],this.connection&&(e=await this.connection.__runCommand__(["del",...g.allKeys(`${this.id}-${t}`)])),null!=s&&(delete this.instances[t],await s.disconnect()),null!=s||e>0}limiters(){var t,e,s,r;for(t in s=[],e=this.instances)r=e[t],s.push({key:t,limiter:r});return s}keys(){return Object.keys(this.instances)}async clusterKeys(){var t,e,s,r,i,n,o,a,h;if(null==this.connection)return this.Promise.resolve(this.keys());for(n=[],t=null,h=`b_${this.id}-`.length,e="_settings".length;0!==t;)for([a,s]=await this.connection.__runCommand__(["scan",null!=t?t:0,"match",`b_${this.id}-*_settings`,"count",1e4]),t=~~a,r=0,o=s.length;r<o;r++)i=s[r],n.push(i.slice(h,-e));return n}_startAutoCleanup(){var t;return clearInterval(this.interval),"function"==typeof(t=this.interval=setInterval((async()=>{var t,e,s,r,i,n;for(e in i=Date.now(),r=[],s=this.instances){n=s[e];try{await n._store.__groupCheck__(i)?r.push(this.deleteKey(e)):r.push(void 0)}catch(e){t=e,r.push(n.Events.trigger("error",t))}}return r}),this.timeout/2)).unref?t.unref():void 0}updateSettings(t={}){if(y.overwrite(t,this.defaults,this),y.overwrite(t,t,this.limiterOptions),null!=t.timeout)return this._startAutoCleanup()}disconnect(t=!0){var e;if(!this.sharedConnection)return null!=(e=this.connection)?e.disconnect(t):void 0}}return t.prototype.defaults={timeout:3e5,connection:null,Promise,id:"group-key"},t}.call(t);k=i,x=o;var C,I,D,P,T,A,L,S,q,$=function(){class t{constructor(t={}){this.options=t,k.load(this.options,this.defaults,this),this.Events=new x(this),this._arr=[],this._resetPromise(),this._lastFlush=Date.now()}_resetPromise(){return this._promise=new this.Promise(((t,e)=>this._resolve=t))}_flush(){return clearTimeout(this._timeout),this._lastFlush=Date.now(),this._resolve(),this.Events.trigger("batch",this._arr),this._arr=[],this._resetPromise()}add(t){var e;return this._arr.push(t),e=this._promise,this._arr.length===this.maxSize?this._flush():null!=this.maxTime&&1===this._arr.length&&(this._timeout=setTimeout((()=>this._flush()),this.maxTime)),e}}return t.prototype.defaults={maxTime:null,maxSize:null,Promise},t}.call(t),B=(q=E)&&q.default||q,N=[].splice;S=i,P=class{constructor(t){this.Events=new r(this),this._length=0,this._lists=function(){var s,r,i;for(i=[],s=1,r=t;1<=r?s<=r:s>=r;1<=r?++s:--s)i.push(new e((()=>this.incr()),(()=>this.decr())));return i}.call(this)}incr(){if(0==this._length++)return this.Events.trigger("leftzero")}decr(){if(0==--this._length)return this.Events.trigger("zero")}push(t){return this._lists[t.options.priority].push(t)}queued(t){return null!=t?this._lists[t].length:this._length}shiftAll(t){return this._lists.forEach((function(e){return e.forEachShift(t)}))}getFirst(t=this._lists){var e,s,r;for(e=0,s=t.length;e<s;e++)if((r=t[e]).length>0)return r;return[]}shiftLastFrom(t){return this.getFirst(this._lists.slice(t).reverse()).shift()}},I=p,D=class{constructor(t,e,s){this.instance=t,this.storeOptions=e,this.clientId=this.instance._randomIndex(),c.load(s,s,this),this._nextRequest=this._lastReservoirRefresh=this._lastReservoirIncrease=Date.now(),this._running=0,this._done=0,this._unblockTime=0,this.ready=this.Promise.resolve(),this.clients={},this._startHeartbeat()}_startHeartbeat(){var t;return null==this.heartbeat&&(null!=this.storeOptions.reservoirRefreshInterval&&null!=this.storeOptions.reservoirRefreshAmount||null!=this.storeOptions.reservoirIncreaseInterval&&null!=this.storeOptions.reservoirIncreaseAmount)?"function"==typeof(t=this.heartbeat=setInterval((()=>{var t,e,s,r,i;if(r=Date.now(),null!=this.storeOptions.reservoirRefreshInterval&&r>=this._lastReservoirRefresh+this.storeOptions.reservoirRefreshInterval&&(this._lastReservoirRefresh=r,this.storeOptions.reservoir=this.storeOptions.reservoirRefreshAmount,this.instance._drainAll(this.computeCapacity())),null!=this.storeOptions.reservoirIncreaseInterval&&r>=this._lastReservoirIncrease+this.storeOptions.reservoirIncreaseInterval&&(({reservoirIncreaseAmount:t,reservoirIncreaseMaximum:s,reservoir:i}=this.storeOptions),this._lastReservoirIncrease=r,(e=null!=s?Math.min(t,s-i):t)>0))return this.storeOptions.reservoir+=e,this.instance._drainAll(this.computeCapacity())}),this.heartbeatInterval)).unref?t.unref():void 0:clearInterval(this.heartbeat)}async __publish__(t){return await this.yieldLoop(),this.instance.Events.trigger("message",t.toString())}async __disconnect__(t){return await this.yieldLoop(),clearInterval(this.heartbeat),this.Promise.resolve()}yieldLoop(t=0){return new this.Promise((function(e,s){return setTimeout(e,t)}))}computePenalty(){var t;return null!=(t=this.storeOptions.penalty)?t:15*this.storeOptions.minTime||5e3}async __updateSettings__(t){return await this.yieldLoop(),c.overwrite(t,t,this.storeOptions),this._startHeartbeat(),this.instance._drainAll(this.computeCapacity()),!0}async __running__(){return await this.yieldLoop(),this._running}async __queued__(){return await this.yieldLoop(),this.instance.queued()}async __done__(){return await this.yieldLoop(),this._done}async __groupCheck__(t){return await this.yieldLoop(),this._nextRequest+this.timeout<t}computeCapacity(){var t,e;return({maxConcurrent:t,reservoir:e}=this.storeOptions),null!=t&&null!=e?Math.min(t-this._running,e):null!=t?t-this._running:null!=e?e:null}conditionsCheck(t){var e;return null==(e=this.computeCapacity())||t<=e}async __incrementReservoir__(t){var e;return await this.yieldLoop(),e=this.storeOptions.reservoir+=t,this.instance._drainAll(this.computeCapacity()),e}async __currentReservoir__(){return await this.yieldLoop(),this.storeOptions.reservoir}isBlocked(t){return this._unblockTime>=t}check(t,e){return this.conditionsCheck(t)&&this._nextRequest-e<=0}async __check__(t){var e;return await this.yieldLoop(),e=Date.now(),this.check(t,e)}async __register__(t,e,s){var r,i;return await this.yieldLoop(),r=Date.now(),this.conditionsCheck(e)?(this._running+=e,null!=this.storeOptions.reservoir&&(this.storeOptions.reservoir-=e),i=Math.max(this._nextRequest-r,0),this._nextRequest=r+i+this.storeOptions.minTime,{success:!0,wait:i,reservoir:this.storeOptions.reservoir}):{success:!1}}strategyIsBlock(){return 3===this.storeOptions.strategy}async __submit__(t,e){var s,r,i;if(await this.yieldLoop(),null!=this.storeOptions.maxConcurrent&&e>this.storeOptions.maxConcurrent)throw new l(`Impossible to add a job having a weight of ${e} to a limiter having a maxConcurrent setting of ${this.storeOptions.maxConcurrent}`);return r=Date.now(),i=null!=this.storeOptions.highWater&&t===this.storeOptions.highWater&&!this.check(e,r),(s=this.strategyIsBlock()&&(i||this.isBlocked(r)))&&(this._unblockTime=r+this.computePenalty(),this._nextRequest=this._unblockTime+this.storeOptions.minTime,this.instance._dropAllQueued()),{reachedHWM:i,blocked:s,strategy:this.storeOptions.strategy}}async __free__(t,e){return await this.yieldLoop(),this._running-=e,this._done+=e,this.instance._drainAll(this.computeCapacity()),{running:this._running}}},T=()=>console.log("You must import the full version of Bottleneck in order to use this feature."),C=o,A=class{constructor(t){this.status=t,this._jobs={},this.counts=this.status.map((function(){return 0}))}next(t){var e,s;return s=(e=this._jobs[t])+1,null!=e&&s<this.status.length?(this.counts[e]--,this.counts[s]++,this._jobs[t]++):null!=e?(this.counts[e]--,delete this._jobs[t]):void 0}start(t){return 0,this._jobs[t]=0,this.counts[0]++}remove(t){var e;return null!=(e=this._jobs[t])&&(this.counts[e]--,delete this._jobs[t]),null!=e}jobStatus(t){var e;return null!=(e=this.status[this._jobs[t]])?e:null}statusJobs(t){var e,s,r,i;if(null!=t){if((s=this.status.indexOf(t))<0)throw new d(`status must be one of ${this.status.join(", ")}`);for(e in i=[],r=this._jobs)r[e]===s&&i.push(e);return i}return Object.keys(this._jobs)}statusCounts(){return this.counts.reduce(((t,e,s)=>(t[this.status[s]]=e,t)),{})}},L=class{constructor(t,e){this.schedule=this.schedule.bind(this),this.name=t,this.Promise=e,this._running=0,this._queue=new _}isEmpty(){return 0===this._queue.length}async _tryToRun(){var t,e,s,r,i,n,o;if(this._running<1&&this._queue.length>0)return this._running++,({task:o,args:t,resolve:i,reject:r}=this._queue.shift()),e=await async function(){try{return n=await o(...t),function(){return i(n)}}catch(t){return s=t,function(){return r(s)}}}(),this._running--,this._tryToRun(),e()}schedule(t,...e){var s,r,i;return i=r=null,s=new this.Promise((function(t,e){return i=t,r=e})),this._queue.push({task:t,args:e,resolve:i,reject:r}),this._tryToRun(),s}};var U=function(){class t{constructor(e={},...s){var r,i;this._addToQueue=this._addToQueue.bind(this),this._validateOptions(e,s),S.load(e,this.instanceDefaults,this),this._queues=new P(10),this._scheduled={},this._states=new A(["RECEIVED","QUEUED","RUNNING","EXECUTING"].concat(this.trackDoneStatus?["DONE"]:[])),this._limiter=null,this.Events=new C(this),this._submitLock=new L("submit",this.Promise),this._registerLock=new L("register",this.Promise),i=S.load(e,this.storeDefaults,{}),this._store=function(){if("redis"===this.datastore||"ioredis"===this.datastore||null!=this.connection)return r=S.load(e,this.redisStoreDefaults,{}),new T(this,i,r);if("local"===this.datastore)return r=S.load(e,this.localStoreDefaults,{}),new D(this,i,r);throw new t.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`)}.call(this),this._queues.on("leftzero",(()=>{var t;return null!=(t=this._store.heartbeat)&&"function"==typeof t.ref?t.ref():void 0})),this._queues.on("zero",(()=>{var t;return null!=(t=this._store.heartbeat)&&"function"==typeof t.unref?t.unref():void 0}))}_validateOptions(e,s){if(null==e||"object"!=typeof e||0!==s.length)throw new t.prototype.BottleneckError("Bottleneck v2 takes a single object argument. Refer to https://github.com/SGrondin/bottleneck#upgrading-to-v2 if you're upgrading from Bottleneck v1.")}ready(){return this._store.ready}clients(){return this._store.clients}channel(){return`b_${this.id}`}channel_client(){return`b_${this.id}_${this._store.clientId}`}publish(t){return this._store.__publish__(t)}disconnect(t=!0){return this._store.__disconnect__(t)}chain(t){return this._limiter=t,this}queued(t){return this._queues.queued(t)}clusterQueued(){return this._store.__queued__()}empty(){return 0===this.queued()&&this._submitLock.isEmpty()}running(){return this._store.__running__()}done(){return this._store.__done__()}jobStatus(t){return this._states.jobStatus(t)}jobs(t){return this._states.statusJobs(t)}counts(){return this._states.statusCounts()}_randomIndex(){return Math.random().toString(36).slice(2)}check(t=1){return this._store.__check__(t)}_clearGlobalState(t){return null!=this._scheduled[t]&&(clearTimeout(this._scheduled[t].expiration),delete this._scheduled[t],!0)}async _free(t,e,s,r){var i,n;try{if(({running:n}=await this._store.__free__(t,s.weight)),this.Events.trigger("debug",`Freed ${s.id}`,r),0===n&&this.empty())return this.Events.trigger("idle")}catch(t){return i=t,this.Events.trigger("error",i)}}_run(t,e,s){var r,i,n;return e.doRun(),r=this._clearGlobalState.bind(this,t),n=this._run.bind(this,t,e),i=this._free.bind(this,t,e),this._scheduled[t]={timeout:setTimeout((()=>e.doExecute(this._limiter,r,n,i)),s),expiration:null!=e.options.expiration?setTimeout((function(){return e.doExpire(r,n,i)}),s+e.options.expiration):void 0,job:e}}_drainOne(t){return this._registerLock.schedule((()=>{var e,s,r,i,n;return 0===this.queued()?this.Promise.resolve(null):(n=this._queues.getFirst(),({options:i,args:e}=r=n.first()),null!=t&&i.weight>t?this.Promise.resolve(null):(this.Events.trigger("debug",`Draining ${i.id}`,{args:e,options:i}),s=this._randomIndex(),this._store.__register__(s,i.weight,i.expiration).then((({success:t,wait:o,reservoir:a})=>{var h;return this.Events.trigger("debug",`Drained ${i.id}`,{success:t,args:e,options:i}),t?(n.shift(),(h=this.empty())&&this.Events.trigger("empty"),0===a&&this.Events.trigger("depleted",h),this._run(s,r,o),this.Promise.resolve(i.weight)):this.Promise.resolve(null)}))))}))}_drainAll(t,e=0){return this._drainOne(t).then((s=>{var r;return null!=s?(r=null!=t?t-s:t,this._drainAll(r,e+s)):this.Promise.resolve(e)})).catch((t=>this.Events.trigger("error",t)))}_dropAllQueued(t){return this._queues.shiftAll((function(e){return e.doDrop({message:t})}))}stop(e={}){var s,r;return e=S.load(e,this.stopDefaults),r=t=>{var e;return e=()=>{var e;return(e=this._states.counts)[0]+e[1]+e[2]+e[3]===t},new this.Promise(((t,s)=>e()?t():this.on("done",(()=>{if(e())return this.removeAllListeners("done"),t()}))))},s=e.dropWaitingJobs?(this._run=function(t,s){return s.doDrop({message:e.dropErrorMessage})},this._drainOne=()=>this.Promise.resolve(null),this._registerLock.schedule((()=>this._submitLock.schedule((()=>{var t,s,i;for(t in s=this._scheduled)i=s[t],"RUNNING"===this.jobStatus(i.job.options.id)&&(clearTimeout(i.timeout),clearTimeout(i.expiration),i.job.doDrop({message:e.dropErrorMessage}));return this._dropAllQueued(e.dropErrorMessage),r(0)}))))):this.schedule({priority:9,weight:0},(()=>r(1))),this._receive=function(s){return s._reject(new t.prototype.BottleneckError(e.enqueueErrorMessage))},this.stop=()=>this.Promise.reject(new t.prototype.BottleneckError("stop() has already been called")),s}async _addToQueue(e){var s,r,i,n,o,a,h;({args:s,options:n}=e);try{({reachedHWM:o,blocked:r,strategy:h}=await this._store.__submit__(this.queued(),n.weight))}catch(t){return i=t,this.Events.trigger("debug",`Could not queue ${n.id}`,{args:s,options:n,error:i}),e.doDrop({error:i}),!1}return r?(e.doDrop(),!0):o&&(null!=(a=h===t.prototype.strategy.LEAK?this._queues.shiftLastFrom(n.priority):h===t.prototype.strategy.OVERFLOW_PRIORITY?this._queues.shiftLastFrom(n.priority+1):h===t.prototype.strategy.OVERFLOW?e:void 0)&&a.doDrop(),null==a||h===t.prototype.strategy.OVERFLOW)?(null==a&&e.doDrop(),o):(e.doQueue(o,r),this._queues.push(e),await this._drainAll(),o)}_receive(e){return null!=this._states.jobStatus(e.options.id)?(e._reject(new t.prototype.BottleneckError(`A job with the same id already exists (id=${e.options.id})`)),!1):(e.doReceive(),this._submitLock.schedule(this._addToQueue,e))}submit(...t){var e,s,r,i,n,o,a;return"function"==typeof t[0]?(n=t,[s,...t]=n,[e]=N.call(t,-1),i=S.load({},this.jobDefaults)):(o=t,[i,s,...t]=o,[e]=N.call(t,-1),i=S.load(i,this.jobDefaults)),a=(...t)=>new this.Promise((function(e,r){return s(...t,(function(...t){return(null!=t[0]?r:e)(t)}))})),(r=new I(a,t,i,this.jobDefaults,this.rejectOnDrop,this.Events,this._states,this.Promise)).promise.then((function(t){return"function"==typeof e?e(...t):void 0})).catch((function(t){return Array.isArray(t)?"function"==typeof e?e(...t):void 0:"function"==typeof e?e(t):void 0})),this._receive(r)}schedule(...t){var e,s,r;return"function"==typeof t[0]?([r,...t]=t,s={}):[s,r,...t]=t,e=new I(r,t,s,this.jobDefaults,this.rejectOnDrop,this.Events,this._states,this.Promise),this._receive(e),e.promise}wrap(t){var e,s;return e=this.schedule.bind(this),(s=function(...s){return e(t.bind(this),...s)}).withOptions=function(s,...r){return e(s,t,...r)},s}async updateSettings(t={}){return await this._store.__updateSettings__(S.overwrite(t,this.storeDefaults)),S.overwrite(t,this.instanceDefaults,this),this}currentReservoir(){return this._store.__currentReservoir__()}incrementReservoir(t=0){return this._store.__incrementReservoir__(t)}}return t.default=t,t.Events=C,t.version=t.prototype.version=B.version,t.strategy=t.prototype.strategy={LEAK:1,OVERFLOW:2,OVERFLOW_PRIORITY:4,BLOCK:3},t.BottleneckError=t.prototype.BottleneckError=u,t.Group=t.prototype.Group=R,t.RedisConnection=t.prototype.RedisConnection=O,t.IORedisConnection=t.prototype.IORedisConnection=j,t.Batcher=t.prototype.Batcher=$,t.prototype.jobDefaults={priority:5,weight:1,expiration:null,id:"<no-id>"},t.prototype.storeDefaults={maxConcurrent:null,minTime:0,highWater:null,strategy:t.prototype.strategy.LEAK,penalty:null,reservoir:null,reservoirRefreshInterval:null,reservoirRefreshAmount:null,reservoirIncreaseInterval:null,reservoirIncreaseAmount:null,reservoirIncreaseMaximum:null},t.prototype.localStoreDefaults={Promise,timeout:null,heartbeatInterval:250},t.prototype.redisStoreDefaults={Promise,timeout:null,heartbeatInterval:5e3,clientTimeout:1e4,Redis:null,clientOptions:{},clusterNodes:null,clearDatastore:!1,connection:null},t.prototype.instanceDefaults={datastore:"local",connection:null,id:"<no-id>",rejectOnDrop:!0,trackDoneStatus:!1,Promise},t.prototype.stopDefaults={enqueueErrorMessage:"This limiter has been stopped and cannot accept new jobs.",dropWaitingJobs:!0,dropErrorMessage:"This limiter has been stopped."},t}.call(t);return U}()},310:t=>{"use strict";t.exports=t=>{if("[object Object]"!==Object.prototype.toString.call(t))return!1;const e=Object.getPrototypeOf(t);return null===e||e===Object.prototype}},942:function(t,e,s){"use strict";const r=s(310),{hasOwnProperty:i}=Object.prototype,{propertyIsEnumerable:n}=Object,o=(t,e,s)=>Object.defineProperty(t,e,{value:s,writable:!0,enumerable:!0,configurable:!0}),a=this,h={concatArrays:!1,ignoreUndefined:!1},u=t=>{const e=[];for(const s in t)i.call(t,s)&&e.push(s);if(Object.getOwnPropertySymbols){const s=Object.getOwnPropertySymbols(t);for(const r of s)n.call(t,r)&&e.push(r)}return e};function l(t){return Array.isArray(t)?function(t){const e=t.slice(0,0);return u(t).forEach((s=>{o(e,s,l(t[s]))})),e}(t):r(t)?function(t){const e=null===Object.getPrototypeOf(t)?Object.create(null):{};return u(t).forEach((s=>{o(e,s,l(t[s]))})),e}(t):t}const c=(t,e,s,r)=>(s.forEach((s=>{void 0===e[s]&&r.ignoreUndefined||(s in t&&t[s]!==Object.getPrototypeOf(t)?o(t,s,p(t[s],e[s],r)):o(t,s,l(e[s])))})),t);function p(t,e,s){return s.concatArrays&&Array.isArray(t)&&Array.isArray(e)?((t,e,s)=>{let r=t.slice(0,0),n=0;return[t,e].forEach((e=>{const a=[];for(let s=0;s<e.length;s++)i.call(e,s)&&(a.push(String(s)),o(r,n++,e===t?e[s]:l(e[s])));r=c(r,e,u(e).filter((t=>!a.includes(t))),s)})),r})(t,e,s):r(e)&&r(t)?c(t,e,u(e),s):l(e)}t.exports=function(...t){const e=p(l(h),this!==a&&this||{},h);let s={_:{}};for(const i of t)if(void 0!==i){if(!r(i))throw new TypeError("`"+i+"` is not an Option Object");s=p(s,{_:i},e)}return s._}},322:(t,e,s)=>{const r=s(427),i=s(942),n=s(526),o={jobOptions:{priority:0},rawContent:!1};t.exports=async(t,e)=>{t=r(t,"children"),e=i(o,e);let s=await n(t,e),a=await s.text();if(e.rawContent)return a;let h=a.match(/<url>.*?<\/url>/g).map((t=>{let e=t.match(/<loc>([^<]+)<\/loc>/),s=t.match(/<lastmod>([^<]+)<\/lastmod>/);return{path:r(e[1]),lastmod:s?Date.parse(s[1]):null}}));return h.length&&h[0].path===r(t)&&h.shift(),h}},565:(t,e,s)=>{const r=s(427),i=s(942),n=s(526),o={rawContent:!1};t.exports=async(t,e={})=>{t=r(t,"content"),e=i(o,e);let s=await n(t,e);if(e.rawContent)return await s.text();let a=s.headers.get("Content-Type");if(a.includes("/json"))return s.json();let h=await s.text();return a.includes("text/html")&&(h=h.replace(/\s+/g," ")),h}},526:(t,e,s)=>{const r=s(427).baseURL,i=s(268),n=new(s(209))({reservoir:150,reservoirRefreshAmount:150,reservoirRefreshInterval:5e3,maxConcurrent:10,trackDoneStatus:!0}),o=(t,e)=>i(t,e);t.exports=e=async(t,e)=>{let s=e?.jobOptions||{},i=await n.schedule(s,o,t,e);if(!i.ok)throw new Error(i.statusText);if(!i.url.startsWith(r))throw new Error("Invalid destination URL");return i},e.limiter=n},138:(t,e,s)=>{const r=s(526),i=s(427),n=s(322),o=s(565),a=s(873);t.exports={fetch:r,normalize:i,children:n,content:o,meta:a}},873:(t,e,s)=>{const r=s(427),i=s(942),n=s(526),o={jobOptions:{expiration:3e4},rawContent:!1},a={Jan:"01",Feb:"02",Mar:"03",Apr:"04",May:"05",Jun:"06",Jul:"07",Aug:"08",Sep:"09",Oct:"10",Nov:"11",Dec:"12"};t.exports=async(t,e)=>{t=r(t,"meta"),e=i(o,e);let s=await n(t,e);if(e.rawContent)return await s.text();if(!s.headers.get("content-type").includes("application/json"))throw new Error("Unexpected response content-type");let h=await s.json();return Object.keys(h).forEach((t=>{"true"===h[t]?h[t]=!0:"false"===h[t]?h[t]=!1:t.endsWith("@TypeHint")?delete h[t]:"string"==typeof h[t]?h[t]=function(t){if(/^\d{4}-\d{2}-\d{2}$/.test(t))return Date.parse(t);let e=/^\w{3} (\w{3}) (\d{2}) (\d{4}) ([\d:]{8}) GMT([\-+]\d{4})$/.exec(t);return e?Date.parse(e[3]+"-"+a[e[1]]+"-"+e[2]+"T"+e[4]+e[5]):t}(h[t]):Array.isArray(h[t])&&0===h[t].length&&delete h[t]})),Object.keys(h).sort().reduce(((t,e)=>(t[e]=h[e],t)),{})}},427:(t,e)=>{const s="https://www.canada.ca/";t.exports=e=(t,e="path")=>{if("string"==typeof t)t=new URL(t,s);else if(!(t instanceof URL))throw new TypeError("string or URL object expected");if(t.protocol="https",!t.href.startsWith(s))throw new Error("URL must start with "+s);let r=t.pathname.startsWith("/content/dam");if(r||(t.pathname=t.pathname.replace(/^\/content\/canadasite\//,"")),!/^\/(en|fr|content\/dam)(\/|\.|$)/.test(t.pathname))throw new Error("Invalid root");return t.pathname=t.pathname.replace(/\/+$/,""),"path"!==e&&t.searchParams.set("_",Date.now()),t.pathname.includes("/_jcr_content/par")?function(t,e){if("path"===e)throw new Error("Cant determine path of a reference node directly");if("meta"===e)t.pathname=t.pathname.replace(/\/image\.img\.\w+\//,"/image.json/");else if("children"===e)throw new Error("Cant load children of a reference node");return t.href}(t,e):r?function(t,e){let s=/(\/|\/\w+)$/.test(t.pathname);if("meta"===e)t.pathname+=s?".json":"/_jcr_content.json";else{if("children"===e)throw new Error("Cant load children of an asset node");if("content"===e&&s)throw new Error("Cant load content of an asset node")}return"path"===e?t.pathname:t.href}(t,e):function(t,e){return t.pathname=t.pathname.replace(/\/?\.(sitemap\.xml|[^/.]+)$/,""),"meta"===e?t.pathname+="/_jcr_content.json":"children"===e?t.pathname+=".sitemap.xml":"content"===e&&(t.pathname+=".html"),"path"===e?t.pathname:t.href}(t,e)},e.baseURL=s},268:e=>{"use strict";e.exports=t}},s={};function r(t){var i=s[t];if(void 0!==i)return i.exports;var n=s[t]={exports:{}};return e[t].call(n.exports,n,n.exports,r),n.exports}return r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),r(138)})()));
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "canada-api",
3
- "version": "1.1.6",
3
+ "version": "2.0.4",
4
4
  "description": "Cross platform API to fetch data from canada.ca",
5
5
  "browser": "dist/ca.js",
6
- "main": "src/index.mjs",
6
+ "main": "src/index.js",
7
7
  "scripts": {
8
8
  "test": "echo \"Error: no test specified\" && exit 1",
9
9
  "build": "webpack",
@@ -11,26 +11,27 @@
11
11
  },
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "https://github.com/bsoicher/canada-api.git"
14
+ "url": "https://github.com/dnd-mdn/canada-api.git"
15
15
  },
16
16
  "keywords": [
17
17
  "canada",
18
18
  "api",
19
19
  "fetch"
20
20
  ],
21
- "author": "Ben Soicher",
21
+ "author": "National Defence",
22
22
  "license": "MIT",
23
- "homepage": "https://github.com/bsoicher/canada-api#readme",
24
- "bugs": "https://github.com/bsoicher/canada-api/issues",
23
+ "homepage": "https://github.com/dnd-mdn/canada-api#readme",
24
+ "bugs": "https://github.com/dnd-mdn/canada-api/issues",
25
25
  "devDependencies": {
26
26
  "webpack": "^5.72.0",
27
27
  "webpack-cli": "^4.9.2"
28
28
  },
29
29
  "dependencies": {
30
30
  "bottleneck": "^2.19.5",
31
- "cross-fetch": "^3.1.5"
31
+ "cross-fetch": "^3.1.5",
32
+ "merge-options": "^3.0.4"
32
33
  },
33
34
  "engines": {
34
35
  "node": ">= 10.0.0"
35
36
  }
36
- }
37
+ }
@@ -0,0 +1,55 @@
1
+ const normalize = require('./normalize.js')
2
+ const merge = require('merge-options')
3
+ const fetch = require('./fetch.js')
4
+
5
+ /**
6
+ * Default fetch options
7
+ * @const {object}
8
+ * @private
9
+ */
10
+ const defaultOptions = {
11
+ jobOptions: {
12
+ priority: 0
13
+ },
14
+ rawContent: false
15
+ }
16
+
17
+ /**
18
+ * Get list of child nodes from sitemap
19
+ * @param {string} url node url
20
+ * @param {Object} [options] fetch options
21
+ * @returns {Promise<Array>}
22
+ */
23
+ const children = async (url, options) => {
24
+ url = normalize(url, 'children')
25
+ options = merge(defaultOptions, options)
26
+
27
+ let response = await fetch(url, options)
28
+ let xml = await response.text()
29
+
30
+ // Return raw text
31
+ if (options.rawContent) {
32
+ return xml
33
+ }
34
+
35
+ // Parse XML sitemap
36
+ let children = xml.match(/<url>.*?<\/url>/g).map(url => {
37
+ let loc = url.match(/<loc>([^<]+)<\/loc>/)
38
+ let mod = url.match(/<lastmod>([^<]+)<\/lastmod>/)
39
+
40
+ return {
41
+ path: normalize(loc[1]),
42
+ lastmod: mod ? Date.parse(mod[1]) : null
43
+ }
44
+ })
45
+
46
+ // First entry may be the parent
47
+ if (children.length && children[0].path === normalize(url)) {
48
+ children.shift()
49
+ }
50
+
51
+ return children
52
+ }
53
+
54
+ // Default export
55
+ module.exports = exports = children
package/src/content.js ADDED
@@ -0,0 +1,48 @@
1
+ const normalize = require('./normalize.js')
2
+ const merge = require('merge-options')
3
+ const fetch = require('./fetch.js')
4
+
5
+ /**
6
+ * Default fetch options
7
+ * @const {object}
8
+ * @private
9
+ */
10
+ const defaultOptions = {
11
+ rawContent: false
12
+ }
13
+
14
+ /**
15
+ * Get node content
16
+ * @param {string} url node URL
17
+ * @param {Object} [options] fetch options
18
+ * @returns {Promise<any>}
19
+ */
20
+ const content = async (url, options = {}) => {
21
+ url = normalize(url, 'content')
22
+ options = merge(defaultOptions, options)
23
+
24
+ let response = await fetch(url, options)
25
+
26
+ // Return raw text
27
+ if (options.rawContent) {
28
+ return await response.text()
29
+ }
30
+
31
+ let type = response.headers.get('Content-Type')
32
+
33
+ if (type.includes('/json')) {
34
+ return response.json()
35
+ }
36
+
37
+ let text = await response.text()
38
+
39
+ // Compress whitespace in html
40
+ if (type.includes('text/html')) {
41
+ text = text.replace(/\s+/g, ' ')
42
+ }
43
+
44
+ return text
45
+ }
46
+
47
+ // Default export
48
+ module.exports = exports = content
package/src/fetch.js ADDED
@@ -0,0 +1,60 @@
1
+ const baseURL = require('./normalize').baseURL
2
+ const crossFetch = require('cross-fetch')
3
+ const Bottleneck = require('bottleneck/light.js')
4
+
5
+ /**
6
+ * Default limiter options
7
+ * @see https://stackleap.io/js/bottleneck#user-content-constructor
8
+ * @const {object}
9
+ * @private
10
+ */
11
+ const limiterOptions = {
12
+ reservoir: 150,
13
+ reservoirRefreshAmount: 150,
14
+ reservoirRefreshInterval: 5000,
15
+ maxConcurrent: 10,
16
+ trackDoneStatus: true
17
+ }
18
+
19
+ /**
20
+ * Rate limiter
21
+ * @const {Bottleneck}
22
+ */
23
+ const limiter = new Bottleneck(limiterOptions)
24
+
25
+ /**
26
+ * Passthrough arguments to prevent running method on non window object
27
+ * @param {string|URL} url
28
+ * @param {object} [options] Fetch options
29
+ * @private
30
+ */
31
+ const pFetch = (url, options) => crossFetch(url, options)
32
+
33
+ /**
34
+ * Modified rate limited fetch
35
+ * @param {string|URL} url
36
+ * @param {object} [options] Fetch options
37
+ * @returns {Promise<Response>}
38
+ */
39
+ const fetch = async (url, options) => {
40
+ let jobOptions = options?.jobOptions || {}
41
+ let response = await limiter.schedule(jobOptions, pFetch, url, options)
42
+
43
+ // Verify response code
44
+ if (!response.ok) {
45
+ throw new Error(response.statusText)
46
+ }
47
+
48
+ // Verify destination
49
+ if (!response.url.startsWith(baseURL)) {
50
+ throw new Error('Invalid destination URL')
51
+ }
52
+
53
+ return response
54
+ }
55
+
56
+ // Default export
57
+ module.exports = exports = fetch
58
+
59
+ // Expose the limiter
60
+ exports.limiter = limiter
package/src/index.js ADDED
@@ -0,0 +1,16 @@
1
+ const fetch = require('./fetch.js')
2
+ const normalize = require('./normalize.js')
3
+ const children = require('./children.js')
4
+ const content = require('./content.js')
5
+ const meta = require('./meta.js')
6
+
7
+ module.exports = exports = {
8
+ // Core
9
+ fetch,
10
+ normalize,
11
+
12
+ // Basic API
13
+ children,
14
+ content,
15
+ meta,
16
+ }
package/src/meta.js ADDED
@@ -0,0 +1,95 @@
1
+ const normalize = require('./normalize.js')
2
+ const merge = require('merge-options')
3
+ const fetch = require('./fetch.js')
4
+
5
+ /**
6
+ * Default fetch options
7
+ * @const {object}
8
+ * @private
9
+ */
10
+ const defaultOptions = {
11
+ jobOptions: {
12
+ expiration: 30000
13
+ },
14
+ rawContent: false
15
+ }
16
+
17
+ /**
18
+ * Get node metadata from jcr content
19
+ * @param {string} url node path
20
+ * @param {Object} [options] fetch options
21
+ * @returns {Promise<Object>}
22
+ */
23
+ const meta = async (url, options) => {
24
+ url = normalize(url, 'meta')
25
+ options = merge(defaultOptions, options)
26
+
27
+ let response = await fetch(url, options)
28
+
29
+ // Return raw text
30
+ if (options.rawContent) {
31
+ return await response.text()
32
+ }
33
+
34
+ // Verify content-type
35
+ if (!response.headers.get('content-type').includes('application/json')) {
36
+ throw new Error('Unexpected response content-type')
37
+ }
38
+
39
+ let json = await response.json()
40
+
41
+ // Format meta properties
42
+ Object.keys(json).forEach(key => {
43
+ if (json[key] === 'true') {
44
+ json[key] = true
45
+ } else if (json[key] === 'false') {
46
+ json[key] = false
47
+ } else if (key.endsWith('@TypeHint')) {
48
+ delete json[key]
49
+ } else if (typeof json[key] === 'string') {
50
+ json[key] = maybeParseDate(json[key])
51
+ } else if (Array.isArray(json[key]) && json[key].length === 0) {
52
+ delete json[key]
53
+ }
54
+ })
55
+
56
+ // Sort object keys alphabetically for readability
57
+ return Object.keys(json).sort().reduce((obj, key) => {
58
+ obj[key] = json[key]
59
+ return obj
60
+ }, {})
61
+ }
62
+
63
+ /**
64
+ * Map month name to number
65
+ * @const {object}
66
+ * @private
67
+ */
68
+ const months = {
69
+ 'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
70
+ 'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'
71
+ }
72
+
73
+ /**
74
+ * Try to parse a date
75
+ * @param {string} date
76
+ * @returns {number|string}
77
+ * @private
78
+ */
79
+ function maybeParseDate(date) {
80
+ // Simple
81
+ if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
82
+ return Date.parse(date)
83
+ }
84
+
85
+ // RFC1123
86
+ let m = /^\w{3} (\w{3}) (\d{2}) (\d{4}) ([\d:]{8}) GMT([\-+]\d{4})$/.exec(date)
87
+ if (m) {
88
+ return Date.parse(m[3] + '-' + months[m[1]] + '-' + m[2] + 'T' + m[4] + m[5])
89
+ }
90
+
91
+ return date
92
+ }
93
+
94
+ // Default export
95
+ module.exports = exports = meta
@@ -0,0 +1,128 @@
1
+
2
+ /**
3
+ * Base URL
4
+ * @const {string}
5
+ * @private
6
+ */
7
+ const baseURL = 'https://www.canada.ca/'
8
+
9
+ /**
10
+ * Normalize AEM URL
11
+ * @param {string|URL} url
12
+ * @param {string} [type=path]
13
+ * @returns {string}
14
+ */
15
+ const normalize = (url, type = 'path') => {
16
+
17
+ if (typeof url === 'string') {
18
+ url = new URL(url, baseURL)
19
+ } else if (!(url instanceof URL)) {
20
+ throw new TypeError('string or URL object expected')
21
+ }
22
+
23
+ // Force https
24
+ url.protocol = 'https'
25
+
26
+ // Verify domain
27
+ if (!url.href.startsWith(baseURL)) {
28
+ throw new Error('URL must start with ' + baseURL)
29
+ }
30
+
31
+ let isAsset = url.pathname.startsWith('/content/dam')
32
+
33
+ // Strip canadasite prefix
34
+ if (!isAsset) {
35
+ url.pathname = url.pathname.replace(/^\/content\/canadasite\//, '')
36
+ }
37
+
38
+ // Verify root
39
+ if (!/^\/(en|fr|content\/dam)(\/|\.|$)/.test(url.pathname)) {
40
+ throw new Error('Invalid root')
41
+ }
42
+
43
+ // Strip Trailing slashes
44
+ url.pathname = url.pathname.replace(/\/+$/, '')
45
+
46
+ // Cache busting
47
+ if (type !== 'path') {
48
+ url.searchParams.set('_', Date.now())
49
+ }
50
+
51
+ // Handle other URL types
52
+ if (url.pathname.includes('/_jcr_content/par')) {
53
+ return normalizeReference(url, type)
54
+ } else if (isAsset) {
55
+ return normalizeAsset(url, type)
56
+ }
57
+
58
+ return normalizePage(url, type)
59
+ }
60
+
61
+ /**
62
+ * Normalize AEM page
63
+ * @param {URL} url
64
+ * @param {string} type
65
+ * @returns {URL}
66
+ * @private
67
+ */
68
+ function normalizePage(url, type) {
69
+ // Remove existing extension
70
+ url.pathname = url.pathname.replace(/\/?\.(sitemap\.xml|[^/.]+)$/, '')
71
+
72
+ if (type === 'meta') {
73
+ url.pathname += '/_jcr_content.json'
74
+ } else if (type === 'children') {
75
+ url.pathname += '.sitemap.xml'
76
+ } else if (type === 'content') {
77
+ url.pathname += '.html'
78
+ }
79
+
80
+ return type === 'path' ? url.pathname : url.href
81
+ }
82
+
83
+ /**
84
+ * Normalize AEM asset
85
+ * @param {URL} url
86
+ * @param {string} type
87
+ * @returns {URL}
88
+ * @private
89
+ */
90
+ function normalizeAsset(url, type) {
91
+ let isDir = /(\/|\/\w+)$/.test(url.pathname)
92
+
93
+ if (type === 'meta') {
94
+ url.pathname += isDir ? '.json' : '/_jcr_content.json'
95
+ } else if (type === 'children') {
96
+ throw new Error('Cant load children of an asset node')
97
+ } else if (type === 'content' && isDir) {
98
+ throw new Error('Cant load content of an asset node')
99
+ }
100
+
101
+ return type === 'path' ? url.pathname : url.href
102
+ }
103
+
104
+ /**
105
+ * Normalize AEM reference
106
+ * @param {URL} url
107
+ * @param {string} type
108
+ * @returns {URL}
109
+ * @private
110
+ */
111
+ function normalizeReference(url, type) {
112
+
113
+ if (type === 'path') {
114
+ throw new Error('Cant determine path of a reference node directly')
115
+ } else if (type === 'meta') {
116
+ url.pathname = url.pathname.replace(/\/image\.img\.\w+\//, '/image.json/')
117
+ } else if (type === 'children') {
118
+ throw new Error('Cant load children of a reference node')
119
+ }
120
+
121
+ return url.href
122
+ }
123
+
124
+ // Default export
125
+ module.exports = exports = normalize
126
+
127
+ // Expose the baseURL
128
+ exports.baseURL = baseURL
package/webpack.config.js CHANGED
@@ -2,7 +2,7 @@ const path = require('path');
2
2
 
3
3
  module.exports = {
4
4
  mode: 'production',
5
- entry: './src/index.mjs',
5
+ entry: './src/index.js',
6
6
  output: {
7
7
  path: path.resolve(__dirname, './dist/'),
8
8
  filename: 'ca.js',
@@ -21,5 +21,5 @@ module.exports = {
21
21
  commonjs2: 'cross-fetch',
22
22
  root: 'fetch'
23
23
  }
24
- },
24
+ }
25
25
  }
package/src/index.mjs DELETED
@@ -1,204 +0,0 @@
1
- import fetch from 'cross-fetch'
2
- import Bottleneck from 'bottleneck/light.js'
3
-
4
- const domain = 'www.canada.ca'
5
-
6
- /**
7
- * Map month name to index
8
- */
9
- const months = {
10
- 'Jan': '01',
11
- 'Feb': '02',
12
- 'Mar': '03',
13
- 'Apr': '04',
14
- 'May': '05',
15
- 'Jun': '06',
16
- 'Jul': '07',
17
- 'Aug': '08',
18
- 'Sep': '09',
19
- 'Oct': '10',
20
- 'Nov': '11',
21
- 'Dec': '12'
22
- }
23
-
24
- /**
25
- * Parse date in RFC1123 format
26
- * @param {string} date
27
- * @returns {Number}
28
- */
29
- function maybeParseDate(date) {
30
- // Basic
31
- if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
32
- return Date.parse(date)
33
- }
34
-
35
- // RFC1123
36
- let m = /^\w{3} (\w{3}) (\d{2}) (\d{4}) ([\d:]{8}) GMT([\-+]\d{4})$/.exec(date)
37
- return m ? Date.parse(m[3] + '-' + months[m[1]] + '-' + m[2] + 'T' + m[4] + m[5]) : date
38
- }
39
-
40
- /**
41
- * Throttles requests to prevent being throttled by the server
42
- */
43
- export const limiter = new Bottleneck({
44
- reservoir: 150,
45
- reservoirRefreshAmount: 150,
46
- reservoirRefreshInterval: 5000,
47
- maxConcurrent: 10,
48
- trackDoneStatus: true
49
- })
50
-
51
- /**
52
- * Format fetch URL
53
- * @param {string} path Node path
54
- * @param {string} suffix
55
- * @returns {string}
56
- */
57
- function formatURL(path, suffix) {
58
- return 'https://' + domain + path + suffix + '?_=' + Date.now()
59
- }
60
-
61
- /**
62
- * Normalize AEM path from URL
63
- * @param {string} url
64
- * @throws {Error} Invalid path
65
- * @returns {string}
66
- */
67
- export function normalizePath(url) {
68
- url = new URL(url, 'https://' + domain)
69
-
70
- if (url.hostname !== domain) {
71
- throw new Error('Invalid hostname ' + url.hostname)
72
- }
73
-
74
- if (url.pathname.startsWith('/content/dam/')) {
75
- return url.pathname
76
- } else if (url.pathname.startsWith('/content/canadasite/')) {
77
- url.pathname = url.pathname.substr(19)
78
- }
79
-
80
- if (/^\/(en|fr)(\/|$)/.test(url.pathname)) {
81
- return url.pathname.replace(/\.[^\.]+$/, '').replace(/\/$/, '')
82
- }
83
-
84
- throw new Error('Invalid path ' + url)
85
- }
86
-
87
- /**
88
- * Normalize node object (Modifies existing node if provided)
89
- * @param {string|Object} node
90
- * @throws {Error} Invalid node
91
- * @returns {Object}
92
- */
93
- export function normalizeNode(node) {
94
- if (typeof node === 'string') {
95
- return { path: normalizePath(node) }
96
- } else if (typeof node === 'object' && typeof node.path === 'string') {
97
- node.path = normalizePath(node.path)
98
- return node
99
- }
100
-
101
- throw new Error('Invalid node')
102
- }
103
-
104
- /**
105
- * Verify response properties
106
- * @param {Response} response
107
- * @throws {Error} Bad response
108
- * @returns {Response}
109
- */
110
- function verifyResponse(response) {
111
- if (!response.ok) {
112
- throw new Error(response.statusText)
113
- } else if (!response.url.includes(domain)) {
114
- throw new Error('Redirect')
115
- } else if (response.url.includes('/404.html')) {
116
- throw new Error('Not Found')
117
- }
118
-
119
- return response
120
- }
121
-
122
- /**
123
- * Get node children from XML sitemap
124
- * @param {string|Object} node
125
- * @returns {Promise<Object>}
126
- */
127
- export function children(node) {
128
- node = normalizeNode(node)
129
-
130
- return limiter.schedule({ expiration: 60000, priority: 4 }, url => fetch(url), formatURL(node.path, '.sitemap.xml'))
131
- .then(verifyResponse)
132
- .then(response => response.text())
133
- .then(xml => {
134
- // Parse XML
135
- node.children = xml.match(/<url>.*?<\/url>/g).map(url => {
136
- let loc = url.match(/<loc>([^<]+)<\/loc>/)
137
- let mod = url.match(/<lastmod>([^<]+)<\/lastmod>/)
138
- return {
139
- path: normalizePath(loc[1]),
140
- lastmod: mod ? Date.parse(mod[1]) : null
141
- }
142
- })
143
-
144
- // First entry may be the input node
145
- if (node.children.length && node.children[0].path === node.path) {
146
- node.lastmod = node.children.shift().lastmod
147
- }
148
-
149
- return node
150
- })
151
- }
152
-
153
- /**
154
- * Get node metadata from jcr content
155
- * @param {string|Object} node
156
- * @returns {Promise<Object>}
157
- */
158
- export function meta(node) {
159
- node = normalizeNode(node)
160
-
161
- return limiter.schedule({ expiration: 30000 }, url => fetch(url), formatURL(node.path, '/jcr:content.json'))
162
- .then(verifyResponse)
163
- .then(response => response.json())
164
- .then(meta => {
165
- // Reformat some meta properties
166
- Object.keys(meta).forEach(key => {
167
- if (meta[key] === 'true') {
168
- meta[key] = true
169
- } else if (meta[key] === 'false') {
170
- meta[key] = false
171
- } else if (key.endsWith('@TypeHint')) {
172
- delete meta[key]
173
- } else if (typeof meta[key] === 'string') {
174
- meta[key] = maybeParseDate(meta[key])
175
- }
176
- })
177
-
178
- node.meta = meta
179
- return node
180
- })
181
- }
182
-
183
- /**
184
- * Get node content
185
- * @param {string|Object} node
186
- * @returns {Promise<Object>}
187
- */
188
- export function content(node) {
189
- node = normalizeNode(node)
190
- let suffix = node.path.startsWith('/content/dam/') ? '' : '.html'
191
-
192
- return limiter.schedule({ expiration: 30000 }, url => fetch(url), formatURL(node.path, suffix))
193
- .then(verifyResponse)
194
- .then(response => {
195
- if (response.headers.get('Content-Type').includes('application/json')) {
196
- return response.json()
197
- }
198
- return response.text()
199
- })
200
- .then(content => {
201
- node.content = content
202
- return node
203
- })
204
- }