From e9e3d97904a0c2b269519a3d495294217aa485b2 Mon Sep 17 00:00:00 2001 From: f-w Date: Sun, 26 May 2024 23:20:52 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20=20@=20af267?= =?UTF-8?q?f20671f180d024244abd0e6f2958661b734=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 404.html | 4 ++-- assets/{404.html-eadb7290.js => 404.html-0303a6b9.js} | 2 +- assets/{app-77c416fe.js => app-be471a62.js} | 4 ++-- ...ram.html-f6d30c08.js => filterQueryParam.html-be5e74b7.js} | 2 +- ...html-63f24e00.js => filterQueryParamCode.html-5188a76f.js} | 2 +- ...l-3c9d0dce.js => filterQueryParamExample.html-30c0817c.js} | 2 +- assets/{index.html-287f081e.js => index.html-00524c3e.js} | 2 +- assets/{index.html-ea9c36e4.js => index.html-15bf84b8.js} | 2 +- assets/{index.html-03b54c01.js => index.html-1a8bbf38.js} | 2 +- assets/{index.html-9b491f72.js => index.html-1bba510e.js} | 2 +- assets/{index.html-ffb5aa93.js => index.html-200962d8.js} | 2 +- assets/{index.html-4c7abb90.js => index.html-219bb653.js} | 2 +- assets/{index.html-baba90e5.js => index.html-255cab24.js} | 2 +- assets/{index.html-4e48a4d5.js => index.html-287c16ec.js} | 2 +- assets/{index.html-6cc49290.js => index.html-29e78a52.js} | 2 +- assets/{index.html-ffbaf637.js => index.html-3031d490.js} | 2 +- assets/{index.html-819c8b73.js => index.html-421ecf2b.js} | 2 +- assets/{index.html-8e2d950c.js => index.html-45a39212.js} | 2 +- assets/{index.html-46fb1d05.js => index.html-4a2d667d.js} | 2 +- assets/{index.html-8b2ab8f8.js => index.html-4b695468.js} | 2 +- assets/{index.html-091f58dc.js => index.html-566c81a6.js} | 2 +- assets/{index.html-05c25248.js => index.html-599e9037.js} | 2 +- assets/{index.html-ac8da399.js => index.html-59f066f2.js} | 2 +- assets/{index.html-9d0a34f4.js => index.html-64244dc0.js} | 2 +- assets/{index.html-5795a2ec.js => index.html-6906b24f.js} | 2 +- assets/{index.html-1fa10348.js => index.html-698e3d15.js} | 2 +- assets/{index.html-e98535e1.js => index.html-73d191ff.js} | 2 +- assets/{index.html-c12f943f.js => index.html-9cf75b13.js} | 2 +- assets/{index.html-70ee88ef.js => index.html-9fc267c0.js} | 2 +- assets/{index.html-51b84ddd.js => index.html-a40f24b5.js} | 2 +- assets/{index.html-81176e6b.js => index.html-a85cf3ae.js} | 2 +- assets/{index.html-04c7721d.js => index.html-aaab02f3.js} | 2 +- assets/{index.html-321d8b52.js => index.html-aad2a358.js} | 2 +- assets/{index.html-95e23621.js => index.html-b9f3ea8d.js} | 2 +- assets/{index.html-13cd24c1.js => index.html-bf54ea9c.js} | 2 +- assets/{index.html-00458894.js => index.html-c60b591d.js} | 2 +- assets/{index.html-655164a6.js => index.html-c8dd00de.js} | 2 +- assets/{index.html-cd7a5a49.js => index.html-d1f8fc1e.js} | 2 +- assets/{index.html-5fb6acba.js => index.html-d4f673ca.js} | 2 +- assets/{index.html-6b8cefcc.js => index.html-db6fe449.js} | 2 +- assets/{index.html-cf1801d6.js => index.html-e0ff510f.js} | 2 +- assets/{index.html-38194d5f.js => index.html-e9ca5832.js} | 2 +- assets/{index.html-54e79f35.js => index.html-ea71ab91.js} | 2 +- assets/{index.html-90a99e66.js => index.html-f201ed52.js} | 2 +- assets/{index.html-a3a34a72.js => index.html-f2c18423.js} | 2 +- ...ilter.html-a67ec5c7.js => jmespathFilter.html-f6652c45.js} | 2 +- .../{throttle.html-b33db38d.js => throttle.html-2d038ede.js} | 2 +- ...aram.html-e8cad04b.js => whereQueryParam.html-6115caa0.js} | 2 +- ....html-feffff85.js => whereQueryParamCode.html-ec3065e2.js} | 2 +- ...ml-810bff15.js => whereQueryParamExample.html-8a4f7134.js} | 2 +- docs/acknowledgments/index.html | 4 ++-- docs/api-administrator/index.html | 4 ++-- docs/api-bounce/index.html | 4 ++-- docs/api-config/index.html | 4 ++-- docs/api-notification/index.html | 4 ++-- docs/api-overview/index.html | 4 ++-- docs/api-subscription/index.html | 4 ++-- docs/benchmarks/index.html | 4 ++-- docs/bulk-import/index.html | 4 ++-- docs/conduct/index.html | 4 ++-- docs/config-adminIpList/index.html | 4 ++-- docs/config-certificates/index.html | 4 ++-- docs/config-cronJobs/index.html | 4 ++-- docs/config-database/index.html | 4 ++-- docs/config-email/index.html | 4 ++-- docs/config-httpHost/index.html | 4 ++-- docs/config-internalHttpHost/index.html | 4 ++-- docs/config-middleware/index.html | 4 ++-- docs/config-nodeRoles/index.html | 4 ++-- docs/config-notification/index.html | 4 ++-- docs/config-oidc/index.html | 4 ++-- docs/config-overview/index.html | 4 ++-- docs/config-reverseProxyIpLists/index.html | 4 ++-- docs/config-rsaKeys/index.html | 4 ++-- docs/config-sms/index.html | 4 ++-- docs/config-subscription/index.html | 4 ++-- docs/config-workerProcessCount/index.html | 4 ++-- docs/developer-notes/index.html | 4 ++-- docs/health-check/index.html | 4 ++-- docs/index.html | 4 ++-- docs/installation/index.html | 4 ++-- docs/memory-dump/index.html | 4 ++-- docs/overview/index.html | 4 ++-- docs/quickstart/index.html | 4 ++-- docs/shared/filterQueryParam.html | 4 ++-- docs/shared/filterQueryParamCode.html | 4 ++-- docs/shared/filterQueryParamExample.html | 4 ++-- docs/shared/jmespathFilter.html | 4 ++-- docs/shared/throttle.html | 4 ++-- docs/shared/whereQueryParam.html | 4 ++-- docs/shared/whereQueryParamCode.html | 4 ++-- docs/shared/whereQueryParamExample.html | 4 ++-- docs/upgrade/index.html | 4 ++-- docs/web-console/index.html | 4 ++-- docs/what's-new/index.html | 4 ++-- help/index.html | 4 ++-- index.html | 4 ++-- 97 files changed, 146 insertions(+), 146 deletions(-) rename assets/{404.html-eadb7290.js => 404.html-0303a6b9.js} (63%) rename assets/{app-77c416fe.js => app-be471a62.js} (96%) rename assets/{filterQueryParam.html-f6d30c08.js => filterQueryParam.html-be5e74b7.js} (96%) rename assets/{filterQueryParamCode.html-63f24e00.js => filterQueryParamCode.html-5188a76f.js} (81%) rename assets/{filterQueryParamExample.html-3c9d0dce.js => filterQueryParamExample.html-30c0817c.js} (86%) rename assets/{index.html-287f081e.js => index.html-00524c3e.js} (99%) rename assets/{index.html-ea9c36e4.js => index.html-15bf84b8.js} (99%) rename assets/{index.html-03b54c01.js => index.html-1a8bbf38.js} (98%) rename assets/{index.html-9b491f72.js => index.html-1bba510e.js} (99%) rename assets/{index.html-ffb5aa93.js => index.html-200962d8.js} (97%) rename assets/{index.html-4c7abb90.js => index.html-219bb653.js} (99%) rename assets/{index.html-baba90e5.js => index.html-255cab24.js} (95%) rename assets/{index.html-4e48a4d5.js => index.html-287c16ec.js} (97%) rename assets/{index.html-6cc49290.js => index.html-29e78a52.js} (98%) rename assets/{index.html-ffbaf637.js => index.html-3031d490.js} (99%) rename assets/{index.html-819c8b73.js => index.html-421ecf2b.js} (99%) rename assets/{index.html-8e2d950c.js => index.html-45a39212.js} (99%) rename assets/{index.html-46fb1d05.js => index.html-4a2d667d.js} (97%) rename assets/{index.html-8b2ab8f8.js => index.html-4b695468.js} (99%) rename assets/{index.html-091f58dc.js => index.html-566c81a6.js} (93%) rename assets/{index.html-05c25248.js => index.html-599e9037.js} (95%) rename assets/{index.html-ac8da399.js => index.html-59f066f2.js} (94%) rename assets/{index.html-9d0a34f4.js => index.html-64244dc0.js} (98%) rename assets/{index.html-5795a2ec.js => index.html-6906b24f.js} (99%) rename assets/{index.html-1fa10348.js => index.html-698e3d15.js} (96%) rename assets/{index.html-e98535e1.js => index.html-73d191ff.js} (98%) rename assets/{index.html-c12f943f.js => index.html-9cf75b13.js} (63%) rename assets/{index.html-70ee88ef.js => index.html-9fc267c0.js} (97%) rename assets/{index.html-51b84ddd.js => index.html-a40f24b5.js} (98%) rename assets/{index.html-81176e6b.js => index.html-a85cf3ae.js} (99%) rename assets/{index.html-04c7721d.js => index.html-aaab02f3.js} (99%) rename assets/{index.html-321d8b52.js => index.html-aad2a358.js} (98%) rename assets/{index.html-95e23621.js => index.html-b9f3ea8d.js} (99%) rename assets/{index.html-13cd24c1.js => index.html-bf54ea9c.js} (96%) rename assets/{index.html-00458894.js => index.html-c60b591d.js} (99%) rename assets/{index.html-655164a6.js => index.html-c8dd00de.js} (99%) rename assets/{index.html-cd7a5a49.js => index.html-d1f8fc1e.js} (96%) rename assets/{index.html-5fb6acba.js => index.html-d4f673ca.js} (99%) rename assets/{index.html-6b8cefcc.js => index.html-db6fe449.js} (96%) rename assets/{index.html-cf1801d6.js => index.html-e0ff510f.js} (91%) rename assets/{index.html-38194d5f.js => index.html-e9ca5832.js} (99%) rename assets/{index.html-54e79f35.js => index.html-ea71ab91.js} (98%) rename assets/{index.html-90a99e66.js => index.html-f201ed52.js} (99%) rename assets/{index.html-a3a34a72.js => index.html-f2c18423.js} (99%) rename assets/{jmespathFilter.html-a67ec5c7.js => jmespathFilter.html-f6652c45.js} (94%) rename assets/{throttle.html-b33db38d.js => throttle.html-2d038ede.js} (94%) rename assets/{whereQueryParam.html-e8cad04b.js => whereQueryParam.html-6115caa0.js} (85%) rename assets/{whereQueryParamCode.html-feffff85.js => whereQueryParamCode.html-ec3065e2.js} (80%) rename assets/{whereQueryParamExample.html-810bff15.js => whereQueryParamExample.html-8a4f7134.js} (86%) diff --git a/404.html b/404.html index 55df8165a..57336a877 100644 --- a/404.html +++ b/404.html @@ -24,10 +24,10 @@ NotifyBC - +

404

Looks like we've got some broken links.
Take me home
- + diff --git a/assets/404.html-eadb7290.js b/assets/404.html-0303a6b9.js similarity index 63% rename from assets/404.html-eadb7290.js rename to assets/404.html-0303a6b9.js index 2c810a527..002e48a9e 100644 --- a/assets/404.html-eadb7290.js +++ b/assets/404.html-0303a6b9.js @@ -1 +1 @@ -import{_ as e,o as c,c as t}from"./app-77c416fe.js";const _={};function o(r,n){return c(),t("div")}const a=e(_,[["render",o],["__file","404.html.vue"]]);export{a as default}; +import{_ as e,o as c,c as t}from"./app-be471a62.js";const _={};function o(r,n){return c(),t("div")}const a=e(_,[["render",o],["__file","404.html.vue"]]);export{a as default}; diff --git a/assets/app-77c416fe.js b/assets/app-be471a62.js similarity index 96% rename from assets/app-77c416fe.js rename to assets/app-be471a62.js index b90441ebb..983629668 100644 --- a/assets/app-77c416fe.js +++ b/assets/app-be471a62.js @@ -1,4 +1,4 @@ -const ma="modulepreload",va=function(e){return"/NotifyBC/"+e},os={},O=function(t,n,o){if(!n||n.length===0)return t();const r=document.getElementsByTagName("link");return Promise.all(n.map(s=>{if(s=va(s),s in os)return;os[s]=!0;const i=s.endsWith(".css"),l=i?'[rel="stylesheet"]':"";if(!!o)for(let u=r.length-1;u>=0;u--){const f=r[u];if(f.href===s&&(!i||f.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${s}"]${l}`))return;const c=document.createElement("link");if(c.rel=i?"stylesheet":ma,i||(c.as="script",c.crossOrigin=""),c.href=s,document.head.appendChild(c),i)return new Promise((u,f)=>{c.addEventListener("load",u),c.addEventListener("error",()=>f(new Error(`Unable to preload CSS for ${s}`)))})})).then(()=>t()).catch(s=>{const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=s,window.dispatchEvent(i),!i.defaultPrevented)throw s})};function yr(e,t){const n=Object.create(null),o=e.split(",");for(let r=0;r!!n[r.toLowerCase()]:r=>!!n[r]}const Te={},rn=[],st=()=>{},ga=()=>!1,_a=/^on[^a-z]/,zn=e=>_a.test(e),Er=e=>e.startsWith("onUpdate:"),Se=Object.assign,wr=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},ba=Object.prototype.hasOwnProperty,pe=(e,t)=>ba.call(e,t),q=Array.isArray,sn=e=>Un(e)==="[object Map]",So=e=>Un(e)==="[object Set]",rs=e=>Un(e)==="[object Date]",re=e=>typeof e=="function",me=e=>typeof e=="string",Rn=e=>typeof e=="symbol",ye=e=>e!==null&&typeof e=="object",xi=e=>ye(e)&&re(e.then)&&re(e.catch),Pi=Object.prototype.toString,Un=e=>Pi.call(e),ya=e=>Un(e).slice(8,-1),Oi=e=>Un(e)==="[object Object]",Cr=e=>me(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,xn=yr(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),ko=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Ea=/-(\w)/g,ft=ko(e=>e.replace(Ea,(t,n)=>n?n.toUpperCase():"")),wa=/\B([A-Z])/g,Yt=ko(e=>e.replace(wa,"-$1").toLowerCase()),Io=ko(e=>e.charAt(0).toUpperCase()+e.slice(1)),Uo=ko(e=>e?`on${Io(e)}`:""),Dn=(e,t)=>!Object.is(e,t),fo=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Si=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Ca=e=>{const t=me(e)?Number(e):NaN;return isNaN(t)?e:t};let ss;const or=()=>ss||(ss=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Qt(e){if(q(e)){const t={};for(let n=0;n{if(n){const o=n.split(La);o.length>1&&(t[o[0].trim()]=o[1].trim())}}),t}function ze(e){let t="";if(me(e))t=e;else if(q(e))for(let n=0;nAo(n,t))}const Ie=e=>me(e)?e:e==null?"":q(e)||ye(e)&&(e.toString===Pi||!re(e.toString))?JSON.stringify(e,Ii,2):String(e),Ii=(e,t)=>t&&t.__v_isRef?Ii(e,t.value):sn(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[o,r])=>(n[`${o} =>`]=r,n),{})}:So(t)?{[`Set(${t.size})`]:[...t.values()]}:ye(t)&&!q(t)&&!Oi(t)?String(t):t;let Qe;class Aa{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=Qe,!t&&Qe&&(this.index=(Qe.scopes||(Qe.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=Qe;try{return Qe=this,t()}finally{Qe=n}}}on(){Qe=this}off(){Qe=this.parent}stop(t){if(this._active){let n,o;for(n=0,o=this.effects.length;n{const t=new Set(e);return t.w=0,t.n=0,t},Ri=e=>(e.w&Mt)>0,Di=e=>(e.n&Mt)>0,$a=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let o=0;o{(u==="length"||u>=a)&&l.push(c)})}else switch(n!==void 0&&l.push(i.get(n)),t){case"add":q(e)?Cr(n)&&l.push(i.get("length")):(l.push(i.get(qt)),sn(e)&&l.push(i.get(sr)));break;case"delete":q(e)||(l.push(i.get(qt)),sn(e)&&l.push(i.get(sr)));break;case"set":sn(e)&&l.push(i.get(qt));break}if(l.length===1)l[0]&&ir(l[0]);else{const a=[];for(const c of l)c&&a.push(...c);ir(Tr(a))}}function ir(e,t){const n=q(e)?e:[...e];for(const o of n)o.computed&&ls(o);for(const o of n)o.computed||ls(o)}function ls(e,t){(e!==ot||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}function Na(e,t){var n;return(n=vo.get(e))==null?void 0:n.get(t)}const Ha=yr("__proto__,__v_isRef,__isVue"),Ni=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Rn)),Ba=xr(),ja=xr(!1,!0),Va=xr(!0),as=Fa();function Fa(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const o=he(this);for(let s=0,i=this.length;s{e[t]=function(...n){vn();const o=he(this)[t].apply(this,n);return gn(),o}}),e}function za(e){const t=he(this);return qe(t,"has",e),t.hasOwnProperty(e)}function xr(e=!1,t=!1){return function(o,r,s){if(r==="__v_isReactive")return!e;if(r==="__v_isReadonly")return e;if(r==="__v_isShallow")return t;if(r==="__v_raw"&&s===(e?t?sc:Fi:t?Vi:ji).get(o))return o;const i=q(o);if(!e){if(i&&pe(as,r))return Reflect.get(as,r,s);if(r==="hasOwnProperty")return za}const l=Reflect.get(o,r,s);return(Rn(r)?Ni.has(r):Ha(r))||(e||qe(o,"get",r),t)?l:Ae(l)?i&&Cr(r)?l:l.value:ye(l)?e?_n(l):Kn(l):l}}const Ua=Hi(),Ka=Hi(!0);function Hi(e=!1){return function(n,o,r,s){let i=n[o];if(un(i)&&Ae(i)&&!Ae(r))return!1;if(!e&&(!go(r)&&!un(r)&&(i=he(i),r=he(r)),!q(n)&&Ae(i)&&!Ae(r)))return i.value=r,!0;const l=q(n)&&Cr(o)?Number(o)e,Ro=e=>Reflect.getPrototypeOf(e);function Zn(e,t,n=!1,o=!1){e=e.__v_raw;const r=he(e),s=he(t);n||(t!==s&&qe(r,"get",t),qe(r,"get",s));const{has:i}=Ro(r),l=o?Pr:n?kr:$n;if(i.call(r,t))return l(e.get(t));if(i.call(r,s))return l(e.get(s));e!==r&&e.get(t)}function Xn(e,t=!1){const n=this.__v_raw,o=he(n),r=he(e);return t||(e!==r&&qe(o,"has",e),qe(o,"has",r)),e===r?n.has(e):n.has(e)||n.has(r)}function eo(e,t=!1){return e=e.__v_raw,!t&&qe(he(e),"iterate",qt),Reflect.get(e,"size",e)}function cs(e){e=he(e);const t=he(this);return Ro(t).has.call(t,e)||(t.add(e),_t(t,"add",e,e)),this}function us(e,t){t=he(t);const n=he(this),{has:o,get:r}=Ro(n);let s=o.call(n,e);s||(e=he(e),s=o.call(n,e));const i=r.call(n,e);return n.set(e,t),s?Dn(t,i)&&_t(n,"set",e,t):_t(n,"add",e,t),this}function fs(e){const t=he(this),{has:n,get:o}=Ro(t);let r=n.call(t,e);r||(e=he(e),r=n.call(t,e)),o&&o.call(t,e);const s=t.delete(e);return r&&_t(t,"delete",e,void 0),s}function ds(){const e=he(this),t=e.size!==0,n=e.clear();return t&&_t(e,"clear",void 0,void 0),n}function to(e,t){return function(o,r){const s=this,i=s.__v_raw,l=he(i),a=t?Pr:e?kr:$n;return!e&&qe(l,"iterate",qt),i.forEach((c,u)=>o.call(r,a(c),a(u),s))}}function no(e,t,n){return function(...o){const r=this.__v_raw,s=he(r),i=sn(s),l=e==="entries"||e===Symbol.iterator&&i,a=e==="keys"&&i,c=r[e](...o),u=n?Pr:t?kr:$n;return!t&&qe(s,"iterate",a?sr:qt),{next(){const{value:f,done:p}=c.next();return p?{value:f,done:p}:{value:l?[u(f[0]),u(f[1])]:u(f),done:p}},[Symbol.iterator](){return this}}}}function xt(e){return function(...t){return e==="delete"?!1:this}}function Ga(){const e={get(s){return Zn(this,s)},get size(){return eo(this)},has:Xn,add:cs,set:us,delete:fs,clear:ds,forEach:to(!1,!1)},t={get(s){return Zn(this,s,!1,!0)},get size(){return eo(this)},has:Xn,add:cs,set:us,delete:fs,clear:ds,forEach:to(!1,!0)},n={get(s){return Zn(this,s,!0)},get size(){return eo(this,!0)},has(s){return Xn.call(this,s,!0)},add:xt("add"),set:xt("set"),delete:xt("delete"),clear:xt("clear"),forEach:to(!0,!1)},o={get(s){return Zn(this,s,!0,!0)},get size(){return eo(this,!0)},has(s){return Xn.call(this,s,!0)},add:xt("add"),set:xt("set"),delete:xt("delete"),clear:xt("clear"),forEach:to(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(s=>{e[s]=no(s,!1,!1),n[s]=no(s,!0,!1),t[s]=no(s,!1,!0),o[s]=no(s,!0,!0)}),[e,n,t,o]}const[Za,Xa,ec,tc]=Ga();function Or(e,t){const n=t?e?tc:ec:e?Xa:Za;return(o,r,s)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?o:Reflect.get(pe(n,r)&&r in o?n:o,r,s)}const nc={get:Or(!1,!1)},oc={get:Or(!1,!0)},rc={get:Or(!0,!1)},ji=new WeakMap,Vi=new WeakMap,Fi=new WeakMap,sc=new WeakMap;function ic(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function lc(e){return e.__v_skip||!Object.isExtensible(e)?0:ic(ya(e))}function Kn(e){return un(e)?e:Sr(e,!1,Bi,nc,ji)}function zi(e){return Sr(e,!1,Ya,oc,Vi)}function _n(e){return Sr(e,!0,Qa,rc,Fi)}function Sr(e,t,n,o,r){if(!ye(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const s=r.get(e);if(s)return s;const i=lc(e);if(i===0)return e;const l=new Proxy(e,i===2?o:n);return r.set(e,l),l}function ln(e){return un(e)?ln(e.__v_raw):!!(e&&e.__v_isReactive)}function un(e){return!!(e&&e.__v_isReadonly)}function go(e){return!!(e&&e.__v_isShallow)}function Ui(e){return ln(e)||un(e)}function he(e){const t=e&&e.__v_raw;return t?he(t):e}function Ki(e){return mo(e,"__v_skip",!0),e}const $n=e=>ye(e)?Kn(e):e,kr=e=>ye(e)?_n(e):e;function Ir(e){Rt&&ot&&(e=he(e),Mi(e.dep||(e.dep=Tr())))}function Ar(e,t){e=he(e);const n=e.dep;n&&ir(n)}function Ae(e){return!!(e&&e.__v_isRef===!0)}function be(e){return qi(e,!1)}function Rr(e){return qi(e,!0)}function qi(e,t){return Ae(e)?e:new ac(e,t)}class ac{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:he(t),this._value=n?t:$n(t)}get value(){return Ir(this),this._value}set value(t){const n=this.__v_isShallow||go(t)||un(t);t=n?t:he(t),Dn(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:$n(t),Ar(this))}}function X(e){return Ae(e)?e.value:e}const cc={get:(e,t,n)=>X(Reflect.get(e,t,n)),set:(e,t,n,o)=>{const r=e[t];return Ae(r)&&!Ae(n)?(r.value=n,!0):Reflect.set(e,t,n,o)}};function Wi(e){return ln(e)?e:new Proxy(e,cc)}class uc{constructor(t){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:o}=t(()=>Ir(this),()=>Ar(this));this._get=n,this._set=o}get value(){return this._get()}set value(t){this._set(t)}}function fc(e){return new uc(e)}function Dr(e){const t=q(e)?new Array(e.length):{};for(const n in e)t[n]=Ji(e,n);return t}class dc{constructor(t,n,o){this._object=t,this._key=n,this._defaultValue=o,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return Na(he(this._object),this._key)}}class pc{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function hc(e,t,n){return Ae(e)?e:re(e)?new pc(e):ye(e)&&arguments.length>1?Ji(e,t,n):be(e)}function Ji(e,t,n){const o=e[t];return Ae(o)?o:new dc(e,t,n)}class mc{constructor(t,n,o,r){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this._dirty=!0,this.effect=new Lr(t,()=>{this._dirty||(this._dirty=!0,Ar(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!r,this.__v_isReadonly=o}get value(){const t=he(this);return Ir(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}function vc(e,t,n=!1){let o,r;const s=re(e);return s?(o=e,r=st):(o=e.get,r=e.set),new mc(o,r,s||!r,n)}function Dt(e,t,n,o){let r;try{r=o?e(...o):e()}catch(s){qn(s,t,n)}return r}function Xe(e,t,n,o){if(re(e)){const s=Dt(e,t,n,o);return s&&xi(s)&&s.catch(i=>{qn(i,t,n)}),s}const r=[];for(let s=0;s>>1;Nn(je[o])ut&&je.splice(t,1)}function yc(e){q(e)?an.push(...e):(!mt||!mt.includes(e,e.allowRecurse?Ft+1:Ft))&&an.push(e),Yi()}function ps(e,t=Mn?ut+1:0){for(;tNn(n)-Nn(o)),Ft=0;Fte.id==null?1/0:e.id,Ec=(e,t)=>{const n=Nn(e)-Nn(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function Gi(e){lr=!1,Mn=!0,je.sort(Ec);const t=st;try{for(ut=0;utme(v)?v.trim():v)),f&&(r=n.map(Si))}let l,a=o[l=Uo(t)]||o[l=Uo(ft(t))];!a&&s&&(a=o[l=Uo(Yt(t))]),a&&Xe(a,e,6,r);const c=o[l+"Once"];if(c){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Xe(c,e,6,r)}}function Zi(e,t,n=!1){const o=t.emitsCache,r=o.get(e);if(r!==void 0)return r;const s=e.emits;let i={},l=!1;if(!re(e)){const a=c=>{const u=Zi(c,t,!0);u&&(l=!0,Se(i,u))};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!s&&!l?(ye(e)&&o.set(e,null),null):(q(s)?s.forEach(a=>i[a]=null):Se(i,s),ye(e)&&o.set(e,i),i)}function Mo(e,t){return!e||!zn(t)?!1:(t=t.slice(2).replace(/Once$/,""),pe(e,t[0].toLowerCase()+t.slice(1))||pe(e,Yt(t))||pe(e,t))}let Ne=null,No=null;function bo(e){const t=Ne;return Ne=e,No=e&&e.type.__scopeId||null,t}function Cc(e){No=e}function Tc(){No=null}function Me(e,t=Ne,n){if(!t||e._n)return e;const o=(...r)=>{o._d&&Ls(-1);const s=bo(t);let i;try{i=e(...r)}finally{bo(s),o._d&&Ls(1)}return i};return o._n=!0,o._c=!0,o._d=!0,o}function Ko(e){const{type:t,vnode:n,proxy:o,withProxy:r,props:s,propsOptions:[i],slots:l,attrs:a,emit:c,render:u,renderCache:f,data:p,setupState:v,ctx:y,inheritAttrs:w}=e;let x,g;const b=bo(e);try{if(n.shapeFlag&4){const k=r||o;x=nt(u.call(k,k,f,s,v,p,y)),g=a}else{const k=t;x=nt(k.length>1?k(s,{attrs:a,slots:l,emit:c}):k(s,null)),g=t.props?a:Lc(a)}}catch(k){Sn.length=0,qn(k,e,1),x=ne(Ye)}let I=x;if(g&&w!==!1){const k=Object.keys(g),{shapeFlag:W}=I;k.length&&W&7&&(i&&k.some(Er)&&(g=xc(g,i)),I=Nt(I,g))}return n.dirs&&(I=Nt(I),I.dirs=I.dirs?I.dirs.concat(n.dirs):n.dirs),n.transition&&(I.transition=n.transition),x=I,bo(b),x}const Lc=e=>{let t;for(const n in e)(n==="class"||n==="style"||zn(n))&&((t||(t={}))[n]=e[n]);return t},xc=(e,t)=>{const n={};for(const o in e)(!Er(o)||!(o.slice(9)in t))&&(n[o]=e[o]);return n};function Pc(e,t,n){const{props:o,children:r,component:s}=e,{props:i,children:l,patchFlag:a}=t,c=s.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&a>=0){if(a&1024)return!0;if(a&16)return o?hs(o,i,c):!!i;if(a&8){const u=t.dynamicProps;for(let f=0;fe.__isSuspense;function Xi(e,t){t&&t.pendingBranch?q(e)?t.effects.push(...e):t.effects.push(e):yc(e)}function el(e,t){return Mr(e,null,t)}const oo={};function et(e,t,n){return Mr(e,t,n)}function Mr(e,t,{immediate:n,deep:o,flush:r,onTrack:s,onTrigger:i}=Te){var l;const a=Ai()===((l=ke)==null?void 0:l.scope)?ke:null;let c,u=!1,f=!1;if(Ae(e)?(c=()=>e.value,u=go(e)):ln(e)?(c=()=>e,o=!0):q(e)?(f=!0,u=e.some(k=>ln(k)||go(k)),c=()=>e.map(k=>{if(Ae(k))return k.value;if(ln(k))return Kt(k);if(re(k))return Dt(k,a,2)})):re(e)?t?c=()=>Dt(e,a,2):c=()=>{if(!(a&&a.isUnmounted))return p&&p(),Xe(e,a,3,[v])}:c=st,t&&o){const k=c;c=()=>Kt(k())}let p,v=k=>{p=b.onStop=()=>{Dt(k,a,4)}},y;if(pn)if(v=st,t?n&&Xe(t,a,3,[c(),f?[]:void 0,v]):c(),r==="sync"){const k=Cu();y=k.__watcherHandles||(k.__watcherHandles=[])}else return st;let w=f?new Array(e.length).fill(oo):oo;const x=()=>{if(b.active)if(t){const k=b.run();(o||u||(f?k.some((W,ee)=>Dn(W,w[ee])):Dn(k,w)))&&(p&&p(),Xe(t,a,3,[k,w===oo?void 0:f&&w[0]===oo?[]:w,v]),w=k)}else b.run()};x.allowRecurse=!!t;let g;r==="sync"?g=x:r==="post"?g=()=>Ke(x,a&&a.suspense):(x.pre=!0,a&&(x.id=a.uid),g=()=>$o(x));const b=new Lr(c,g);t?n?x():w=b.run():r==="post"?Ke(b.run.bind(b),a&&a.suspense):b.run();const I=()=>{b.stop(),a&&a.scope&&wr(a.scope.effects,b)};return y&&y.push(I),I}function kc(e,t,n){const o=this.proxy,r=me(e)?e.includes(".")?tl(o,e):()=>o[e]:e.bind(o,o);let s;re(t)?s=t:(s=t.handler,n=t);const i=ke;dn(this);const l=Mr(r,s.bind(o),n);return i?dn(i):Jt(),l}function tl(e,t){const n=t.split(".");return()=>{let o=e;for(let r=0;r{Kt(n,t)});else if(Oi(e))for(const n in e)Kt(e[n],t);return e}function Hn(e,t){const n=Ne;if(n===null)return e;const o=Vo(n)||n.proxy,r=e.dirs||(e.dirs=[]);for(let s=0;s{e.isMounted=!0}),Jn(()=>{e.isUnmounting=!0}),e}const Ge=[Function,Array],nl={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Ge,onEnter:Ge,onAfterEnter:Ge,onEnterCancelled:Ge,onBeforeLeave:Ge,onLeave:Ge,onAfterLeave:Ge,onLeaveCancelled:Ge,onBeforeAppear:Ge,onAppear:Ge,onAfterAppear:Ge,onAppearCancelled:Ge},Ac={name:"BaseTransition",props:nl,setup(e,{slots:t}){const n=yl(),o=Ic();let r;return()=>{const s=t.default&&rl(t.default(),!0);if(!s||!s.length)return;let i=s[0];if(s.length>1){for(const w of s)if(w.type!==Ye){i=w;break}}const l=he(e),{mode:a}=l;if(o.isLeaving)return qo(i);const c=ms(i);if(!c)return qo(i);const u=ar(c,l,o,n);cr(c,u);const f=n.subTree,p=f&&ms(f);let v=!1;const{getTransitionKey:y}=c.type;if(y){const w=y();r===void 0?r=w:w!==r&&(r=w,v=!0)}if(p&&p.type!==Ye&&(!zt(c,p)||v)){const w=ar(p,l,o,n);if(cr(p,w),a==="out-in")return o.isLeaving=!0,w.afterLeave=()=>{o.isLeaving=!1,n.update.active!==!1&&n.update()},qo(i);a==="in-out"&&c.type!==Ye&&(w.delayLeave=(x,g,b)=>{const I=ol(o,p);I[String(p.key)]=p,x._leaveCb=()=>{g(),x._leaveCb=void 0,delete u.delayedLeave},u.delayedLeave=b})}return i}}},Rc=Ac;function ol(e,t){const{leavingVNodes:n}=e;let o=n.get(t.type);return o||(o=Object.create(null),n.set(t.type,o)),o}function ar(e,t,n,o){const{appear:r,mode:s,persisted:i=!1,onBeforeEnter:l,onEnter:a,onAfterEnter:c,onEnterCancelled:u,onBeforeLeave:f,onLeave:p,onAfterLeave:v,onLeaveCancelled:y,onBeforeAppear:w,onAppear:x,onAfterAppear:g,onAppearCancelled:b}=t,I=String(e.key),k=ol(n,e),W=(m,F)=>{m&&Xe(m,o,9,F)},ee=(m,F)=>{const B=F[1];W(m,F),q(m)?m.every(Y=>Y.length<=1)&&B():m.length<=1&&B()},N={mode:s,persisted:i,beforeEnter(m){let F=l;if(!n.isMounted)if(r)F=w||l;else return;m._leaveCb&&m._leaveCb(!0);const B=k[I];B&&zt(e,B)&&B.el._leaveCb&&B.el._leaveCb(),W(F,[m])},enter(m){let F=a,B=c,Y=u;if(!n.isMounted)if(r)F=x||a,B=g||c,Y=b||u;else return;let L=!1;const R=m._enterCb=D=>{L||(L=!0,D?W(Y,[m]):W(B,[m]),N.delayedLeave&&N.delayedLeave(),m._enterCb=void 0)};F?ee(F,[m,R]):R()},leave(m,F){const B=String(e.key);if(m._enterCb&&m._enterCb(!0),n.isUnmounting)return F();W(f,[m]);let Y=!1;const L=m._leaveCb=R=>{Y||(Y=!0,F(),R?W(y,[m]):W(v,[m]),m._leaveCb=void 0,k[B]===e&&delete k[B])};k[B]=e,p?ee(p,[m,L]):L()},clone(m){return ar(m,t,n,o)}};return N}function qo(e){if(Wn(e))return e=Nt(e),e.children=null,e}function ms(e){return Wn(e)?e.children?e.children[0]:void 0:e}function cr(e,t){e.shapeFlag&6&&e.component?cr(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function rl(e,t=!1,n){let o=[],r=0;for(let s=0;s1)for(let s=0;sSe({name:e.name},t,{setup:e}))():e}const cn=e=>!!e.type.__asyncLoader;function te(e){re(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:o,delay:r=200,timeout:s,suspensible:i=!0,onError:l}=e;let a=null,c,u=0;const f=()=>(u++,a=null,p()),p=()=>{let v;return a||(v=a=t().catch(y=>{if(y=y instanceof Error?y:new Error(String(y)),l)return new Promise((w,x)=>{l(y,()=>w(f()),()=>x(y),u+1)});throw y}).then(y=>v!==a&&a?a:(y&&(y.__esModule||y[Symbol.toStringTag]==="Module")&&(y=y.default),c=y,y)))};return fe({name:"AsyncComponentWrapper",__asyncLoader:p,get __asyncResolved(){return c},setup(){const v=ke;if(c)return()=>Wo(c,v);const y=b=>{a=null,qn(b,v,13,!o)};if(i&&v.suspense||pn)return p().then(b=>()=>Wo(b,v)).catch(b=>(y(b),()=>o?ne(o,{error:b}):null));const w=be(!1),x=be(),g=be(!!r);return r&&setTimeout(()=>{g.value=!1},r),s!=null&&setTimeout(()=>{if(!w.value&&!x.value){const b=new Error(`Async component timed out after ${s}ms.`);y(b),x.value=b}},s),p().then(()=>{w.value=!0,v.parent&&Wn(v.parent.vnode)&&$o(v.parent.update)}).catch(b=>{y(b),x.value=b}),()=>{if(w.value&&c)return Wo(c,v);if(x.value&&o)return ne(o,{error:x.value});if(n&&!g.value)return ne(n)}}})}function Wo(e,t){const{ref:n,props:o,children:r,ce:s}=t.vnode,i=ne(e,o,r);return i.ref=n,i.ce=s,delete t.vnode.ce,i}const Wn=e=>e.type.__isKeepAlive;function Dc(e,t){sl(e,"a",t)}function $c(e,t){sl(e,"da",t)}function sl(e,t,n=ke){const o=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(Ho(t,o,n),n){let r=n.parent;for(;r&&r.parent;)Wn(r.parent.vnode)&&Mc(o,t,n,r),r=r.parent}}function Mc(e,t,n,o){const r=Ho(t,e,o,!0);Bo(()=>{wr(o[t],r)},n)}function Ho(e,t,n=ke,o=!1){if(n){const r=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...i)=>{if(n.isUnmounted)return;vn(),dn(n);const l=Xe(t,n,e,i);return Jt(),gn(),l});return o?r.unshift(s):r.push(s),s}}const wt=e=>(t,n=ke)=>(!pn||e==="sp")&&Ho(e,(...o)=>t(...o),n),Nc=wt("bm"),We=wt("m"),Hc=wt("bu"),il=wt("u"),Jn=wt("bum"),Bo=wt("um"),Bc=wt("sp"),jc=wt("rtg"),Vc=wt("rtc");function Fc(e,t=ke){Ho("ec",e,t)}const ll="components";function bt(e,t){return Uc(ll,e,!0,t)||e}const zc=Symbol.for("v-ndc");function Uc(e,t,n=!0,o=!1){const r=Ne||ke;if(r){const s=r.type;if(e===ll){const l=yu(s,!1);if(l&&(l===t||l===ft(t)||l===Io(ft(t))))return s}const i=vs(r[e]||s[e],t)||vs(r.appContext[e],t);return!i&&o?s:i}}function vs(e,t){return e&&(e[t]||e[ft(t)]||e[Io(ft(t))])}function yt(e,t,n,o){let r;const s=n&&n[o];if(q(e)||me(e)){r=new Array(e.length);for(let i=0,l=e.length;it(i,l,void 0,s&&s[l]));else{const i=Object.keys(e);r=new Array(i.length);for(let l=0,a=i.length;lCo(t)?!(t.type===Ye||t.type===Ee&&!al(t.children)):!0)?e:null}const ur=e=>e?El(e)?Vo(e)||e.proxy:ur(e.parent):null,Pn=Se(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>ur(e.parent),$root:e=>ur(e.root),$emit:e=>e.emit,$options:e=>Nr(e),$forceUpdate:e=>e.f||(e.f=()=>$o(e.update)),$nextTick:e=>e.n||(e.n=Do.bind(e.proxy)),$watch:e=>kc.bind(e)}),Jo=(e,t)=>e!==Te&&!e.__isScriptSetup&&pe(e,t),Kc={get({_:e},t){const{ctx:n,setupState:o,data:r,props:s,accessCache:i,type:l,appContext:a}=e;let c;if(t[0]!=="$"){const v=i[t];if(v!==void 0)switch(v){case 1:return o[t];case 2:return r[t];case 4:return n[t];case 3:return s[t]}else{if(Jo(o,t))return i[t]=1,o[t];if(r!==Te&&pe(r,t))return i[t]=2,r[t];if((c=e.propsOptions[0])&&pe(c,t))return i[t]=3,s[t];if(n!==Te&&pe(n,t))return i[t]=4,n[t];fr&&(i[t]=0)}}const u=Pn[t];let f,p;if(u)return t==="$attrs"&&qe(e,"get",t),u(e);if((f=l.__cssModules)&&(f=f[t]))return f;if(n!==Te&&pe(n,t))return i[t]=4,n[t];if(p=a.config.globalProperties,pe(p,t))return p[t]},set({_:e},t,n){const{data:o,setupState:r,ctx:s}=e;return Jo(r,t)?(r[t]=n,!0):o!==Te&&pe(o,t)?(o[t]=n,!0):pe(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:r,propsOptions:s}},i){let l;return!!n[i]||e!==Te&&pe(e,i)||Jo(t,i)||(l=s[0])&&pe(l,i)||pe(o,i)||pe(Pn,i)||pe(r.config.globalProperties,i)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:pe(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function gs(e){return q(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let fr=!0;function qc(e){const t=Nr(e),n=e.proxy,o=e.ctx;fr=!1,t.beforeCreate&&_s(t.beforeCreate,e,"bc");const{data:r,computed:s,methods:i,watch:l,provide:a,inject:c,created:u,beforeMount:f,mounted:p,beforeUpdate:v,updated:y,activated:w,deactivated:x,beforeDestroy:g,beforeUnmount:b,destroyed:I,unmounted:k,render:W,renderTracked:ee,renderTriggered:N,errorCaptured:m,serverPrefetch:F,expose:B,inheritAttrs:Y,components:L,directives:R,filters:D}=t;if(c&&Wc(c,o,null),i)for(const se in i){const ie=i[se];re(ie)&&(o[se]=ie.bind(n))}if(r){const se=r.call(n,n);ye(se)&&(e.data=Kn(se))}if(fr=!0,s)for(const se in s){const ie=s[se],He=re(ie)?ie.bind(n,n):re(ie.get)?ie.get.bind(n,n):st,$e=!re(ie)&&re(ie.set)?ie.set.bind(n):st,Ue=z({get:He,set:$e});Object.defineProperty(o,se,{enumerable:!0,configurable:!0,get:()=>Ue.value,set:Be=>Ue.value=Be})}if(l)for(const se in l)cl(l[se],o,n,se);if(a){const se=re(a)?a.call(n):a;Reflect.ownKeys(se).forEach(ie=>{Wt(ie,se[ie])})}u&&_s(u,e,"c");function U(se,ie){q(ie)?ie.forEach(He=>se(He.bind(n))):ie&&se(ie.bind(n))}if(U(Nc,f),U(We,p),U(Hc,v),U(il,y),U(Dc,w),U($c,x),U(Fc,m),U(Vc,ee),U(jc,N),U(Jn,b),U(Bo,k),U(Bc,F),q(B))if(B.length){const se=e.exposed||(e.exposed={});B.forEach(ie=>{Object.defineProperty(se,ie,{get:()=>n[ie],set:He=>n[ie]=He})})}else e.exposed||(e.exposed={});W&&e.render===st&&(e.render=W),Y!=null&&(e.inheritAttrs=Y),L&&(e.components=L),R&&(e.directives=R)}function Wc(e,t,n=st){q(e)&&(e=dr(e));for(const o in e){const r=e[o];let s;ye(r)?"default"in r?s=Oe(r.from||o,r.default,!0):s=Oe(r.from||o):s=Oe(r),Ae(s)?Object.defineProperty(t,o,{enumerable:!0,configurable:!0,get:()=>s.value,set:i=>s.value=i}):t[o]=s}}function _s(e,t,n){Xe(q(e)?e.map(o=>o.bind(t.proxy)):e.bind(t.proxy),t,n)}function cl(e,t,n,o){const r=o.includes(".")?tl(n,o):()=>n[o];if(me(e)){const s=t[e];re(s)&&et(r,s)}else if(re(e))et(r,e.bind(n));else if(ye(e))if(q(e))e.forEach(s=>cl(s,t,n,o));else{const s=re(e.handler)?e.handler.bind(n):t[e.handler];re(s)&&et(r,s,e)}}function Nr(e){const t=e.type,{mixins:n,extends:o}=t,{mixins:r,optionsCache:s,config:{optionMergeStrategies:i}}=e.appContext,l=s.get(t);let a;return l?a=l:!r.length&&!n&&!o?a=t:(a={},r.length&&r.forEach(c=>yo(a,c,i,!0)),yo(a,t,i)),ye(t)&&s.set(t,a),a}function yo(e,t,n,o=!1){const{mixins:r,extends:s}=t;s&&yo(e,s,n,!0),r&&r.forEach(i=>yo(e,i,n,!0));for(const i in t)if(!(o&&i==="expose")){const l=Jc[i]||n&&n[i];e[i]=l?l(e[i],t[i]):t[i]}return e}const Jc={data:bs,props:ys,emits:ys,methods:Ln,computed:Ln,beforeCreate:Ve,created:Ve,beforeMount:Ve,mounted:Ve,beforeUpdate:Ve,updated:Ve,beforeDestroy:Ve,beforeUnmount:Ve,destroyed:Ve,unmounted:Ve,activated:Ve,deactivated:Ve,errorCaptured:Ve,serverPrefetch:Ve,components:Ln,directives:Ln,watch:Yc,provide:bs,inject:Qc};function bs(e,t){return t?e?function(){return Se(re(e)?e.call(this,this):e,re(t)?t.call(this,this):t)}:t:e}function Qc(e,t){return Ln(dr(e),dr(t))}function dr(e){if(q(e)){const t={};for(let n=0;n1)return n&&re(t)?t.call(o&&o.proxy):t}}function Xc(e,t,n,o=!1){const r={},s={};mo(s,jo,1),e.propsDefaults=Object.create(null),fl(e,t,r,s);for(const i in e.propsOptions[0])i in r||(r[i]=void 0);n?e.props=o?r:zi(r):e.type.props?e.props=r:e.props=s,e.attrs=s}function eu(e,t,n,o){const{props:r,attrs:s,vnode:{patchFlag:i}}=e,l=he(r),[a]=e.propsOptions;let c=!1;if((o||i>0)&&!(i&16)){if(i&8){const u=e.vnode.dynamicProps;for(let f=0;f{a=!0;const[p,v]=dl(f,t,!0);Se(i,p),v&&l.push(...v)};!n&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}if(!s&&!a)return ye(e)&&o.set(e,rn),rn;if(q(s))for(let u=0;u-1,v[1]=w<0||y-1||pe(v,"default"))&&l.push(f)}}}const c=[i,l];return ye(e)&&o.set(e,c),c}function Es(e){return e[0]!=="$"}function ws(e){const t=e&&e.toString().match(/^\s*(function|class) (\w+)/);return t?t[2]:e===null?"null":""}function Cs(e,t){return ws(e)===ws(t)}function Ts(e,t){return q(t)?t.findIndex(n=>Cs(n,e)):re(t)&&Cs(t,e)?0:-1}const pl=e=>e[0]==="_"||e==="$stable",Hr=e=>q(e)?e.map(nt):[nt(e)],tu=(e,t,n)=>{if(t._n)return t;const o=Me((...r)=>Hr(t(...r)),n);return o._c=!1,o},hl=(e,t,n)=>{const o=e._ctx;for(const r in e){if(pl(r))continue;const s=e[r];if(re(s))t[r]=tu(r,s,o);else if(s!=null){const i=Hr(s);t[r]=()=>i}}},ml=(e,t)=>{const n=Hr(t);e.slots.default=()=>n},nu=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=he(t),mo(t,"_",n)):hl(t,e.slots={})}else e.slots={},t&&ml(e,t);mo(e.slots,jo,1)},ou=(e,t,n)=>{const{vnode:o,slots:r}=e;let s=!0,i=Te;if(o.shapeFlag&32){const l=t._;l?n&&l===1?s=!1:(Se(r,t),!n&&l===1&&delete r._):(s=!t.$stable,hl(t,r)),i=t}else t&&(ml(e,t),i={default:1});if(s)for(const l in r)!pl(l)&&!(l in i)&&delete r[l]};function wo(e,t,n,o,r=!1){if(q(e)){e.forEach((p,v)=>wo(p,t&&(q(t)?t[v]:t),n,o,r));return}if(cn(o)&&!r)return;const s=o.shapeFlag&4?Vo(o.component)||o.component.proxy:o.el,i=r?null:s,{i:l,r:a}=e,c=t&&t.r,u=l.refs===Te?l.refs={}:l.refs,f=l.setupState;if(c!=null&&c!==a&&(me(c)?(u[c]=null,pe(f,c)&&(f[c]=null)):Ae(c)&&(c.value=null)),re(a))Dt(a,l,12,[i,u]);else{const p=me(a),v=Ae(a);if(p||v){const y=()=>{if(e.f){const w=p?pe(f,a)?f[a]:u[a]:a.value;r?q(w)&&wr(w,s):q(w)?w.includes(s)||w.push(s):p?(u[a]=[s],pe(f,a)&&(f[a]=u[a])):(a.value=[s],e.k&&(u[e.k]=a.value))}else p?(u[a]=i,pe(f,a)&&(f[a]=i)):v&&(a.value=i,e.k&&(u[e.k]=i))};i?(y.id=-1,Ke(y,n)):y()}}}let Pt=!1;const ro=e=>/svg/.test(e.namespaceURI)&&e.tagName!=="foreignObject",so=e=>e.nodeType===8;function ru(e){const{mt:t,p:n,o:{patchProp:o,createText:r,nextSibling:s,parentNode:i,remove:l,insert:a,createComment:c}}=e,u=(g,b)=>{if(!b.hasChildNodes()){n(null,g,b),_o(),b._vnode=g;return}Pt=!1,f(b.firstChild,g,null,null,null),_o(),b._vnode=g,Pt&&console.error("Hydration completed but contains mismatches.")},f=(g,b,I,k,W,ee=!1)=>{const N=so(g)&&g.data==="[",m=()=>w(g,b,I,k,W,N),{type:F,ref:B,shapeFlag:Y,patchFlag:L}=b;let R=g.nodeType;b.el=g,L===-2&&(ee=!1,b.dynamicChildren=null);let D=null;switch(F){case fn:R!==3?b.children===""?(a(b.el=r(""),i(g),g),D=g):D=m():(g.data!==b.children&&(Pt=!0,g.data=b.children),D=s(g));break;case Ye:R!==8||N?D=m():D=s(g);break;case On:if(N&&(g=s(g),R=g.nodeType),R===1||R===3){D=g;const le=!b.children.length;for(let U=0;U{ee=ee||!!b.dynamicChildren;const{type:N,props:m,patchFlag:F,shapeFlag:B,dirs:Y}=b,L=N==="input"&&Y||N==="option";if(L||F!==-1){if(Y&&ct(b,null,I,"created"),m)if(L||!ee||F&48)for(const D in m)(L&&D.endsWith("value")||zn(D)&&!xn(D))&&o(g,D,null,m[D],!1,void 0,I);else m.onClick&&o(g,"onClick",null,m.onClick,!1,void 0,I);let R;if((R=m&&m.onVnodeBeforeMount)&&Ze(R,I,b),Y&&ct(b,null,I,"beforeMount"),((R=m&&m.onVnodeMounted)||Y)&&Xi(()=>{R&&Ze(R,I,b),Y&&ct(b,null,I,"mounted")},k),B&16&&!(m&&(m.innerHTML||m.textContent))){let D=v(g.firstChild,b,g,I,k,W,ee);for(;D;){Pt=!0;const le=D;D=D.nextSibling,l(le)}}else B&8&&g.textContent!==b.children&&(Pt=!0,g.textContent=b.children)}return g.nextSibling},v=(g,b,I,k,W,ee,N)=>{N=N||!!b.dynamicChildren;const m=b.children,F=m.length;for(let B=0;B{const{slotScopeIds:N}=b;N&&(W=W?W.concat(N):N);const m=i(g),F=v(s(g),b,m,I,k,W,ee);return F&&so(F)&&F.data==="]"?s(b.anchor=F):(Pt=!0,a(b.anchor=c("]"),m,F),F)},w=(g,b,I,k,W,ee)=>{if(Pt=!0,b.el=null,ee){const F=x(g);for(;;){const B=s(g);if(B&&B!==F)l(B);else break}}const N=s(g),m=i(g);return l(g),n(null,b,m,N,I,k,ro(m),W),N},x=g=>{let b=0;for(;g;)if(g=s(g),g&&so(g)&&(g.data==="["&&b++,g.data==="]")){if(b===0)return s(g);b--}return g};return[u,f]}const Ke=Xi;function su(e){return vl(e)}function iu(e){return vl(e,ru)}function vl(e,t){const n=or();n.__VUE__=!0;const{insert:o,remove:r,patchProp:s,createElement:i,createText:l,createComment:a,setText:c,setElementText:u,parentNode:f,nextSibling:p,setScopeId:v=st,insertStaticContent:y}=e,w=(d,h,_,E=null,T=null,P=null,j=!1,A=null,M=!!h.dynamicChildren)=>{if(d===h)return;d&&!zt(d,h)&&(E=C(d),Be(d,T,P,!0),d=null),h.patchFlag===-2&&(M=!1,h.dynamicChildren=null);const{type:S,ref:G,shapeFlag:K}=h;switch(S){case fn:x(d,h,_,E);break;case Ye:g(d,h,_,E);break;case On:d==null&&b(h,_,E,j);break;case Ee:L(d,h,_,E,T,P,j,A,M);break;default:K&1?W(d,h,_,E,T,P,j,A,M):K&6?R(d,h,_,E,T,P,j,A,M):(K&64||K&128)&&S.process(d,h,_,E,T,P,j,A,M,$)}G!=null&&T&&wo(G,d&&d.ref,P,h||d,!h)},x=(d,h,_,E)=>{if(d==null)o(h.el=l(h.children),_,E);else{const T=h.el=d.el;h.children!==d.children&&c(T,h.children)}},g=(d,h,_,E)=>{d==null?o(h.el=a(h.children||""),_,E):h.el=d.el},b=(d,h,_,E)=>{[d.el,d.anchor]=y(d.children,h,_,E,d.el,d.anchor)},I=({el:d,anchor:h},_,E)=>{let T;for(;d&&d!==h;)T=p(d),o(d,_,E),d=T;o(h,_,E)},k=({el:d,anchor:h})=>{let _;for(;d&&d!==h;)_=p(d),r(d),d=_;r(h)},W=(d,h,_,E,T,P,j,A,M)=>{j=j||h.type==="svg",d==null?ee(h,_,E,T,P,j,A,M):F(d,h,T,P,j,A,M)},ee=(d,h,_,E,T,P,j,A)=>{let M,S;const{type:G,props:K,shapeFlag:Z,transition:oe,dirs:ce}=d;if(M=d.el=i(d.type,P,K&&K.is,K),Z&8?u(M,d.children):Z&16&&m(d.children,M,null,E,T,P&&G!=="foreignObject",j,A),ce&&ct(d,null,E,"created"),N(M,d,d.scopeId,j,E),K){for(const _e in K)_e!=="value"&&!xn(_e)&&s(M,_e,null,K[_e],P,d.children,E,T,De);"value"in K&&s(M,"value",null,K.value),(S=K.onVnodeBeforeMount)&&Ze(S,E,d)}ce&&ct(d,null,E,"beforeMount");const we=(!T||T&&!T.pendingBranch)&&oe&&!oe.persisted;we&&oe.beforeEnter(M),o(M,h,_),((S=K&&K.onVnodeMounted)||we||ce)&&Ke(()=>{S&&Ze(S,E,d),we&&oe.enter(M),ce&&ct(d,null,E,"mounted")},T)},N=(d,h,_,E,T)=>{if(_&&v(d,_),E)for(let P=0;P{for(let S=M;S{const A=h.el=d.el;let{patchFlag:M,dynamicChildren:S,dirs:G}=h;M|=d.patchFlag&16;const K=d.props||Te,Z=h.props||Te;let oe;_&&Ht(_,!1),(oe=Z.onVnodeBeforeUpdate)&&Ze(oe,_,h,d),G&&ct(h,d,_,"beforeUpdate"),_&&Ht(_,!0);const ce=T&&h.type!=="foreignObject";if(S?B(d.dynamicChildren,S,A,_,E,ce,P):j||ie(d,h,A,null,_,E,ce,P,!1),M>0){if(M&16)Y(A,h,K,Z,_,E,T);else if(M&2&&K.class!==Z.class&&s(A,"class",null,Z.class,T),M&4&&s(A,"style",K.style,Z.style,T),M&8){const we=h.dynamicProps;for(let _e=0;_e{oe&&Ze(oe,_,h,d),G&&ct(h,d,_,"updated")},E)},B=(d,h,_,E,T,P,j)=>{for(let A=0;A{if(_!==E){if(_!==Te)for(const A in _)!xn(A)&&!(A in E)&&s(d,A,_[A],null,j,h.children,T,P,De);for(const A in E){if(xn(A))continue;const M=E[A],S=_[A];M!==S&&A!=="value"&&s(d,A,S,M,j,h.children,T,P,De)}"value"in E&&s(d,"value",_.value,E.value)}},L=(d,h,_,E,T,P,j,A,M)=>{const S=h.el=d?d.el:l(""),G=h.anchor=d?d.anchor:l("");let{patchFlag:K,dynamicChildren:Z,slotScopeIds:oe}=h;oe&&(A=A?A.concat(oe):oe),d==null?(o(S,_,E),o(G,_,E),m(h.children,_,G,T,P,j,A,M)):K>0&&K&64&&Z&&d.dynamicChildren?(B(d.dynamicChildren,Z,_,T,P,j,A),(h.key!=null||T&&h===T.subTree)&&gl(d,h,!0)):ie(d,h,_,G,T,P,j,A,M)},R=(d,h,_,E,T,P,j,A,M)=>{h.slotScopeIds=A,d==null?h.shapeFlag&512?T.ctx.activate(h,_,E,j,M):D(h,_,E,T,P,j,M):le(d,h,M)},D=(d,h,_,E,T,P,j)=>{const A=d.component=mu(d,E,T);if(Wn(d)&&(A.ctx.renderer=$),vu(A),A.asyncDep){if(T&&T.registerDep(A,U),!d.el){const M=A.subTree=ne(Ye);g(null,M,h,_)}return}U(A,d,h,_,T,P,j)},le=(d,h,_)=>{const E=h.component=d.component;if(Pc(d,h,_))if(E.asyncDep&&!E.asyncResolved){se(E,h,_);return}else E.next=h,bc(E.update),E.update();else h.el=d.el,E.vnode=h},U=(d,h,_,E,T,P,j)=>{const A=()=>{if(d.isMounted){let{next:G,bu:K,u:Z,parent:oe,vnode:ce}=d,we=G,_e;Ht(d,!1),G?(G.el=ce.el,se(d,G,j)):G=ce,K&&fo(K),(_e=G.props&&G.props.onVnodeBeforeUpdate)&&Ze(_e,oe,G,ce),Ht(d,!0);const Pe=Ko(d),tt=d.subTree;d.subTree=Pe,w(tt,Pe,f(tt.el),C(tt),d,T,P),G.el=Pe.el,we===null&&Oc(d,Pe.el),Z&&Ke(Z,T),(_e=G.props&&G.props.onVnodeUpdated)&&Ke(()=>Ze(_e,oe,G,ce),T)}else{let G;const{el:K,props:Z}=h,{bm:oe,m:ce,parent:we}=d,_e=cn(h);if(Ht(d,!1),oe&&fo(oe),!_e&&(G=Z&&Z.onVnodeBeforeMount)&&Ze(G,we,h),Ht(d,!0),K&&ue){const Pe=()=>{d.subTree=Ko(d),ue(K,d.subTree,d,T,null)};_e?h.type.__asyncLoader().then(()=>!d.isUnmounted&&Pe()):Pe()}else{const Pe=d.subTree=Ko(d);w(null,Pe,_,E,d,T,P),h.el=Pe.el}if(ce&&Ke(ce,T),!_e&&(G=Z&&Z.onVnodeMounted)){const Pe=h;Ke(()=>Ze(G,we,Pe),T)}(h.shapeFlag&256||we&&cn(we.vnode)&&we.vnode.shapeFlag&256)&&d.a&&Ke(d.a,T),d.isMounted=!0,h=_=E=null}},M=d.effect=new Lr(A,()=>$o(S),d.scope),S=d.update=()=>M.run();S.id=d.uid,Ht(d,!0),S()},se=(d,h,_)=>{h.component=d;const E=d.vnode.props;d.vnode=h,d.next=null,eu(d,h.props,E,_),ou(d,h.children,_),vn(),ps(),gn()},ie=(d,h,_,E,T,P,j,A,M=!1)=>{const S=d&&d.children,G=d?d.shapeFlag:0,K=h.children,{patchFlag:Z,shapeFlag:oe}=h;if(Z>0){if(Z&128){$e(S,K,_,E,T,P,j,A,M);return}else if(Z&256){He(S,K,_,E,T,P,j,A,M);return}}oe&8?(G&16&&De(S,T,P),K!==S&&u(_,K)):G&16?oe&16?$e(S,K,_,E,T,P,j,A,M):De(S,T,P,!0):(G&8&&u(_,""),oe&16&&m(K,_,E,T,P,j,A,M))},He=(d,h,_,E,T,P,j,A,M)=>{d=d||rn,h=h||rn;const S=d.length,G=h.length,K=Math.min(S,G);let Z;for(Z=0;ZG?De(d,T,P,!0,!1,K):m(h,_,E,T,P,j,A,M,K)},$e=(d,h,_,E,T,P,j,A,M)=>{let S=0;const G=h.length;let K=d.length-1,Z=G-1;for(;S<=K&&S<=Z;){const oe=d[S],ce=h[S]=M?kt(h[S]):nt(h[S]);if(zt(oe,ce))w(oe,ce,_,null,T,P,j,A,M);else break;S++}for(;S<=K&&S<=Z;){const oe=d[K],ce=h[Z]=M?kt(h[Z]):nt(h[Z]);if(zt(oe,ce))w(oe,ce,_,null,T,P,j,A,M);else break;K--,Z--}if(S>K){if(S<=Z){const oe=Z+1,ce=oeZ)for(;S<=K;)Be(d[S],T,P,!0),S++;else{const oe=S,ce=S,we=new Map;for(S=ce;S<=Z;S++){const Je=h[S]=M?kt(h[S]):nt(h[S]);Je.key!=null&&we.set(Je.key,S)}let _e,Pe=0;const tt=Z-ce+1;let Xt=!1,es=0;const bn=new Array(tt);for(S=0;S=tt){Be(Je,T,P,!0);continue}let at;if(Je.key!=null)at=we.get(Je.key);else for(_e=ce;_e<=Z;_e++)if(bn[_e-ce]===0&&zt(Je,h[_e])){at=_e;break}at===void 0?Be(Je,T,P,!0):(bn[at-ce]=S+1,at>=es?es=at:Xt=!0,w(Je,h[at],_,null,T,P,j,A,M),Pe++)}const ts=Xt?lu(bn):rn;for(_e=ts.length-1,S=tt-1;S>=0;S--){const Je=ce+S,at=h[Je],ns=Je+1{const{el:P,type:j,transition:A,children:M,shapeFlag:S}=d;if(S&6){Ue(d.component.subTree,h,_,E);return}if(S&128){d.suspense.move(h,_,E);return}if(S&64){j.move(d,h,_,$);return}if(j===Ee){o(P,h,_);for(let K=0;KA.enter(P),T);else{const{leave:K,delayLeave:Z,afterLeave:oe}=A,ce=()=>o(P,h,_),we=()=>{K(P,()=>{ce(),oe&&oe()})};Z?Z(P,ce,we):we()}else o(P,h,_)},Be=(d,h,_,E=!1,T=!1)=>{const{type:P,props:j,ref:A,children:M,dynamicChildren:S,shapeFlag:G,patchFlag:K,dirs:Z}=d;if(A!=null&&wo(A,null,_,d,!0),G&256){h.ctx.deactivate(d);return}const oe=G&1&&Z,ce=!cn(d);let we;if(ce&&(we=j&&j.onVnodeBeforeUnmount)&&Ze(we,h,d),G&6)lt(d.component,_,E);else{if(G&128){d.suspense.unmount(_,E);return}oe&&ct(d,null,h,"beforeUnmount"),G&64?d.type.remove(d,h,_,T,$,E):S&&(P!==Ee||K>0&&K&64)?De(S,h,_,!1,!0):(P===Ee&&K&384||!T&&G&16)&&De(M,h,_),E&&Tt(d)}(ce&&(we=j&&j.onVnodeUnmounted)||oe)&&Ke(()=>{we&&Ze(we,h,d),oe&&ct(d,null,h,"unmounted")},_)},Tt=d=>{const{type:h,el:_,anchor:E,transition:T}=d;if(h===Ee){Lt(_,E);return}if(h===On){k(d);return}const P=()=>{r(_),T&&!T.persisted&&T.afterLeave&&T.afterLeave()};if(d.shapeFlag&1&&T&&!T.persisted){const{leave:j,delayLeave:A}=T,M=()=>j(_,P);A?A(d.el,P,M):M()}else P()},Lt=(d,h)=>{let _;for(;d!==h;)_=p(d),r(d),d=_;r(h)},lt=(d,h,_)=>{const{bum:E,scope:T,update:P,subTree:j,um:A}=d;E&&fo(E),T.stop(),P&&(P.active=!1,Be(j,d,h,_)),A&&Ke(A,h),Ke(()=>{d.isUnmounted=!0},h),h&&h.pendingBranch&&!h.isUnmounted&&d.asyncDep&&!d.asyncResolved&&d.suspenseId===h.pendingId&&(h.deps--,h.deps===0&&h.resolve())},De=(d,h,_,E=!1,T=!1,P=0)=>{for(let j=P;jd.shapeFlag&6?C(d.component.subTree):d.shapeFlag&128?d.suspense.next():p(d.anchor||d.el),V=(d,h,_)=>{d==null?h._vnode&&Be(h._vnode,null,null,!0):w(h._vnode||null,d,h,null,null,null,_),ps(),_o(),h._vnode=d},$={p:w,um:Be,m:Ue,r:Tt,mt:D,mc:m,pc:ie,pbc:B,n:C,o:e};let J,ue;return t&&([J,ue]=t($)),{render:V,hydrate:J,createApp:Zc(V,J)}}function Ht({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function gl(e,t,n=!1){const o=e.children,r=t.children;if(q(o)&&q(r))for(let s=0;s>1,e[n[l]]0&&(t[o]=n[s-1]),n[s]=o)}}for(s=n.length,i=n[s-1];s-- >0;)n[s]=i,i=t[i];return n}const au=e=>e.__isTeleport,Ee=Symbol.for("v-fgt"),fn=Symbol.for("v-txt"),Ye=Symbol.for("v-cmt"),On=Symbol.for("v-stc"),Sn=[];let rt=null;function H(e=!1){Sn.push(rt=e?null:[])}function cu(){Sn.pop(),rt=Sn[Sn.length-1]||null}let Bn=1;function Ls(e){Bn+=e}function _l(e){return e.dynamicChildren=Bn>0?rt||rn:null,cu(),Bn>0&&rt&&rt.push(e),e}function Q(e,t,n,o,r,s){return _l(ae(e,t,n,o,r,s,!0))}function Re(e,t,n,o,r){return _l(ne(e,t,n,o,r,!0))}function Co(e){return e?e.__v_isVNode===!0:!1}function zt(e,t){return e.type===t.type&&e.key===t.key}const jo="__vInternal",bl=({key:e})=>e??null,po=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?me(e)||Ae(e)||re(e)?{i:Ne,r:e,k:t,f:!!n}:e:null);function ae(e,t=null,n=null,o=0,r=null,s=e===Ee?0:1,i=!1,l=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&bl(t),ref:t&&po(t),scopeId:No,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:o,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:Ne};return l?(Br(a,n),s&128&&e.normalize(a)):n&&(a.shapeFlag|=me(n)?8:16),Bn>0&&!i&&rt&&(a.patchFlag>0||s&6)&&a.patchFlag!==32&&rt.push(a),a}const ne=uu;function uu(e,t=null,n=null,o=0,r=null,s=!1){if((!e||e===zc)&&(e=Ye),Co(e)){const l=Nt(e,t,!0);return n&&Br(l,n),Bn>0&&!s&&rt&&(l.shapeFlag&6?rt[rt.indexOf(e)]=l:rt.push(l)),l.patchFlag|=-2,l}if(Eu(e)&&(e=e.__vccOpts),t){t=fu(t);let{class:l,style:a}=t;l&&!me(l)&&(t.class=ze(l)),ye(a)&&(Ui(a)&&!q(a)&&(a=Se({},a)),t.style=Qt(a))}const i=me(e)?1:Sc(e)?128:au(e)?64:ye(e)?4:re(e)?2:0;return ae(e,t,n,o,r,i,s,!0)}function fu(e){return e?Ui(e)||jo in e?Se({},e):e:null}function Nt(e,t,n=!1){const{props:o,ref:r,patchFlag:s,children:i}=e,l=t?hr(o||{},t):o;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&bl(l),ref:t&&t.ref?n&&r?q(r)?r.concat(po(t)):[r,po(t)]:po(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:i,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Ee?s===-1?16:s|16:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Nt(e.ssContent),ssFallback:e.ssFallback&&Nt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Et(e=" ",t=0){return ne(fn,null,e,t)}function du(e,t){const n=ne(On,null,e);return n.staticCount=t,n}function xe(e="",t=!1){return t?(H(),Re(Ye,null,e)):ne(Ye,null,e)}function nt(e){return e==null||typeof e=="boolean"?ne(Ye):q(e)?ne(Ee,null,e.slice()):typeof e=="object"?kt(e):ne(fn,null,String(e))}function kt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Nt(e)}function Br(e,t){let n=0;const{shapeFlag:o}=e;if(t==null)t=null;else if(q(t))n=16;else if(typeof t=="object")if(o&65){const r=t.default;r&&(r._c&&(r._d=!1),Br(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!(jo in t)?t._ctx=Ne:r===3&&Ne&&(Ne.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else re(t)?(t={default:t,_ctx:Ne},n=32):(t=String(t),o&64?(n=16,t=[Et(t)]):n=8);e.children=t,e.shapeFlag|=n}function hr(...e){const t={};for(let n=0;nke||Ne;let jr,en,xs="__VUE_INSTANCE_SETTERS__";(en=or()[xs])||(en=or()[xs]=[]),en.push(e=>ke=e),jr=e=>{en.length>1?en.forEach(t=>t(e)):en[0](e)};const dn=e=>{jr(e),e.scope.on()},Jt=()=>{ke&&ke.scope.off(),jr(null)};function El(e){return e.vnode.shapeFlag&4}let pn=!1;function vu(e,t=!1){pn=t;const{props:n,children:o}=e.vnode,r=El(e);Xc(e,n,r,t),nu(e,o);const s=r?gu(e,t):void 0;return pn=!1,s}function gu(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Ki(new Proxy(e.ctx,Kc));const{setup:o}=n;if(o){const r=e.setupContext=o.length>1?bu(e):null;dn(e),vn();const s=Dt(o,e,0,[e.props,r]);if(gn(),Jt(),xi(s)){if(s.then(Jt,Jt),t)return s.then(i=>{Ps(e,i,t)}).catch(i=>{qn(i,e,0)});e.asyncDep=s}else Ps(e,s,t)}else wl(e,t)}function Ps(e,t,n){re(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ye(t)&&(e.setupState=Wi(t)),wl(e,n)}let Os;function wl(e,t,n){const o=e.type;if(!e.render){if(!t&&Os&&!o.render){const r=o.template||Nr(e).template;if(r){const{isCustomElement:s,compilerOptions:i}=e.appContext.config,{delimiters:l,compilerOptions:a}=o,c=Se(Se({isCustomElement:s,delimiters:l},i),a);o.render=Os(r,c)}}e.render=o.render||st}dn(e),vn(),qc(e),gn(),Jt()}function _u(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(t,n){return qe(e,"get","$attrs"),t[n]}}))}function bu(e){const t=n=>{e.exposed=n||{}};return{get attrs(){return _u(e)},slots:e.slots,emit:e.emit,expose:t}}function Vo(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Wi(Ki(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Pn)return Pn[n](e)},has(t,n){return n in t||n in Pn}}))}function yu(e,t=!0){return re(e)?e.displayName||e.name:e.name||t&&e.__name}function Eu(e){return re(e)&&"__vccOpts"in e}const z=(e,t)=>vc(e,t,pn);function ge(e,t,n){const o=arguments.length;return o===2?ye(t)&&!q(t)?Co(t)?ne(e,null,[t]):ne(e,t):ne(e,null,t):(o>3?n=Array.prototype.slice.call(arguments,2):o===3&&Co(n)&&(n=[n]),ne(e,t,n))}const wu=Symbol.for("v-scx"),Cu=()=>Oe(wu),Tu="3.3.4",Lu="http://www.w3.org/2000/svg",Ut=typeof document<"u"?document:null,Ss=Ut&&Ut.createElement("template"),xu={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,o)=>{const r=t?Ut.createElementNS(Lu,e):Ut.createElement(e,n?{is:n}:void 0);return e==="select"&&o&&o.multiple!=null&&r.setAttribute("multiple",o.multiple),r},createText:e=>Ut.createTextNode(e),createComment:e=>Ut.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ut.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,o,r,s){const i=n?n.previousSibling:t.lastChild;if(r&&(r===s||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===s||!(r=r.nextSibling)););else{Ss.innerHTML=o?`${e}`:e;const l=Ss.content;if(o){const a=l.firstChild;for(;a.firstChild;)l.appendChild(a.firstChild);l.removeChild(a)}t.insertBefore(l,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};function Pu(e,t,n){const o=e._vtc;o&&(t=(t?[t,...o]:[...o]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}function Ou(e,t,n){const o=e.style,r=me(n);if(n&&!r){if(t&&!me(t))for(const s in t)n[s]==null&&mr(o,s,"");for(const s in n)mr(o,s,n[s])}else{const s=o.display;r?t!==n&&(o.cssText=n):t&&e.removeAttribute("style"),"_vod"in e&&(o.display=s)}}const ks=/\s*!important$/;function mr(e,t,n){if(q(n))n.forEach(o=>mr(e,t,o));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const o=Su(e,t);ks.test(n)?e.setProperty(Yt(o),n.replace(ks,""),"important"):e[o]=n}}const Is=["Webkit","Moz","ms"],Qo={};function Su(e,t){const n=Qo[t];if(n)return n;let o=ft(t);if(o!=="filter"&&o in e)return Qo[t]=o;o=Io(o);for(let r=0;rYo||($u.then(()=>Yo=0),Yo=Date.now());function Nu(e,t){const n=o=>{if(!o._vts)o._vts=Date.now();else if(o._vts<=n.attached)return;Xe(Hu(o,n.value),t,5,[o])};return n.value=e,n.attached=Mu(),n}function Hu(e,t){if(q(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(o=>r=>!r._stopped&&o&&o(r))}else return t}const Ds=/^on[a-z]/,Bu=(e,t,n,o,r=!1,s,i,l,a)=>{t==="class"?Pu(e,o,r):t==="style"?Ou(e,n,o):zn(t)?Er(t)||Ru(e,t,n,o,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ju(e,t,o,r))?Iu(e,t,o,s,i,l,a):(t==="true-value"?e._trueValue=o:t==="false-value"&&(e._falseValue=o),ku(e,t,o,r))};function ju(e,t,n,o){return o?!!(t==="innerHTML"||t==="textContent"||t in e&&Ds.test(t)&&re(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||Ds.test(t)&&me(n)?!1:t in e}const Ot="transition",yn="animation",Qn=(e,{slots:t})=>ge(Rc,Vu(e),t);Qn.displayName="Transition";const Tl={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};Qn.props=Se({},nl,Tl);const Bt=(e,t=[])=>{q(e)?e.forEach(n=>n(...t)):e&&e(...t)},$s=e=>e?q(e)?e.some(t=>t.length>1):e.length>1:!1;function Vu(e){const t={};for(const L in e)L in Tl||(t[L]=e[L]);if(e.css===!1)return t;const{name:n="v",type:o,duration:r,enterFromClass:s=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:a=s,appearActiveClass:c=i,appearToClass:u=l,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:v=`${n}-leave-to`}=e,y=Fu(r),w=y&&y[0],x=y&&y[1],{onBeforeEnter:g,onEnter:b,onEnterCancelled:I,onLeave:k,onLeaveCancelled:W,onBeforeAppear:ee=g,onAppear:N=b,onAppearCancelled:m=I}=t,F=(L,R,D)=>{jt(L,R?u:l),jt(L,R?c:i),D&&D()},B=(L,R)=>{L._isLeaving=!1,jt(L,f),jt(L,v),jt(L,p),R&&R()},Y=L=>(R,D)=>{const le=L?N:b,U=()=>F(R,L,D);Bt(le,[R,U]),Ms(()=>{jt(R,L?a:s),St(R,L?u:l),$s(le)||Ns(R,o,w,U)})};return Se(t,{onBeforeEnter(L){Bt(g,[L]),St(L,s),St(L,i)},onBeforeAppear(L){Bt(ee,[L]),St(L,a),St(L,c)},onEnter:Y(!1),onAppear:Y(!0),onLeave(L,R){L._isLeaving=!0;const D=()=>B(L,R);St(L,f),Ku(),St(L,p),Ms(()=>{L._isLeaving&&(jt(L,f),St(L,v),$s(k)||Ns(L,o,x,D))}),Bt(k,[L,D])},onEnterCancelled(L){F(L,!1),Bt(I,[L])},onAppearCancelled(L){F(L,!0),Bt(m,[L])},onLeaveCancelled(L){B(L),Bt(W,[L])}})}function Fu(e){if(e==null)return null;if(ye(e))return[Go(e.enter),Go(e.leave)];{const t=Go(e);return[t,t]}}function Go(e){return Ca(e)}function St(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e._vtc||(e._vtc=new Set)).add(t)}function jt(e,t){t.split(/\s+/).forEach(o=>o&&e.classList.remove(o));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function Ms(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let zu=0;function Ns(e,t,n,o){const r=e._endId=++zu,s=()=>{r===e._endId&&o()};if(n)return setTimeout(s,n);const{type:i,timeout:l,propCount:a}=Uu(e,t);if(!i)return o();const c=i+"end";let u=0;const f=()=>{e.removeEventListener(c,p),s()},p=v=>{v.target===e&&++u>=a&&f()};setTimeout(()=>{u(n[y]||"").split(", "),r=o(`${Ot}Delay`),s=o(`${Ot}Duration`),i=Hs(r,s),l=o(`${yn}Delay`),a=o(`${yn}Duration`),c=Hs(l,a);let u=null,f=0,p=0;t===Ot?i>0&&(u=Ot,f=i,p=s.length):t===yn?c>0&&(u=yn,f=c,p=a.length):(f=Math.max(i,c),u=f>0?i>c?Ot:yn:null,p=u?u===Ot?s.length:a.length:0);const v=u===Ot&&/\b(transform|all)(,|$)/.test(o(`${Ot}Property`).toString());return{type:u,timeout:f,propCount:p,hasTransform:v}}function Hs(e,t){for(;e.lengthBs(n)+Bs(e[o])))}function Bs(e){return Number(e.slice(0,-1).replace(",","."))*1e3}function Ku(){return document.body.offsetHeight}const js=e=>{const t=e.props["onUpdate:modelValue"]||!1;return q(t)?n=>fo(t,n):t},qu={deep:!0,created(e,{value:t,modifiers:{number:n}},o){const r=So(t);Cl(e,"change",()=>{const s=Array.prototype.filter.call(e.options,i=>i.selected).map(i=>n?Si(To(i)):To(i));e._assign(e.multiple?r?new Set(s):s:s[0])}),e._assign=js(o)},mounted(e,{value:t}){Vs(e,t)},beforeUpdate(e,t,n){e._assign=js(n)},updated(e,{value:t}){Vs(e,t)}};function Vs(e,t){const n=e.multiple;if(!(n&&!q(t)&&!So(t))){for(let o=0,r=e.options.length;o-1:s.selected=t.has(i);else if(Ao(To(s),t)){e.selectedIndex!==o&&(e.selectedIndex=o);return}}!n&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function To(e){return"_value"in e?e._value:e.value}const Wu={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},Ju=(e,t)=>n=>{if(!("key"in n))return;const o=Yt(n.key);if(t.some(r=>r===o||Wu[r]===o))return e(n)},Lo={beforeMount(e,{value:t},{transition:n}){e._vod=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):En(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnter(e),En(e,!0),o.enter(e)):o.leave(e,()=>{En(e,!1)}):En(e,t))},beforeUnmount(e,{value:t}){En(e,t)}};function En(e,t){e.style.display=t?e._vod:"none"}const Ll=Se({patchProp:Bu},xu);let kn,Fs=!1;function Qu(){return kn||(kn=su(Ll))}function Yu(){return kn=Fs?kn:iu(Ll),Fs=!0,kn}const Gu=(...e)=>{const t=Qu().createApp(...e),{mount:n}=t;return t.mount=o=>{const r=xl(o);if(!r)return;const s=t._component;!re(s)&&!s.render&&!s.template&&(s.template=r.innerHTML),r.innerHTML="";const i=n(r,!1,r instanceof SVGElement);return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),i},t},Zu=(...e)=>{const t=Yu().createApp(...e),{mount:n}=t;return t.mount=o=>{const r=xl(o);if(r)return n(r,!0,r instanceof SVGElement)},t};function xl(e){return me(e)?document.querySelector(e):e}const Xu={"v-8daa1a0e":()=>O(()=>import("./index.html-692e45cb.js"),[]).then(({data:e})=>e),"v-14ac19b5":()=>O(()=>import("./index.html-7d2ae716.js"),[]).then(({data:e})=>e),"v-e5065f60":()=>O(()=>import("./index.html-7dc4890d.js"),[]).then(({data:e})=>e),"v-eb8745ce":()=>O(()=>import("./index.html-15fde261.js"),[]).then(({data:e})=>e),"v-828730c2":()=>O(()=>import("./index.html-93ff6c80.js"),[]).then(({data:e})=>e),"v-563ef996":()=>O(()=>import("./index.html-09f857ca.js"),[]).then(({data:e})=>e),"v-04b96fc8":()=>O(()=>import("./index.html-70c61dd5.js"),[]).then(({data:e})=>e),"v-fe45a0b8":()=>O(()=>import("./index.html-a7337040.js"),[]).then(({data:e})=>e),"v-7ed00a2a":()=>O(()=>import("./index.html-f8b7a659.js"),[]).then(({data:e})=>e),"v-326db923":()=>O(()=>import("./index.html-ced62edd.js"),[]).then(({data:e})=>e),"v-587df7db":()=>O(()=>import("./index.html-88fcd172.js"),[]).then(({data:e})=>e),"v-0b2aad78":()=>O(()=>import("./index.html-a2d4ab5c.js"),[]).then(({data:e})=>e),"v-23f62a3a":()=>O(()=>import("./index.html-6b858d80.js"),[]).then(({data:e})=>e),"v-1963670f":()=>O(()=>import("./index.html-549ff1f9.js"),[]).then(({data:e})=>e),"v-4cf2565c":()=>O(()=>import("./index.html-6e94f439.js"),[]).then(({data:e})=>e),"v-17bdcfe6":()=>O(()=>import("./index.html-c46be575.js"),[]).then(({data:e})=>e),"v-3481b484":()=>O(()=>import("./index.html-a70b6cf1.js"),[]).then(({data:e})=>e),"v-b6a1f058":()=>O(()=>import("./index.html-a9eb55a1.js"),[]).then(({data:e})=>e),"v-94b7dab4":()=>O(()=>import("./index.html-f9768eb1.js"),[]).then(({data:e})=>e),"v-391365f4":()=>O(()=>import("./index.html-6bb621c5.js"),[]).then(({data:e})=>e),"v-32b5e2dd":()=>O(()=>import("./index.html-bc520f9b.js"),[]).then(({data:e})=>e),"v-02a19d2b":()=>O(()=>import("./index.html-42466a4a.js"),[]).then(({data:e})=>e),"v-26e624c6":()=>O(()=>import("./index.html-3bc7b1c6.js"),[]).then(({data:e})=>e),"v-6165843c":()=>O(()=>import("./index.html-caa38aac.js"),[]).then(({data:e})=>e),"v-22e054a1":()=>O(()=>import("./index.html-34c15120.js"),[]).then(({data:e})=>e),"v-ca3407c4":()=>O(()=>import("./index.html-fae03536.js"),[]).then(({data:e})=>e),"v-3cf0fa66":()=>O(()=>import("./index.html-6ca7c7e1.js"),[]).then(({data:e})=>e),"v-147825fb":()=>O(()=>import("./index.html-2fb78bd6.js"),[]).then(({data:e})=>e),"v-255f131a":()=>O(()=>import("./index.html-963a4ecf.js"),[]).then(({data:e})=>e),"v-6768263b":()=>O(()=>import("./index.html-1d87c569.js"),[]).then(({data:e})=>e),"v-6a4de75f":()=>O(()=>import("./index.html-a0e9a4ad.js"),[]).then(({data:e})=>e),"v-a20dfce8":()=>O(()=>import("./index.html-76d114a5.js"),[]).then(({data:e})=>e),"v-9a955b1e":()=>O(()=>import("./index.html-e1863c04.js"),[]).then(({data:e})=>e),"v-b09aba04":()=>O(()=>import("./index.html-568e376b.js"),[]).then(({data:e})=>e),"v-b341ee2c":()=>O(()=>import("./index.html-ccd9b97c.js"),[]).then(({data:e})=>e),"v-0c9564ec":()=>O(()=>import("./index.html-a48fd168.js"),[]).then(({data:e})=>e),"v-36e2ae9d":()=>O(()=>import("./index.html-c67caa0a.js"),[]).then(({data:e})=>e),"v-5b6d532c":()=>O(()=>import("./index.html-9094b141.js"),[]).then(({data:e})=>e),"v-9712b6e4":()=>O(()=>import("./index.html-602c2540.js"),[]).then(({data:e})=>e),"v-bdba93e6":()=>O(()=>import("./filterQueryParam.html-d51cd871.js"),[]).then(({data:e})=>e),"v-31ddcbc0":()=>O(()=>import("./filterQueryParamCode.html-293978be.js"),[]).then(({data:e})=>e),"v-0e79de1b":()=>O(()=>import("./filterQueryParamExample.html-c90c301f.js"),[]).then(({data:e})=>e),"v-9a1a7988":()=>O(()=>import("./jmespathFilter.html-7c48b0c1.js"),[]).then(({data:e})=>e),"v-4395d380":()=>O(()=>import("./throttle.html-4a67c62e.js"),[]).then(({data:e})=>e),"v-17bf8008":()=>O(()=>import("./whereQueryParam.html-10f3d62d.js"),[]).then(({data:e})=>e),"v-0b4a148f":()=>O(()=>import("./whereQueryParamCode.html-33176c09.js"),[]).then(({data:e})=>e),"v-5119194c":()=>O(()=>import("./whereQueryParamExample.html-68248e1c.js"),[]).then(({data:e})=>e),"v-3706649a":()=>O(()=>import("./404.html-60b35caa.js"),[]).then(({data:e})=>e)},ef=JSON.parse('{"base":"/NotifyBC/","lang":"en-US","title":"NotifyBC","description":"A versatile notification API server","head":[["meta",{"name":"theme-color","content":"#3eaf7c"}],["meta",{"name":"apple-mobile-web-app-capable","content":"yes"}],["meta",{"name":"apple-mobile-web-app-status-bar-style","content":"black"}],["link",{"rel":"icon","type":"image/x-icon","href":"/NotifyBC/favicon.ico"}],["link",{"rel":"stylesheet","href":"https://fonts.googleapis.com/icon?family=Material+Icons"}]],"locales":{}}');var tf=([e,t,n])=>e==="meta"&&t.name?`${e}.${t.name}`:["title","base"].includes(e)?e:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,t,n]),nf=e=>{const t=new Set,n=[];return e.forEach(o=>{const r=tf(o);t.has(r)||(t.add(r),n.push(o))}),n},Yn=e=>/^(https?:)?\/\//.test(e),of=e=>/^mailto:/.test(e),rf=e=>/^tel:/.test(e),Vr=e=>Object.prototype.toString.call(e)==="[object Object]",Pl=e=>e[e.length-1]==="/"?e.slice(0,-1):e,Ol=e=>e[0]==="/"?e.slice(1):e,Sl=(e,t)=>{const n=Object.keys(e).sort((o,r)=>{const s=r.split("/").length-o.split("/").length;return s!==0?s:r.length-o.length});for(const o of n)if(t.startsWith(o))return o;return"/"},zs=(e,t="/")=>{const n=e.replace(/^(https?:)?\/\/[^/]*/,"");return n.startsWith(t)?`/${n.slice(t.length)}`:n};const kl={"v-8daa1a0e":te(()=>O(()=>import("./index.html-c12f943f.js"),[])),"v-14ac19b5":te(()=>O(()=>import("./index.html-1fa10348.js"),[])),"v-e5065f60":te(()=>O(()=>import("./index.html-a3a34a72.js"),[])),"v-eb8745ce":te(()=>O(()=>import("./index.html-6cc49290.js"),[])),"v-828730c2":te(()=>O(()=>import("./index.html-5fb6acba.js"),[])),"v-563ef996":te(()=>O(()=>import("./index.html-38194d5f.js"),[])),"v-04b96fc8":te(()=>O(()=>import("./index.html-ac8da399.js"),[])),"v-fe45a0b8":te(()=>O(()=>import("./index.html-5795a2ec.js"),[])),"v-7ed00a2a":te(()=>O(()=>import("./index.html-4e48a4d5.js"),[])),"v-326db923":te(()=>O(()=>import("./index.html-95e23621.js"),[])),"v-587df7db":te(()=>O(()=>import("./index.html-00458894.js"),[])),"v-0b2aad78":te(()=>O(()=>import("./index.html-70ee88ef.js"),[])),"v-23f62a3a":te(()=>O(()=>import("./index.html-287f081e.js"),[])),"v-1963670f":te(()=>O(()=>import("./index.html-05c25248.js"),[])),"v-4cf2565c":te(()=>O(()=>import("./index.html-46fb1d05.js"),[])),"v-17bdcfe6":te(()=>O(()=>import("./index.html-ea9c36e4.js"),[])),"v-3481b484":te(()=>O(()=>import("./index.html-cf1801d6.js"),[])),"v-b6a1f058":te(()=>O(()=>import("./index.html-90a99e66.js"),[])),"v-94b7dab4":te(()=>O(()=>import("./index.html-e98535e1.js"),[])),"v-391365f4":te(()=>O(()=>import("./index.html-54e79f35.js"),[])),"v-32b5e2dd":te(()=>O(()=>import("./index.html-03b54c01.js"),[])),"v-02a19d2b":te(()=>O(()=>import("./index.html-8b2ab8f8.js"),[])),"v-26e624c6":te(()=>O(()=>import("./index.html-9b491f72.js"),[])),"v-6165843c":te(()=>O(()=>import("./index.html-8e2d950c.js"),[])),"v-22e054a1":te(()=>O(()=>import("./index.html-091f58dc.js"),[])),"v-ca3407c4":te(()=>O(()=>import("./index.html-13cd24c1.js"),[])),"v-3cf0fa66":te(()=>O(()=>import("./index.html-ffb5aa93.js"),[])),"v-147825fb":te(()=>O(()=>import("./index.html-6b8cefcc.js"),[])),"v-255f131a":te(()=>O(()=>import("./index.html-4c7abb90.js"),[])),"v-6768263b":te(()=>O(()=>import("./index.html-ffbaf637.js"),[])),"v-6a4de75f":te(()=>O(()=>import("./index.html-baba90e5.js"),[])),"v-a20dfce8":te(()=>O(()=>import("./index.html-04c7721d.js"),[])),"v-9a955b1e":te(()=>O(()=>import("./index.html-81176e6b.js"),[])),"v-b09aba04":te(()=>O(()=>import("./index.html-819c8b73.js"),[])),"v-b341ee2c":te(()=>O(()=>import("./index.html-51b84ddd.js"),[])),"v-0c9564ec":te(()=>O(()=>import("./index.html-9d0a34f4.js"),[])),"v-36e2ae9d":te(()=>O(()=>import("./index.html-321d8b52.js"),[])),"v-5b6d532c":te(()=>O(()=>import("./index.html-cd7a5a49.js"),[])),"v-9712b6e4":te(()=>O(()=>import("./index.html-655164a6.js"),[])),"v-bdba93e6":te(()=>O(()=>import("./filterQueryParam.html-f6d30c08.js"),[])),"v-31ddcbc0":te(()=>O(()=>import("./filterQueryParamCode.html-63f24e00.js"),[])),"v-0e79de1b":te(()=>O(()=>import("./filterQueryParamExample.html-3c9d0dce.js"),[])),"v-9a1a7988":te(()=>O(()=>import("./jmespathFilter.html-a67ec5c7.js"),[])),"v-4395d380":te(()=>O(()=>import("./throttle.html-b33db38d.js"),[])),"v-17bf8008":te(()=>O(()=>import("./whereQueryParam.html-e8cad04b.js"),[])),"v-0b4a148f":te(()=>O(()=>import("./whereQueryParamCode.html-feffff85.js"),[])),"v-5119194c":te(()=>O(()=>import("./whereQueryParamExample.html-810bff15.js"),[])),"v-3706649a":te(()=>O(()=>import("./404.html-eadb7290.js"),[]))};var sf=Symbol(""),lf=be(Xu),Il=_n({key:"",path:"",title:"",lang:"",frontmatter:{},headers:[]}),It=be(Il),$t=()=>It,Al=Symbol(""),vt=()=>{const e=Oe(Al);if(!e)throw new Error("usePageFrontmatter() is called without provider.");return e},Rl=Symbol(""),af=()=>{const e=Oe(Rl);if(!e)throw new Error("usePageHead() is called without provider.");return e},cf=Symbol(""),Dl=Symbol(""),$l=()=>{const e=Oe(Dl);if(!e)throw new Error("usePageLang() is called without provider.");return e},Ml=Symbol(""),uf=()=>{const e=Oe(Ml);if(!e)throw new Error("usePageLayout() is called without provider.");return e},Fr=Symbol(""),Gn=()=>{const e=Oe(Fr);if(!e)throw new Error("useRouteLocale() is called without provider.");return e},on=be(ef),Nl=()=>on,Hl=Symbol(""),zr=()=>{const e=Oe(Hl);if(!e)throw new Error("useSiteLocaleData() is called without provider.");return e},ff=Symbol(""),df="Layout",pf="NotFound",pt=Kn({resolveLayouts:e=>e.reduce((t,n)=>({...t,...n.layouts}),{}),resolvePageData:async e=>{const t=lf.value[e];return await(t==null?void 0:t())??Il},resolvePageFrontmatter:e=>e.frontmatter,resolvePageHead:(e,t,n)=>{const o=me(t.description)?t.description:n.description,r=[...q(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:o}]];return nf(r)},resolvePageHeadTitle:(e,t)=>[e.title,t.title].filter(n=>!!n).join(" | "),resolvePageLang:(e,t)=>e.lang||t.lang||"en-US",resolvePageLayout:(e,t)=>{let n;if(e.path){const o=e.frontmatter.layout;me(o)?n=o:n=df}else n=pf;return t[n]},resolveRouteLocale:(e,t)=>Sl(e,t),resolveSiteLocaleData:(e,t)=>({...e,...e.locales[t]})}),Ur=fe({name:"ClientOnly",setup(e,t){const n=be(!1);return We(()=>{n.value=!0}),()=>{var o,r;return n.value?(r=(o=t.slots).default)==null?void 0:r.call(o):null}}}),hf=fe({name:"Content",props:{pageKey:{type:String,required:!1,default:""}},setup(e){const t=$t(),n=z(()=>kl[e.pageKey||t.value.key]);return()=>n.value?ge(n.value):ge("div","404 Not Found")}}),Ct=(e={})=>e,Kr=e=>Yn(e)?e:`/NotifyBC/${Ol(e)}`;function qr(e,t,n){var o,r,s;t===void 0&&(t=50),n===void 0&&(n={});var i=(o=n.isImmediate)!=null&&o,l=(r=n.callback)!=null&&r,a=n.maxWait,c=Date.now(),u=[];function f(){if(a!==void 0){var v=Date.now()-c;if(v+t>=a)return a-v}return t}var p=function(){var v=[].slice.call(arguments),y=this;return new Promise(function(w,x){var g=i&&s===void 0;if(s!==void 0&&clearTimeout(s),s=setTimeout(function(){if(s=void 0,c=Date.now(),!i){var I=e.apply(y,v);l&&l(I),u.forEach(function(k){return(0,k.resolve)(I)}),u=[]}},f()),g){var b=e.apply(y,v);return l&&l(b),w(b)}u.push({resolve:w,reject:x})})};return p.cancel=function(v){s!==void 0&&clearTimeout(s),u.forEach(function(y){return(0,y.reject)(v)}),u=[]},p}/*! +const ma="modulepreload",va=function(e){return"/NotifyBC/"+e},os={},O=function(t,n,o){if(!n||n.length===0)return t();const r=document.getElementsByTagName("link");return Promise.all(n.map(s=>{if(s=va(s),s in os)return;os[s]=!0;const i=s.endsWith(".css"),l=i?'[rel="stylesheet"]':"";if(!!o)for(let u=r.length-1;u>=0;u--){const f=r[u];if(f.href===s&&(!i||f.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${s}"]${l}`))return;const c=document.createElement("link");if(c.rel=i?"stylesheet":ma,i||(c.as="script",c.crossOrigin=""),c.href=s,document.head.appendChild(c),i)return new Promise((u,f)=>{c.addEventListener("load",u),c.addEventListener("error",()=>f(new Error(`Unable to preload CSS for ${s}`)))})})).then(()=>t()).catch(s=>{const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=s,window.dispatchEvent(i),!i.defaultPrevented)throw s})};function yr(e,t){const n=Object.create(null),o=e.split(",");for(let r=0;r!!n[r.toLowerCase()]:r=>!!n[r]}const Te={},rn=[],st=()=>{},ga=()=>!1,_a=/^on[^a-z]/,zn=e=>_a.test(e),Er=e=>e.startsWith("onUpdate:"),Se=Object.assign,wr=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},ba=Object.prototype.hasOwnProperty,pe=(e,t)=>ba.call(e,t),q=Array.isArray,sn=e=>Un(e)==="[object Map]",So=e=>Un(e)==="[object Set]",rs=e=>Un(e)==="[object Date]",re=e=>typeof e=="function",me=e=>typeof e=="string",Rn=e=>typeof e=="symbol",ye=e=>e!==null&&typeof e=="object",xi=e=>ye(e)&&re(e.then)&&re(e.catch),Pi=Object.prototype.toString,Un=e=>Pi.call(e),ya=e=>Un(e).slice(8,-1),Oi=e=>Un(e)==="[object Object]",Cr=e=>me(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,xn=yr(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),ko=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Ea=/-(\w)/g,ft=ko(e=>e.replace(Ea,(t,n)=>n?n.toUpperCase():"")),wa=/\B([A-Z])/g,Yt=ko(e=>e.replace(wa,"-$1").toLowerCase()),Io=ko(e=>e.charAt(0).toUpperCase()+e.slice(1)),Uo=ko(e=>e?`on${Io(e)}`:""),Dn=(e,t)=>!Object.is(e,t),fo=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Si=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Ca=e=>{const t=me(e)?Number(e):NaN;return isNaN(t)?e:t};let ss;const or=()=>ss||(ss=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Qt(e){if(q(e)){const t={};for(let n=0;n{if(n){const o=n.split(La);o.length>1&&(t[o[0].trim()]=o[1].trim())}}),t}function ze(e){let t="";if(me(e))t=e;else if(q(e))for(let n=0;nAo(n,t))}const Ie=e=>me(e)?e:e==null?"":q(e)||ye(e)&&(e.toString===Pi||!re(e.toString))?JSON.stringify(e,Ii,2):String(e),Ii=(e,t)=>t&&t.__v_isRef?Ii(e,t.value):sn(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[o,r])=>(n[`${o} =>`]=r,n),{})}:So(t)?{[`Set(${t.size})`]:[...t.values()]}:ye(t)&&!q(t)&&!Oi(t)?String(t):t;let Qe;class Aa{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=Qe,!t&&Qe&&(this.index=(Qe.scopes||(Qe.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=Qe;try{return Qe=this,t()}finally{Qe=n}}}on(){Qe=this}off(){Qe=this.parent}stop(t){if(this._active){let n,o;for(n=0,o=this.effects.length;n{const t=new Set(e);return t.w=0,t.n=0,t},Ri=e=>(e.w&Mt)>0,Di=e=>(e.n&Mt)>0,$a=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let o=0;o{(u==="length"||u>=a)&&l.push(c)})}else switch(n!==void 0&&l.push(i.get(n)),t){case"add":q(e)?Cr(n)&&l.push(i.get("length")):(l.push(i.get(qt)),sn(e)&&l.push(i.get(sr)));break;case"delete":q(e)||(l.push(i.get(qt)),sn(e)&&l.push(i.get(sr)));break;case"set":sn(e)&&l.push(i.get(qt));break}if(l.length===1)l[0]&&ir(l[0]);else{const a=[];for(const c of l)c&&a.push(...c);ir(Tr(a))}}function ir(e,t){const n=q(e)?e:[...e];for(const o of n)o.computed&&ls(o);for(const o of n)o.computed||ls(o)}function ls(e,t){(e!==ot||e.allowRecurse)&&(e.scheduler?e.scheduler():e.run())}function Na(e,t){var n;return(n=vo.get(e))==null?void 0:n.get(t)}const Ha=yr("__proto__,__v_isRef,__isVue"),Ni=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Rn)),Ba=xr(),ja=xr(!1,!0),Va=xr(!0),as=Fa();function Fa(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const o=he(this);for(let s=0,i=this.length;s{e[t]=function(...n){vn();const o=he(this)[t].apply(this,n);return gn(),o}}),e}function za(e){const t=he(this);return qe(t,"has",e),t.hasOwnProperty(e)}function xr(e=!1,t=!1){return function(o,r,s){if(r==="__v_isReactive")return!e;if(r==="__v_isReadonly")return e;if(r==="__v_isShallow")return t;if(r==="__v_raw"&&s===(e?t?sc:Fi:t?Vi:ji).get(o))return o;const i=q(o);if(!e){if(i&&pe(as,r))return Reflect.get(as,r,s);if(r==="hasOwnProperty")return za}const l=Reflect.get(o,r,s);return(Rn(r)?Ni.has(r):Ha(r))||(e||qe(o,"get",r),t)?l:Ae(l)?i&&Cr(r)?l:l.value:ye(l)?e?_n(l):Kn(l):l}}const Ua=Hi(),Ka=Hi(!0);function Hi(e=!1){return function(n,o,r,s){let i=n[o];if(un(i)&&Ae(i)&&!Ae(r))return!1;if(!e&&(!go(r)&&!un(r)&&(i=he(i),r=he(r)),!q(n)&&Ae(i)&&!Ae(r)))return i.value=r,!0;const l=q(n)&&Cr(o)?Number(o)e,Ro=e=>Reflect.getPrototypeOf(e);function Zn(e,t,n=!1,o=!1){e=e.__v_raw;const r=he(e),s=he(t);n||(t!==s&&qe(r,"get",t),qe(r,"get",s));const{has:i}=Ro(r),l=o?Pr:n?kr:$n;if(i.call(r,t))return l(e.get(t));if(i.call(r,s))return l(e.get(s));e!==r&&e.get(t)}function Xn(e,t=!1){const n=this.__v_raw,o=he(n),r=he(e);return t||(e!==r&&qe(o,"has",e),qe(o,"has",r)),e===r?n.has(e):n.has(e)||n.has(r)}function eo(e,t=!1){return e=e.__v_raw,!t&&qe(he(e),"iterate",qt),Reflect.get(e,"size",e)}function cs(e){e=he(e);const t=he(this);return Ro(t).has.call(t,e)||(t.add(e),_t(t,"add",e,e)),this}function us(e,t){t=he(t);const n=he(this),{has:o,get:r}=Ro(n);let s=o.call(n,e);s||(e=he(e),s=o.call(n,e));const i=r.call(n,e);return n.set(e,t),s?Dn(t,i)&&_t(n,"set",e,t):_t(n,"add",e,t),this}function fs(e){const t=he(this),{has:n,get:o}=Ro(t);let r=n.call(t,e);r||(e=he(e),r=n.call(t,e)),o&&o.call(t,e);const s=t.delete(e);return r&&_t(t,"delete",e,void 0),s}function ds(){const e=he(this),t=e.size!==0,n=e.clear();return t&&_t(e,"clear",void 0,void 0),n}function to(e,t){return function(o,r){const s=this,i=s.__v_raw,l=he(i),a=t?Pr:e?kr:$n;return!e&&qe(l,"iterate",qt),i.forEach((c,u)=>o.call(r,a(c),a(u),s))}}function no(e,t,n){return function(...o){const r=this.__v_raw,s=he(r),i=sn(s),l=e==="entries"||e===Symbol.iterator&&i,a=e==="keys"&&i,c=r[e](...o),u=n?Pr:t?kr:$n;return!t&&qe(s,"iterate",a?sr:qt),{next(){const{value:f,done:p}=c.next();return p?{value:f,done:p}:{value:l?[u(f[0]),u(f[1])]:u(f),done:p}},[Symbol.iterator](){return this}}}}function xt(e){return function(...t){return e==="delete"?!1:this}}function Ga(){const e={get(s){return Zn(this,s)},get size(){return eo(this)},has:Xn,add:cs,set:us,delete:fs,clear:ds,forEach:to(!1,!1)},t={get(s){return Zn(this,s,!1,!0)},get size(){return eo(this)},has:Xn,add:cs,set:us,delete:fs,clear:ds,forEach:to(!1,!0)},n={get(s){return Zn(this,s,!0)},get size(){return eo(this,!0)},has(s){return Xn.call(this,s,!0)},add:xt("add"),set:xt("set"),delete:xt("delete"),clear:xt("clear"),forEach:to(!0,!1)},o={get(s){return Zn(this,s,!0,!0)},get size(){return eo(this,!0)},has(s){return Xn.call(this,s,!0)},add:xt("add"),set:xt("set"),delete:xt("delete"),clear:xt("clear"),forEach:to(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(s=>{e[s]=no(s,!1,!1),n[s]=no(s,!0,!1),t[s]=no(s,!1,!0),o[s]=no(s,!0,!0)}),[e,n,t,o]}const[Za,Xa,ec,tc]=Ga();function Or(e,t){const n=t?e?tc:ec:e?Xa:Za;return(o,r,s)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?o:Reflect.get(pe(n,r)&&r in o?n:o,r,s)}const nc={get:Or(!1,!1)},oc={get:Or(!1,!0)},rc={get:Or(!0,!1)},ji=new WeakMap,Vi=new WeakMap,Fi=new WeakMap,sc=new WeakMap;function ic(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function lc(e){return e.__v_skip||!Object.isExtensible(e)?0:ic(ya(e))}function Kn(e){return un(e)?e:Sr(e,!1,Bi,nc,ji)}function zi(e){return Sr(e,!1,Ya,oc,Vi)}function _n(e){return Sr(e,!0,Qa,rc,Fi)}function Sr(e,t,n,o,r){if(!ye(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const s=r.get(e);if(s)return s;const i=lc(e);if(i===0)return e;const l=new Proxy(e,i===2?o:n);return r.set(e,l),l}function ln(e){return un(e)?ln(e.__v_raw):!!(e&&e.__v_isReactive)}function un(e){return!!(e&&e.__v_isReadonly)}function go(e){return!!(e&&e.__v_isShallow)}function Ui(e){return ln(e)||un(e)}function he(e){const t=e&&e.__v_raw;return t?he(t):e}function Ki(e){return mo(e,"__v_skip",!0),e}const $n=e=>ye(e)?Kn(e):e,kr=e=>ye(e)?_n(e):e;function Ir(e){Rt&&ot&&(e=he(e),Mi(e.dep||(e.dep=Tr())))}function Ar(e,t){e=he(e);const n=e.dep;n&&ir(n)}function Ae(e){return!!(e&&e.__v_isRef===!0)}function be(e){return qi(e,!1)}function Rr(e){return qi(e,!0)}function qi(e,t){return Ae(e)?e:new ac(e,t)}class ac{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:he(t),this._value=n?t:$n(t)}get value(){return Ir(this),this._value}set value(t){const n=this.__v_isShallow||go(t)||un(t);t=n?t:he(t),Dn(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:$n(t),Ar(this))}}function X(e){return Ae(e)?e.value:e}const cc={get:(e,t,n)=>X(Reflect.get(e,t,n)),set:(e,t,n,o)=>{const r=e[t];return Ae(r)&&!Ae(n)?(r.value=n,!0):Reflect.set(e,t,n,o)}};function Wi(e){return ln(e)?e:new Proxy(e,cc)}class uc{constructor(t){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:o}=t(()=>Ir(this),()=>Ar(this));this._get=n,this._set=o}get value(){return this._get()}set value(t){this._set(t)}}function fc(e){return new uc(e)}function Dr(e){const t=q(e)?new Array(e.length):{};for(const n in e)t[n]=Ji(e,n);return t}class dc{constructor(t,n,o){this._object=t,this._key=n,this._defaultValue=o,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return Na(he(this._object),this._key)}}class pc{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function hc(e,t,n){return Ae(e)?e:re(e)?new pc(e):ye(e)&&arguments.length>1?Ji(e,t,n):be(e)}function Ji(e,t,n){const o=e[t];return Ae(o)?o:new dc(e,t,n)}class mc{constructor(t,n,o,r){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this._dirty=!0,this.effect=new Lr(t,()=>{this._dirty||(this._dirty=!0,Ar(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!r,this.__v_isReadonly=o}get value(){const t=he(this);return Ir(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}function vc(e,t,n=!1){let o,r;const s=re(e);return s?(o=e,r=st):(o=e.get,r=e.set),new mc(o,r,s||!r,n)}function Dt(e,t,n,o){let r;try{r=o?e(...o):e()}catch(s){qn(s,t,n)}return r}function Xe(e,t,n,o){if(re(e)){const s=Dt(e,t,n,o);return s&&xi(s)&&s.catch(i=>{qn(i,t,n)}),s}const r=[];for(let s=0;s>>1;Nn(je[o])ut&&je.splice(t,1)}function yc(e){q(e)?an.push(...e):(!mt||!mt.includes(e,e.allowRecurse?Ft+1:Ft))&&an.push(e),Yi()}function ps(e,t=Mn?ut+1:0){for(;tNn(n)-Nn(o)),Ft=0;Fte.id==null?1/0:e.id,Ec=(e,t)=>{const n=Nn(e)-Nn(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function Gi(e){lr=!1,Mn=!0,je.sort(Ec);const t=st;try{for(ut=0;utme(v)?v.trim():v)),f&&(r=n.map(Si))}let l,a=o[l=Uo(t)]||o[l=Uo(ft(t))];!a&&s&&(a=o[l=Uo(Yt(t))]),a&&Xe(a,e,6,r);const c=o[l+"Once"];if(c){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Xe(c,e,6,r)}}function Zi(e,t,n=!1){const o=t.emitsCache,r=o.get(e);if(r!==void 0)return r;const s=e.emits;let i={},l=!1;if(!re(e)){const a=c=>{const u=Zi(c,t,!0);u&&(l=!0,Se(i,u))};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!s&&!l?(ye(e)&&o.set(e,null),null):(q(s)?s.forEach(a=>i[a]=null):Se(i,s),ye(e)&&o.set(e,i),i)}function Mo(e,t){return!e||!zn(t)?!1:(t=t.slice(2).replace(/Once$/,""),pe(e,t[0].toLowerCase()+t.slice(1))||pe(e,Yt(t))||pe(e,t))}let Ne=null,No=null;function bo(e){const t=Ne;return Ne=e,No=e&&e.type.__scopeId||null,t}function Cc(e){No=e}function Tc(){No=null}function Me(e,t=Ne,n){if(!t||e._n)return e;const o=(...r)=>{o._d&&Ls(-1);const s=bo(t);let i;try{i=e(...r)}finally{bo(s),o._d&&Ls(1)}return i};return o._n=!0,o._c=!0,o._d=!0,o}function Ko(e){const{type:t,vnode:n,proxy:o,withProxy:r,props:s,propsOptions:[i],slots:l,attrs:a,emit:c,render:u,renderCache:f,data:p,setupState:v,ctx:y,inheritAttrs:w}=e;let x,g;const b=bo(e);try{if(n.shapeFlag&4){const k=r||o;x=nt(u.call(k,k,f,s,v,p,y)),g=a}else{const k=t;x=nt(k.length>1?k(s,{attrs:a,slots:l,emit:c}):k(s,null)),g=t.props?a:Lc(a)}}catch(k){Sn.length=0,qn(k,e,1),x=ne(Ye)}let I=x;if(g&&w!==!1){const k=Object.keys(g),{shapeFlag:W}=I;k.length&&W&7&&(i&&k.some(Er)&&(g=xc(g,i)),I=Nt(I,g))}return n.dirs&&(I=Nt(I),I.dirs=I.dirs?I.dirs.concat(n.dirs):n.dirs),n.transition&&(I.transition=n.transition),x=I,bo(b),x}const Lc=e=>{let t;for(const n in e)(n==="class"||n==="style"||zn(n))&&((t||(t={}))[n]=e[n]);return t},xc=(e,t)=>{const n={};for(const o in e)(!Er(o)||!(o.slice(9)in t))&&(n[o]=e[o]);return n};function Pc(e,t,n){const{props:o,children:r,component:s}=e,{props:i,children:l,patchFlag:a}=t,c=s.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&a>=0){if(a&1024)return!0;if(a&16)return o?hs(o,i,c):!!i;if(a&8){const u=t.dynamicProps;for(let f=0;fe.__isSuspense;function Xi(e,t){t&&t.pendingBranch?q(e)?t.effects.push(...e):t.effects.push(e):yc(e)}function el(e,t){return Mr(e,null,t)}const oo={};function et(e,t,n){return Mr(e,t,n)}function Mr(e,t,{immediate:n,deep:o,flush:r,onTrack:s,onTrigger:i}=Te){var l;const a=Ai()===((l=ke)==null?void 0:l.scope)?ke:null;let c,u=!1,f=!1;if(Ae(e)?(c=()=>e.value,u=go(e)):ln(e)?(c=()=>e,o=!0):q(e)?(f=!0,u=e.some(k=>ln(k)||go(k)),c=()=>e.map(k=>{if(Ae(k))return k.value;if(ln(k))return Kt(k);if(re(k))return Dt(k,a,2)})):re(e)?t?c=()=>Dt(e,a,2):c=()=>{if(!(a&&a.isUnmounted))return p&&p(),Xe(e,a,3,[v])}:c=st,t&&o){const k=c;c=()=>Kt(k())}let p,v=k=>{p=b.onStop=()=>{Dt(k,a,4)}},y;if(pn)if(v=st,t?n&&Xe(t,a,3,[c(),f?[]:void 0,v]):c(),r==="sync"){const k=Cu();y=k.__watcherHandles||(k.__watcherHandles=[])}else return st;let w=f?new Array(e.length).fill(oo):oo;const x=()=>{if(b.active)if(t){const k=b.run();(o||u||(f?k.some((W,ee)=>Dn(W,w[ee])):Dn(k,w)))&&(p&&p(),Xe(t,a,3,[k,w===oo?void 0:f&&w[0]===oo?[]:w,v]),w=k)}else b.run()};x.allowRecurse=!!t;let g;r==="sync"?g=x:r==="post"?g=()=>Ke(x,a&&a.suspense):(x.pre=!0,a&&(x.id=a.uid),g=()=>$o(x));const b=new Lr(c,g);t?n?x():w=b.run():r==="post"?Ke(b.run.bind(b),a&&a.suspense):b.run();const I=()=>{b.stop(),a&&a.scope&&wr(a.scope.effects,b)};return y&&y.push(I),I}function kc(e,t,n){const o=this.proxy,r=me(e)?e.includes(".")?tl(o,e):()=>o[e]:e.bind(o,o);let s;re(t)?s=t:(s=t.handler,n=t);const i=ke;dn(this);const l=Mr(r,s.bind(o),n);return i?dn(i):Jt(),l}function tl(e,t){const n=t.split(".");return()=>{let o=e;for(let r=0;r{Kt(n,t)});else if(Oi(e))for(const n in e)Kt(e[n],t);return e}function Hn(e,t){const n=Ne;if(n===null)return e;const o=Vo(n)||n.proxy,r=e.dirs||(e.dirs=[]);for(let s=0;s{e.isMounted=!0}),Jn(()=>{e.isUnmounting=!0}),e}const Ge=[Function,Array],nl={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Ge,onEnter:Ge,onAfterEnter:Ge,onEnterCancelled:Ge,onBeforeLeave:Ge,onLeave:Ge,onAfterLeave:Ge,onLeaveCancelled:Ge,onBeforeAppear:Ge,onAppear:Ge,onAfterAppear:Ge,onAppearCancelled:Ge},Ac={name:"BaseTransition",props:nl,setup(e,{slots:t}){const n=yl(),o=Ic();let r;return()=>{const s=t.default&&rl(t.default(),!0);if(!s||!s.length)return;let i=s[0];if(s.length>1){for(const w of s)if(w.type!==Ye){i=w;break}}const l=he(e),{mode:a}=l;if(o.isLeaving)return qo(i);const c=ms(i);if(!c)return qo(i);const u=ar(c,l,o,n);cr(c,u);const f=n.subTree,p=f&&ms(f);let v=!1;const{getTransitionKey:y}=c.type;if(y){const w=y();r===void 0?r=w:w!==r&&(r=w,v=!0)}if(p&&p.type!==Ye&&(!zt(c,p)||v)){const w=ar(p,l,o,n);if(cr(p,w),a==="out-in")return o.isLeaving=!0,w.afterLeave=()=>{o.isLeaving=!1,n.update.active!==!1&&n.update()},qo(i);a==="in-out"&&c.type!==Ye&&(w.delayLeave=(x,g,b)=>{const I=ol(o,p);I[String(p.key)]=p,x._leaveCb=()=>{g(),x._leaveCb=void 0,delete u.delayedLeave},u.delayedLeave=b})}return i}}},Rc=Ac;function ol(e,t){const{leavingVNodes:n}=e;let o=n.get(t.type);return o||(o=Object.create(null),n.set(t.type,o)),o}function ar(e,t,n,o){const{appear:r,mode:s,persisted:i=!1,onBeforeEnter:l,onEnter:a,onAfterEnter:c,onEnterCancelled:u,onBeforeLeave:f,onLeave:p,onAfterLeave:v,onLeaveCancelled:y,onBeforeAppear:w,onAppear:x,onAfterAppear:g,onAppearCancelled:b}=t,I=String(e.key),k=ol(n,e),W=(m,F)=>{m&&Xe(m,o,9,F)},ee=(m,F)=>{const B=F[1];W(m,F),q(m)?m.every(Y=>Y.length<=1)&&B():m.length<=1&&B()},N={mode:s,persisted:i,beforeEnter(m){let F=l;if(!n.isMounted)if(r)F=w||l;else return;m._leaveCb&&m._leaveCb(!0);const B=k[I];B&&zt(e,B)&&B.el._leaveCb&&B.el._leaveCb(),W(F,[m])},enter(m){let F=a,B=c,Y=u;if(!n.isMounted)if(r)F=x||a,B=g||c,Y=b||u;else return;let L=!1;const R=m._enterCb=D=>{L||(L=!0,D?W(Y,[m]):W(B,[m]),N.delayedLeave&&N.delayedLeave(),m._enterCb=void 0)};F?ee(F,[m,R]):R()},leave(m,F){const B=String(e.key);if(m._enterCb&&m._enterCb(!0),n.isUnmounting)return F();W(f,[m]);let Y=!1;const L=m._leaveCb=R=>{Y||(Y=!0,F(),R?W(y,[m]):W(v,[m]),m._leaveCb=void 0,k[B]===e&&delete k[B])};k[B]=e,p?ee(p,[m,L]):L()},clone(m){return ar(m,t,n,o)}};return N}function qo(e){if(Wn(e))return e=Nt(e),e.children=null,e}function ms(e){return Wn(e)?e.children?e.children[0]:void 0:e}function cr(e,t){e.shapeFlag&6&&e.component?cr(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function rl(e,t=!1,n){let o=[],r=0;for(let s=0;s1)for(let s=0;sSe({name:e.name},t,{setup:e}))():e}const cn=e=>!!e.type.__asyncLoader;function te(e){re(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:o,delay:r=200,timeout:s,suspensible:i=!0,onError:l}=e;let a=null,c,u=0;const f=()=>(u++,a=null,p()),p=()=>{let v;return a||(v=a=t().catch(y=>{if(y=y instanceof Error?y:new Error(String(y)),l)return new Promise((w,x)=>{l(y,()=>w(f()),()=>x(y),u+1)});throw y}).then(y=>v!==a&&a?a:(y&&(y.__esModule||y[Symbol.toStringTag]==="Module")&&(y=y.default),c=y,y)))};return fe({name:"AsyncComponentWrapper",__asyncLoader:p,get __asyncResolved(){return c},setup(){const v=ke;if(c)return()=>Wo(c,v);const y=b=>{a=null,qn(b,v,13,!o)};if(i&&v.suspense||pn)return p().then(b=>()=>Wo(b,v)).catch(b=>(y(b),()=>o?ne(o,{error:b}):null));const w=be(!1),x=be(),g=be(!!r);return r&&setTimeout(()=>{g.value=!1},r),s!=null&&setTimeout(()=>{if(!w.value&&!x.value){const b=new Error(`Async component timed out after ${s}ms.`);y(b),x.value=b}},s),p().then(()=>{w.value=!0,v.parent&&Wn(v.parent.vnode)&&$o(v.parent.update)}).catch(b=>{y(b),x.value=b}),()=>{if(w.value&&c)return Wo(c,v);if(x.value&&o)return ne(o,{error:x.value});if(n&&!g.value)return ne(n)}}})}function Wo(e,t){const{ref:n,props:o,children:r,ce:s}=t.vnode,i=ne(e,o,r);return i.ref=n,i.ce=s,delete t.vnode.ce,i}const Wn=e=>e.type.__isKeepAlive;function Dc(e,t){sl(e,"a",t)}function $c(e,t){sl(e,"da",t)}function sl(e,t,n=ke){const o=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(Ho(t,o,n),n){let r=n.parent;for(;r&&r.parent;)Wn(r.parent.vnode)&&Mc(o,t,n,r),r=r.parent}}function Mc(e,t,n,o){const r=Ho(t,e,o,!0);Bo(()=>{wr(o[t],r)},n)}function Ho(e,t,n=ke,o=!1){if(n){const r=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...i)=>{if(n.isUnmounted)return;vn(),dn(n);const l=Xe(t,n,e,i);return Jt(),gn(),l});return o?r.unshift(s):r.push(s),s}}const wt=e=>(t,n=ke)=>(!pn||e==="sp")&&Ho(e,(...o)=>t(...o),n),Nc=wt("bm"),We=wt("m"),Hc=wt("bu"),il=wt("u"),Jn=wt("bum"),Bo=wt("um"),Bc=wt("sp"),jc=wt("rtg"),Vc=wt("rtc");function Fc(e,t=ke){Ho("ec",e,t)}const ll="components";function bt(e,t){return Uc(ll,e,!0,t)||e}const zc=Symbol.for("v-ndc");function Uc(e,t,n=!0,o=!1){const r=Ne||ke;if(r){const s=r.type;if(e===ll){const l=yu(s,!1);if(l&&(l===t||l===ft(t)||l===Io(ft(t))))return s}const i=vs(r[e]||s[e],t)||vs(r.appContext[e],t);return!i&&o?s:i}}function vs(e,t){return e&&(e[t]||e[ft(t)]||e[Io(ft(t))])}function yt(e,t,n,o){let r;const s=n&&n[o];if(q(e)||me(e)){r=new Array(e.length);for(let i=0,l=e.length;it(i,l,void 0,s&&s[l]));else{const i=Object.keys(e);r=new Array(i.length);for(let l=0,a=i.length;lCo(t)?!(t.type===Ye||t.type===Ee&&!al(t.children)):!0)?e:null}const ur=e=>e?El(e)?Vo(e)||e.proxy:ur(e.parent):null,Pn=Se(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>ur(e.parent),$root:e=>ur(e.root),$emit:e=>e.emit,$options:e=>Nr(e),$forceUpdate:e=>e.f||(e.f=()=>$o(e.update)),$nextTick:e=>e.n||(e.n=Do.bind(e.proxy)),$watch:e=>kc.bind(e)}),Jo=(e,t)=>e!==Te&&!e.__isScriptSetup&&pe(e,t),Kc={get({_:e},t){const{ctx:n,setupState:o,data:r,props:s,accessCache:i,type:l,appContext:a}=e;let c;if(t[0]!=="$"){const v=i[t];if(v!==void 0)switch(v){case 1:return o[t];case 2:return r[t];case 4:return n[t];case 3:return s[t]}else{if(Jo(o,t))return i[t]=1,o[t];if(r!==Te&&pe(r,t))return i[t]=2,r[t];if((c=e.propsOptions[0])&&pe(c,t))return i[t]=3,s[t];if(n!==Te&&pe(n,t))return i[t]=4,n[t];fr&&(i[t]=0)}}const u=Pn[t];let f,p;if(u)return t==="$attrs"&&qe(e,"get",t),u(e);if((f=l.__cssModules)&&(f=f[t]))return f;if(n!==Te&&pe(n,t))return i[t]=4,n[t];if(p=a.config.globalProperties,pe(p,t))return p[t]},set({_:e},t,n){const{data:o,setupState:r,ctx:s}=e;return Jo(r,t)?(r[t]=n,!0):o!==Te&&pe(o,t)?(o[t]=n,!0):pe(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:o,appContext:r,propsOptions:s}},i){let l;return!!n[i]||e!==Te&&pe(e,i)||Jo(t,i)||(l=s[0])&&pe(l,i)||pe(o,i)||pe(Pn,i)||pe(r.config.globalProperties,i)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:pe(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function gs(e){return q(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let fr=!0;function qc(e){const t=Nr(e),n=e.proxy,o=e.ctx;fr=!1,t.beforeCreate&&_s(t.beforeCreate,e,"bc");const{data:r,computed:s,methods:i,watch:l,provide:a,inject:c,created:u,beforeMount:f,mounted:p,beforeUpdate:v,updated:y,activated:w,deactivated:x,beforeDestroy:g,beforeUnmount:b,destroyed:I,unmounted:k,render:W,renderTracked:ee,renderTriggered:N,errorCaptured:m,serverPrefetch:F,expose:B,inheritAttrs:Y,components:L,directives:R,filters:D}=t;if(c&&Wc(c,o,null),i)for(const se in i){const ie=i[se];re(ie)&&(o[se]=ie.bind(n))}if(r){const se=r.call(n,n);ye(se)&&(e.data=Kn(se))}if(fr=!0,s)for(const se in s){const ie=s[se],He=re(ie)?ie.bind(n,n):re(ie.get)?ie.get.bind(n,n):st,$e=!re(ie)&&re(ie.set)?ie.set.bind(n):st,Ue=z({get:He,set:$e});Object.defineProperty(o,se,{enumerable:!0,configurable:!0,get:()=>Ue.value,set:Be=>Ue.value=Be})}if(l)for(const se in l)cl(l[se],o,n,se);if(a){const se=re(a)?a.call(n):a;Reflect.ownKeys(se).forEach(ie=>{Wt(ie,se[ie])})}u&&_s(u,e,"c");function U(se,ie){q(ie)?ie.forEach(He=>se(He.bind(n))):ie&&se(ie.bind(n))}if(U(Nc,f),U(We,p),U(Hc,v),U(il,y),U(Dc,w),U($c,x),U(Fc,m),U(Vc,ee),U(jc,N),U(Jn,b),U(Bo,k),U(Bc,F),q(B))if(B.length){const se=e.exposed||(e.exposed={});B.forEach(ie=>{Object.defineProperty(se,ie,{get:()=>n[ie],set:He=>n[ie]=He})})}else e.exposed||(e.exposed={});W&&e.render===st&&(e.render=W),Y!=null&&(e.inheritAttrs=Y),L&&(e.components=L),R&&(e.directives=R)}function Wc(e,t,n=st){q(e)&&(e=dr(e));for(const o in e){const r=e[o];let s;ye(r)?"default"in r?s=Oe(r.from||o,r.default,!0):s=Oe(r.from||o):s=Oe(r),Ae(s)?Object.defineProperty(t,o,{enumerable:!0,configurable:!0,get:()=>s.value,set:i=>s.value=i}):t[o]=s}}function _s(e,t,n){Xe(q(e)?e.map(o=>o.bind(t.proxy)):e.bind(t.proxy),t,n)}function cl(e,t,n,o){const r=o.includes(".")?tl(n,o):()=>n[o];if(me(e)){const s=t[e];re(s)&&et(r,s)}else if(re(e))et(r,e.bind(n));else if(ye(e))if(q(e))e.forEach(s=>cl(s,t,n,o));else{const s=re(e.handler)?e.handler.bind(n):t[e.handler];re(s)&&et(r,s,e)}}function Nr(e){const t=e.type,{mixins:n,extends:o}=t,{mixins:r,optionsCache:s,config:{optionMergeStrategies:i}}=e.appContext,l=s.get(t);let a;return l?a=l:!r.length&&!n&&!o?a=t:(a={},r.length&&r.forEach(c=>yo(a,c,i,!0)),yo(a,t,i)),ye(t)&&s.set(t,a),a}function yo(e,t,n,o=!1){const{mixins:r,extends:s}=t;s&&yo(e,s,n,!0),r&&r.forEach(i=>yo(e,i,n,!0));for(const i in t)if(!(o&&i==="expose")){const l=Jc[i]||n&&n[i];e[i]=l?l(e[i],t[i]):t[i]}return e}const Jc={data:bs,props:ys,emits:ys,methods:Ln,computed:Ln,beforeCreate:Ve,created:Ve,beforeMount:Ve,mounted:Ve,beforeUpdate:Ve,updated:Ve,beforeDestroy:Ve,beforeUnmount:Ve,destroyed:Ve,unmounted:Ve,activated:Ve,deactivated:Ve,errorCaptured:Ve,serverPrefetch:Ve,components:Ln,directives:Ln,watch:Yc,provide:bs,inject:Qc};function bs(e,t){return t?e?function(){return Se(re(e)?e.call(this,this):e,re(t)?t.call(this,this):t)}:t:e}function Qc(e,t){return Ln(dr(e),dr(t))}function dr(e){if(q(e)){const t={};for(let n=0;n1)return n&&re(t)?t.call(o&&o.proxy):t}}function Xc(e,t,n,o=!1){const r={},s={};mo(s,jo,1),e.propsDefaults=Object.create(null),fl(e,t,r,s);for(const i in e.propsOptions[0])i in r||(r[i]=void 0);n?e.props=o?r:zi(r):e.type.props?e.props=r:e.props=s,e.attrs=s}function eu(e,t,n,o){const{props:r,attrs:s,vnode:{patchFlag:i}}=e,l=he(r),[a]=e.propsOptions;let c=!1;if((o||i>0)&&!(i&16)){if(i&8){const u=e.vnode.dynamicProps;for(let f=0;f{a=!0;const[p,v]=dl(f,t,!0);Se(i,p),v&&l.push(...v)};!n&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}if(!s&&!a)return ye(e)&&o.set(e,rn),rn;if(q(s))for(let u=0;u-1,v[1]=w<0||y-1||pe(v,"default"))&&l.push(f)}}}const c=[i,l];return ye(e)&&o.set(e,c),c}function Es(e){return e[0]!=="$"}function ws(e){const t=e&&e.toString().match(/^\s*(function|class) (\w+)/);return t?t[2]:e===null?"null":""}function Cs(e,t){return ws(e)===ws(t)}function Ts(e,t){return q(t)?t.findIndex(n=>Cs(n,e)):re(t)&&Cs(t,e)?0:-1}const pl=e=>e[0]==="_"||e==="$stable",Hr=e=>q(e)?e.map(nt):[nt(e)],tu=(e,t,n)=>{if(t._n)return t;const o=Me((...r)=>Hr(t(...r)),n);return o._c=!1,o},hl=(e,t,n)=>{const o=e._ctx;for(const r in e){if(pl(r))continue;const s=e[r];if(re(s))t[r]=tu(r,s,o);else if(s!=null){const i=Hr(s);t[r]=()=>i}}},ml=(e,t)=>{const n=Hr(t);e.slots.default=()=>n},nu=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=he(t),mo(t,"_",n)):hl(t,e.slots={})}else e.slots={},t&&ml(e,t);mo(e.slots,jo,1)},ou=(e,t,n)=>{const{vnode:o,slots:r}=e;let s=!0,i=Te;if(o.shapeFlag&32){const l=t._;l?n&&l===1?s=!1:(Se(r,t),!n&&l===1&&delete r._):(s=!t.$stable,hl(t,r)),i=t}else t&&(ml(e,t),i={default:1});if(s)for(const l in r)!pl(l)&&!(l in i)&&delete r[l]};function wo(e,t,n,o,r=!1){if(q(e)){e.forEach((p,v)=>wo(p,t&&(q(t)?t[v]:t),n,o,r));return}if(cn(o)&&!r)return;const s=o.shapeFlag&4?Vo(o.component)||o.component.proxy:o.el,i=r?null:s,{i:l,r:a}=e,c=t&&t.r,u=l.refs===Te?l.refs={}:l.refs,f=l.setupState;if(c!=null&&c!==a&&(me(c)?(u[c]=null,pe(f,c)&&(f[c]=null)):Ae(c)&&(c.value=null)),re(a))Dt(a,l,12,[i,u]);else{const p=me(a),v=Ae(a);if(p||v){const y=()=>{if(e.f){const w=p?pe(f,a)?f[a]:u[a]:a.value;r?q(w)&&wr(w,s):q(w)?w.includes(s)||w.push(s):p?(u[a]=[s],pe(f,a)&&(f[a]=u[a])):(a.value=[s],e.k&&(u[e.k]=a.value))}else p?(u[a]=i,pe(f,a)&&(f[a]=i)):v&&(a.value=i,e.k&&(u[e.k]=i))};i?(y.id=-1,Ke(y,n)):y()}}}let Pt=!1;const ro=e=>/svg/.test(e.namespaceURI)&&e.tagName!=="foreignObject",so=e=>e.nodeType===8;function ru(e){const{mt:t,p:n,o:{patchProp:o,createText:r,nextSibling:s,parentNode:i,remove:l,insert:a,createComment:c}}=e,u=(g,b)=>{if(!b.hasChildNodes()){n(null,g,b),_o(),b._vnode=g;return}Pt=!1,f(b.firstChild,g,null,null,null),_o(),b._vnode=g,Pt&&console.error("Hydration completed but contains mismatches.")},f=(g,b,I,k,W,ee=!1)=>{const N=so(g)&&g.data==="[",m=()=>w(g,b,I,k,W,N),{type:F,ref:B,shapeFlag:Y,patchFlag:L}=b;let R=g.nodeType;b.el=g,L===-2&&(ee=!1,b.dynamicChildren=null);let D=null;switch(F){case fn:R!==3?b.children===""?(a(b.el=r(""),i(g),g),D=g):D=m():(g.data!==b.children&&(Pt=!0,g.data=b.children),D=s(g));break;case Ye:R!==8||N?D=m():D=s(g);break;case On:if(N&&(g=s(g),R=g.nodeType),R===1||R===3){D=g;const le=!b.children.length;for(let U=0;U{ee=ee||!!b.dynamicChildren;const{type:N,props:m,patchFlag:F,shapeFlag:B,dirs:Y}=b,L=N==="input"&&Y||N==="option";if(L||F!==-1){if(Y&&ct(b,null,I,"created"),m)if(L||!ee||F&48)for(const D in m)(L&&D.endsWith("value")||zn(D)&&!xn(D))&&o(g,D,null,m[D],!1,void 0,I);else m.onClick&&o(g,"onClick",null,m.onClick,!1,void 0,I);let R;if((R=m&&m.onVnodeBeforeMount)&&Ze(R,I,b),Y&&ct(b,null,I,"beforeMount"),((R=m&&m.onVnodeMounted)||Y)&&Xi(()=>{R&&Ze(R,I,b),Y&&ct(b,null,I,"mounted")},k),B&16&&!(m&&(m.innerHTML||m.textContent))){let D=v(g.firstChild,b,g,I,k,W,ee);for(;D;){Pt=!0;const le=D;D=D.nextSibling,l(le)}}else B&8&&g.textContent!==b.children&&(Pt=!0,g.textContent=b.children)}return g.nextSibling},v=(g,b,I,k,W,ee,N)=>{N=N||!!b.dynamicChildren;const m=b.children,F=m.length;for(let B=0;B{const{slotScopeIds:N}=b;N&&(W=W?W.concat(N):N);const m=i(g),F=v(s(g),b,m,I,k,W,ee);return F&&so(F)&&F.data==="]"?s(b.anchor=F):(Pt=!0,a(b.anchor=c("]"),m,F),F)},w=(g,b,I,k,W,ee)=>{if(Pt=!0,b.el=null,ee){const F=x(g);for(;;){const B=s(g);if(B&&B!==F)l(B);else break}}const N=s(g),m=i(g);return l(g),n(null,b,m,N,I,k,ro(m),W),N},x=g=>{let b=0;for(;g;)if(g=s(g),g&&so(g)&&(g.data==="["&&b++,g.data==="]")){if(b===0)return s(g);b--}return g};return[u,f]}const Ke=Xi;function su(e){return vl(e)}function iu(e){return vl(e,ru)}function vl(e,t){const n=or();n.__VUE__=!0;const{insert:o,remove:r,patchProp:s,createElement:i,createText:l,createComment:a,setText:c,setElementText:u,parentNode:f,nextSibling:p,setScopeId:v=st,insertStaticContent:y}=e,w=(d,h,_,E=null,T=null,P=null,j=!1,A=null,M=!!h.dynamicChildren)=>{if(d===h)return;d&&!zt(d,h)&&(E=C(d),Be(d,T,P,!0),d=null),h.patchFlag===-2&&(M=!1,h.dynamicChildren=null);const{type:S,ref:G,shapeFlag:K}=h;switch(S){case fn:x(d,h,_,E);break;case Ye:g(d,h,_,E);break;case On:d==null&&b(h,_,E,j);break;case Ee:L(d,h,_,E,T,P,j,A,M);break;default:K&1?W(d,h,_,E,T,P,j,A,M):K&6?R(d,h,_,E,T,P,j,A,M):(K&64||K&128)&&S.process(d,h,_,E,T,P,j,A,M,$)}G!=null&&T&&wo(G,d&&d.ref,P,h||d,!h)},x=(d,h,_,E)=>{if(d==null)o(h.el=l(h.children),_,E);else{const T=h.el=d.el;h.children!==d.children&&c(T,h.children)}},g=(d,h,_,E)=>{d==null?o(h.el=a(h.children||""),_,E):h.el=d.el},b=(d,h,_,E)=>{[d.el,d.anchor]=y(d.children,h,_,E,d.el,d.anchor)},I=({el:d,anchor:h},_,E)=>{let T;for(;d&&d!==h;)T=p(d),o(d,_,E),d=T;o(h,_,E)},k=({el:d,anchor:h})=>{let _;for(;d&&d!==h;)_=p(d),r(d),d=_;r(h)},W=(d,h,_,E,T,P,j,A,M)=>{j=j||h.type==="svg",d==null?ee(h,_,E,T,P,j,A,M):F(d,h,T,P,j,A,M)},ee=(d,h,_,E,T,P,j,A)=>{let M,S;const{type:G,props:K,shapeFlag:Z,transition:oe,dirs:ce}=d;if(M=d.el=i(d.type,P,K&&K.is,K),Z&8?u(M,d.children):Z&16&&m(d.children,M,null,E,T,P&&G!=="foreignObject",j,A),ce&&ct(d,null,E,"created"),N(M,d,d.scopeId,j,E),K){for(const _e in K)_e!=="value"&&!xn(_e)&&s(M,_e,null,K[_e],P,d.children,E,T,De);"value"in K&&s(M,"value",null,K.value),(S=K.onVnodeBeforeMount)&&Ze(S,E,d)}ce&&ct(d,null,E,"beforeMount");const we=(!T||T&&!T.pendingBranch)&&oe&&!oe.persisted;we&&oe.beforeEnter(M),o(M,h,_),((S=K&&K.onVnodeMounted)||we||ce)&&Ke(()=>{S&&Ze(S,E,d),we&&oe.enter(M),ce&&ct(d,null,E,"mounted")},T)},N=(d,h,_,E,T)=>{if(_&&v(d,_),E)for(let P=0;P{for(let S=M;S{const A=h.el=d.el;let{patchFlag:M,dynamicChildren:S,dirs:G}=h;M|=d.patchFlag&16;const K=d.props||Te,Z=h.props||Te;let oe;_&&Ht(_,!1),(oe=Z.onVnodeBeforeUpdate)&&Ze(oe,_,h,d),G&&ct(h,d,_,"beforeUpdate"),_&&Ht(_,!0);const ce=T&&h.type!=="foreignObject";if(S?B(d.dynamicChildren,S,A,_,E,ce,P):j||ie(d,h,A,null,_,E,ce,P,!1),M>0){if(M&16)Y(A,h,K,Z,_,E,T);else if(M&2&&K.class!==Z.class&&s(A,"class",null,Z.class,T),M&4&&s(A,"style",K.style,Z.style,T),M&8){const we=h.dynamicProps;for(let _e=0;_e{oe&&Ze(oe,_,h,d),G&&ct(h,d,_,"updated")},E)},B=(d,h,_,E,T,P,j)=>{for(let A=0;A{if(_!==E){if(_!==Te)for(const A in _)!xn(A)&&!(A in E)&&s(d,A,_[A],null,j,h.children,T,P,De);for(const A in E){if(xn(A))continue;const M=E[A],S=_[A];M!==S&&A!=="value"&&s(d,A,S,M,j,h.children,T,P,De)}"value"in E&&s(d,"value",_.value,E.value)}},L=(d,h,_,E,T,P,j,A,M)=>{const S=h.el=d?d.el:l(""),G=h.anchor=d?d.anchor:l("");let{patchFlag:K,dynamicChildren:Z,slotScopeIds:oe}=h;oe&&(A=A?A.concat(oe):oe),d==null?(o(S,_,E),o(G,_,E),m(h.children,_,G,T,P,j,A,M)):K>0&&K&64&&Z&&d.dynamicChildren?(B(d.dynamicChildren,Z,_,T,P,j,A),(h.key!=null||T&&h===T.subTree)&&gl(d,h,!0)):ie(d,h,_,G,T,P,j,A,M)},R=(d,h,_,E,T,P,j,A,M)=>{h.slotScopeIds=A,d==null?h.shapeFlag&512?T.ctx.activate(h,_,E,j,M):D(h,_,E,T,P,j,M):le(d,h,M)},D=(d,h,_,E,T,P,j)=>{const A=d.component=mu(d,E,T);if(Wn(d)&&(A.ctx.renderer=$),vu(A),A.asyncDep){if(T&&T.registerDep(A,U),!d.el){const M=A.subTree=ne(Ye);g(null,M,h,_)}return}U(A,d,h,_,T,P,j)},le=(d,h,_)=>{const E=h.component=d.component;if(Pc(d,h,_))if(E.asyncDep&&!E.asyncResolved){se(E,h,_);return}else E.next=h,bc(E.update),E.update();else h.el=d.el,E.vnode=h},U=(d,h,_,E,T,P,j)=>{const A=()=>{if(d.isMounted){let{next:G,bu:K,u:Z,parent:oe,vnode:ce}=d,we=G,_e;Ht(d,!1),G?(G.el=ce.el,se(d,G,j)):G=ce,K&&fo(K),(_e=G.props&&G.props.onVnodeBeforeUpdate)&&Ze(_e,oe,G,ce),Ht(d,!0);const Pe=Ko(d),tt=d.subTree;d.subTree=Pe,w(tt,Pe,f(tt.el),C(tt),d,T,P),G.el=Pe.el,we===null&&Oc(d,Pe.el),Z&&Ke(Z,T),(_e=G.props&&G.props.onVnodeUpdated)&&Ke(()=>Ze(_e,oe,G,ce),T)}else{let G;const{el:K,props:Z}=h,{bm:oe,m:ce,parent:we}=d,_e=cn(h);if(Ht(d,!1),oe&&fo(oe),!_e&&(G=Z&&Z.onVnodeBeforeMount)&&Ze(G,we,h),Ht(d,!0),K&&ue){const Pe=()=>{d.subTree=Ko(d),ue(K,d.subTree,d,T,null)};_e?h.type.__asyncLoader().then(()=>!d.isUnmounted&&Pe()):Pe()}else{const Pe=d.subTree=Ko(d);w(null,Pe,_,E,d,T,P),h.el=Pe.el}if(ce&&Ke(ce,T),!_e&&(G=Z&&Z.onVnodeMounted)){const Pe=h;Ke(()=>Ze(G,we,Pe),T)}(h.shapeFlag&256||we&&cn(we.vnode)&&we.vnode.shapeFlag&256)&&d.a&&Ke(d.a,T),d.isMounted=!0,h=_=E=null}},M=d.effect=new Lr(A,()=>$o(S),d.scope),S=d.update=()=>M.run();S.id=d.uid,Ht(d,!0),S()},se=(d,h,_)=>{h.component=d;const E=d.vnode.props;d.vnode=h,d.next=null,eu(d,h.props,E,_),ou(d,h.children,_),vn(),ps(),gn()},ie=(d,h,_,E,T,P,j,A,M=!1)=>{const S=d&&d.children,G=d?d.shapeFlag:0,K=h.children,{patchFlag:Z,shapeFlag:oe}=h;if(Z>0){if(Z&128){$e(S,K,_,E,T,P,j,A,M);return}else if(Z&256){He(S,K,_,E,T,P,j,A,M);return}}oe&8?(G&16&&De(S,T,P),K!==S&&u(_,K)):G&16?oe&16?$e(S,K,_,E,T,P,j,A,M):De(S,T,P,!0):(G&8&&u(_,""),oe&16&&m(K,_,E,T,P,j,A,M))},He=(d,h,_,E,T,P,j,A,M)=>{d=d||rn,h=h||rn;const S=d.length,G=h.length,K=Math.min(S,G);let Z;for(Z=0;ZG?De(d,T,P,!0,!1,K):m(h,_,E,T,P,j,A,M,K)},$e=(d,h,_,E,T,P,j,A,M)=>{let S=0;const G=h.length;let K=d.length-1,Z=G-1;for(;S<=K&&S<=Z;){const oe=d[S],ce=h[S]=M?kt(h[S]):nt(h[S]);if(zt(oe,ce))w(oe,ce,_,null,T,P,j,A,M);else break;S++}for(;S<=K&&S<=Z;){const oe=d[K],ce=h[Z]=M?kt(h[Z]):nt(h[Z]);if(zt(oe,ce))w(oe,ce,_,null,T,P,j,A,M);else break;K--,Z--}if(S>K){if(S<=Z){const oe=Z+1,ce=oeZ)for(;S<=K;)Be(d[S],T,P,!0),S++;else{const oe=S,ce=S,we=new Map;for(S=ce;S<=Z;S++){const Je=h[S]=M?kt(h[S]):nt(h[S]);Je.key!=null&&we.set(Je.key,S)}let _e,Pe=0;const tt=Z-ce+1;let Xt=!1,es=0;const bn=new Array(tt);for(S=0;S=tt){Be(Je,T,P,!0);continue}let at;if(Je.key!=null)at=we.get(Je.key);else for(_e=ce;_e<=Z;_e++)if(bn[_e-ce]===0&&zt(Je,h[_e])){at=_e;break}at===void 0?Be(Je,T,P,!0):(bn[at-ce]=S+1,at>=es?es=at:Xt=!0,w(Je,h[at],_,null,T,P,j,A,M),Pe++)}const ts=Xt?lu(bn):rn;for(_e=ts.length-1,S=tt-1;S>=0;S--){const Je=ce+S,at=h[Je],ns=Je+1{const{el:P,type:j,transition:A,children:M,shapeFlag:S}=d;if(S&6){Ue(d.component.subTree,h,_,E);return}if(S&128){d.suspense.move(h,_,E);return}if(S&64){j.move(d,h,_,$);return}if(j===Ee){o(P,h,_);for(let K=0;KA.enter(P),T);else{const{leave:K,delayLeave:Z,afterLeave:oe}=A,ce=()=>o(P,h,_),we=()=>{K(P,()=>{ce(),oe&&oe()})};Z?Z(P,ce,we):we()}else o(P,h,_)},Be=(d,h,_,E=!1,T=!1)=>{const{type:P,props:j,ref:A,children:M,dynamicChildren:S,shapeFlag:G,patchFlag:K,dirs:Z}=d;if(A!=null&&wo(A,null,_,d,!0),G&256){h.ctx.deactivate(d);return}const oe=G&1&&Z,ce=!cn(d);let we;if(ce&&(we=j&&j.onVnodeBeforeUnmount)&&Ze(we,h,d),G&6)lt(d.component,_,E);else{if(G&128){d.suspense.unmount(_,E);return}oe&&ct(d,null,h,"beforeUnmount"),G&64?d.type.remove(d,h,_,T,$,E):S&&(P!==Ee||K>0&&K&64)?De(S,h,_,!1,!0):(P===Ee&&K&384||!T&&G&16)&&De(M,h,_),E&&Tt(d)}(ce&&(we=j&&j.onVnodeUnmounted)||oe)&&Ke(()=>{we&&Ze(we,h,d),oe&&ct(d,null,h,"unmounted")},_)},Tt=d=>{const{type:h,el:_,anchor:E,transition:T}=d;if(h===Ee){Lt(_,E);return}if(h===On){k(d);return}const P=()=>{r(_),T&&!T.persisted&&T.afterLeave&&T.afterLeave()};if(d.shapeFlag&1&&T&&!T.persisted){const{leave:j,delayLeave:A}=T,M=()=>j(_,P);A?A(d.el,P,M):M()}else P()},Lt=(d,h)=>{let _;for(;d!==h;)_=p(d),r(d),d=_;r(h)},lt=(d,h,_)=>{const{bum:E,scope:T,update:P,subTree:j,um:A}=d;E&&fo(E),T.stop(),P&&(P.active=!1,Be(j,d,h,_)),A&&Ke(A,h),Ke(()=>{d.isUnmounted=!0},h),h&&h.pendingBranch&&!h.isUnmounted&&d.asyncDep&&!d.asyncResolved&&d.suspenseId===h.pendingId&&(h.deps--,h.deps===0&&h.resolve())},De=(d,h,_,E=!1,T=!1,P=0)=>{for(let j=P;jd.shapeFlag&6?C(d.component.subTree):d.shapeFlag&128?d.suspense.next():p(d.anchor||d.el),V=(d,h,_)=>{d==null?h._vnode&&Be(h._vnode,null,null,!0):w(h._vnode||null,d,h,null,null,null,_),ps(),_o(),h._vnode=d},$={p:w,um:Be,m:Ue,r:Tt,mt:D,mc:m,pc:ie,pbc:B,n:C,o:e};let J,ue;return t&&([J,ue]=t($)),{render:V,hydrate:J,createApp:Zc(V,J)}}function Ht({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function gl(e,t,n=!1){const o=e.children,r=t.children;if(q(o)&&q(r))for(let s=0;s>1,e[n[l]]0&&(t[o]=n[s-1]),n[s]=o)}}for(s=n.length,i=n[s-1];s-- >0;)n[s]=i,i=t[i];return n}const au=e=>e.__isTeleport,Ee=Symbol.for("v-fgt"),fn=Symbol.for("v-txt"),Ye=Symbol.for("v-cmt"),On=Symbol.for("v-stc"),Sn=[];let rt=null;function H(e=!1){Sn.push(rt=e?null:[])}function cu(){Sn.pop(),rt=Sn[Sn.length-1]||null}let Bn=1;function Ls(e){Bn+=e}function _l(e){return e.dynamicChildren=Bn>0?rt||rn:null,cu(),Bn>0&&rt&&rt.push(e),e}function Q(e,t,n,o,r,s){return _l(ae(e,t,n,o,r,s,!0))}function Re(e,t,n,o,r){return _l(ne(e,t,n,o,r,!0))}function Co(e){return e?e.__v_isVNode===!0:!1}function zt(e,t){return e.type===t.type&&e.key===t.key}const jo="__vInternal",bl=({key:e})=>e??null,po=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?me(e)||Ae(e)||re(e)?{i:Ne,r:e,k:t,f:!!n}:e:null);function ae(e,t=null,n=null,o=0,r=null,s=e===Ee?0:1,i=!1,l=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&bl(t),ref:t&&po(t),scopeId:No,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:o,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:Ne};return l?(Br(a,n),s&128&&e.normalize(a)):n&&(a.shapeFlag|=me(n)?8:16),Bn>0&&!i&&rt&&(a.patchFlag>0||s&6)&&a.patchFlag!==32&&rt.push(a),a}const ne=uu;function uu(e,t=null,n=null,o=0,r=null,s=!1){if((!e||e===zc)&&(e=Ye),Co(e)){const l=Nt(e,t,!0);return n&&Br(l,n),Bn>0&&!s&&rt&&(l.shapeFlag&6?rt[rt.indexOf(e)]=l:rt.push(l)),l.patchFlag|=-2,l}if(Eu(e)&&(e=e.__vccOpts),t){t=fu(t);let{class:l,style:a}=t;l&&!me(l)&&(t.class=ze(l)),ye(a)&&(Ui(a)&&!q(a)&&(a=Se({},a)),t.style=Qt(a))}const i=me(e)?1:Sc(e)?128:au(e)?64:ye(e)?4:re(e)?2:0;return ae(e,t,n,o,r,i,s,!0)}function fu(e){return e?Ui(e)||jo in e?Se({},e):e:null}function Nt(e,t,n=!1){const{props:o,ref:r,patchFlag:s,children:i}=e,l=t?hr(o||{},t):o;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&bl(l),ref:t&&t.ref?n&&r?q(r)?r.concat(po(t)):[r,po(t)]:po(t):r,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:i,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Ee?s===-1?16:s|16:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Nt(e.ssContent),ssFallback:e.ssFallback&&Nt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Et(e=" ",t=0){return ne(fn,null,e,t)}function du(e,t){const n=ne(On,null,e);return n.staticCount=t,n}function xe(e="",t=!1){return t?(H(),Re(Ye,null,e)):ne(Ye,null,e)}function nt(e){return e==null||typeof e=="boolean"?ne(Ye):q(e)?ne(Ee,null,e.slice()):typeof e=="object"?kt(e):ne(fn,null,String(e))}function kt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Nt(e)}function Br(e,t){let n=0;const{shapeFlag:o}=e;if(t==null)t=null;else if(q(t))n=16;else if(typeof t=="object")if(o&65){const r=t.default;r&&(r._c&&(r._d=!1),Br(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!(jo in t)?t._ctx=Ne:r===3&&Ne&&(Ne.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else re(t)?(t={default:t,_ctx:Ne},n=32):(t=String(t),o&64?(n=16,t=[Et(t)]):n=8);e.children=t,e.shapeFlag|=n}function hr(...e){const t={};for(let n=0;nke||Ne;let jr,en,xs="__VUE_INSTANCE_SETTERS__";(en=or()[xs])||(en=or()[xs]=[]),en.push(e=>ke=e),jr=e=>{en.length>1?en.forEach(t=>t(e)):en[0](e)};const dn=e=>{jr(e),e.scope.on()},Jt=()=>{ke&&ke.scope.off(),jr(null)};function El(e){return e.vnode.shapeFlag&4}let pn=!1;function vu(e,t=!1){pn=t;const{props:n,children:o}=e.vnode,r=El(e);Xc(e,n,r,t),nu(e,o);const s=r?gu(e,t):void 0;return pn=!1,s}function gu(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Ki(new Proxy(e.ctx,Kc));const{setup:o}=n;if(o){const r=e.setupContext=o.length>1?bu(e):null;dn(e),vn();const s=Dt(o,e,0,[e.props,r]);if(gn(),Jt(),xi(s)){if(s.then(Jt,Jt),t)return s.then(i=>{Ps(e,i,t)}).catch(i=>{qn(i,e,0)});e.asyncDep=s}else Ps(e,s,t)}else wl(e,t)}function Ps(e,t,n){re(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ye(t)&&(e.setupState=Wi(t)),wl(e,n)}let Os;function wl(e,t,n){const o=e.type;if(!e.render){if(!t&&Os&&!o.render){const r=o.template||Nr(e).template;if(r){const{isCustomElement:s,compilerOptions:i}=e.appContext.config,{delimiters:l,compilerOptions:a}=o,c=Se(Se({isCustomElement:s,delimiters:l},i),a);o.render=Os(r,c)}}e.render=o.render||st}dn(e),vn(),qc(e),gn(),Jt()}function _u(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(t,n){return qe(e,"get","$attrs"),t[n]}}))}function bu(e){const t=n=>{e.exposed=n||{}};return{get attrs(){return _u(e)},slots:e.slots,emit:e.emit,expose:t}}function Vo(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Wi(Ki(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Pn)return Pn[n](e)},has(t,n){return n in t||n in Pn}}))}function yu(e,t=!0){return re(e)?e.displayName||e.name:e.name||t&&e.__name}function Eu(e){return re(e)&&"__vccOpts"in e}const z=(e,t)=>vc(e,t,pn);function ge(e,t,n){const o=arguments.length;return o===2?ye(t)&&!q(t)?Co(t)?ne(e,null,[t]):ne(e,t):ne(e,null,t):(o>3?n=Array.prototype.slice.call(arguments,2):o===3&&Co(n)&&(n=[n]),ne(e,t,n))}const wu=Symbol.for("v-scx"),Cu=()=>Oe(wu),Tu="3.3.4",Lu="http://www.w3.org/2000/svg",Ut=typeof document<"u"?document:null,Ss=Ut&&Ut.createElement("template"),xu={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,o)=>{const r=t?Ut.createElementNS(Lu,e):Ut.createElement(e,n?{is:n}:void 0);return e==="select"&&o&&o.multiple!=null&&r.setAttribute("multiple",o.multiple),r},createText:e=>Ut.createTextNode(e),createComment:e=>Ut.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ut.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,o,r,s){const i=n?n.previousSibling:t.lastChild;if(r&&(r===s||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===s||!(r=r.nextSibling)););else{Ss.innerHTML=o?`${e}`:e;const l=Ss.content;if(o){const a=l.firstChild;for(;a.firstChild;)l.appendChild(a.firstChild);l.removeChild(a)}t.insertBefore(l,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};function Pu(e,t,n){const o=e._vtc;o&&(t=(t?[t,...o]:[...o]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}function Ou(e,t,n){const o=e.style,r=me(n);if(n&&!r){if(t&&!me(t))for(const s in t)n[s]==null&&mr(o,s,"");for(const s in n)mr(o,s,n[s])}else{const s=o.display;r?t!==n&&(o.cssText=n):t&&e.removeAttribute("style"),"_vod"in e&&(o.display=s)}}const ks=/\s*!important$/;function mr(e,t,n){if(q(n))n.forEach(o=>mr(e,t,o));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const o=Su(e,t);ks.test(n)?e.setProperty(Yt(o),n.replace(ks,""),"important"):e[o]=n}}const Is=["Webkit","Moz","ms"],Qo={};function Su(e,t){const n=Qo[t];if(n)return n;let o=ft(t);if(o!=="filter"&&o in e)return Qo[t]=o;o=Io(o);for(let r=0;rYo||($u.then(()=>Yo=0),Yo=Date.now());function Nu(e,t){const n=o=>{if(!o._vts)o._vts=Date.now();else if(o._vts<=n.attached)return;Xe(Hu(o,n.value),t,5,[o])};return n.value=e,n.attached=Mu(),n}function Hu(e,t){if(q(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(o=>r=>!r._stopped&&o&&o(r))}else return t}const Ds=/^on[a-z]/,Bu=(e,t,n,o,r=!1,s,i,l,a)=>{t==="class"?Pu(e,o,r):t==="style"?Ou(e,n,o):zn(t)?Er(t)||Ru(e,t,n,o,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ju(e,t,o,r))?Iu(e,t,o,s,i,l,a):(t==="true-value"?e._trueValue=o:t==="false-value"&&(e._falseValue=o),ku(e,t,o,r))};function ju(e,t,n,o){return o?!!(t==="innerHTML"||t==="textContent"||t in e&&Ds.test(t)&&re(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||Ds.test(t)&&me(n)?!1:t in e}const Ot="transition",yn="animation",Qn=(e,{slots:t})=>ge(Rc,Vu(e),t);Qn.displayName="Transition";const Tl={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};Qn.props=Se({},nl,Tl);const Bt=(e,t=[])=>{q(e)?e.forEach(n=>n(...t)):e&&e(...t)},$s=e=>e?q(e)?e.some(t=>t.length>1):e.length>1:!1;function Vu(e){const t={};for(const L in e)L in Tl||(t[L]=e[L]);if(e.css===!1)return t;const{name:n="v",type:o,duration:r,enterFromClass:s=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:a=s,appearActiveClass:c=i,appearToClass:u=l,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:p=`${n}-leave-active`,leaveToClass:v=`${n}-leave-to`}=e,y=Fu(r),w=y&&y[0],x=y&&y[1],{onBeforeEnter:g,onEnter:b,onEnterCancelled:I,onLeave:k,onLeaveCancelled:W,onBeforeAppear:ee=g,onAppear:N=b,onAppearCancelled:m=I}=t,F=(L,R,D)=>{jt(L,R?u:l),jt(L,R?c:i),D&&D()},B=(L,R)=>{L._isLeaving=!1,jt(L,f),jt(L,v),jt(L,p),R&&R()},Y=L=>(R,D)=>{const le=L?N:b,U=()=>F(R,L,D);Bt(le,[R,U]),Ms(()=>{jt(R,L?a:s),St(R,L?u:l),$s(le)||Ns(R,o,w,U)})};return Se(t,{onBeforeEnter(L){Bt(g,[L]),St(L,s),St(L,i)},onBeforeAppear(L){Bt(ee,[L]),St(L,a),St(L,c)},onEnter:Y(!1),onAppear:Y(!0),onLeave(L,R){L._isLeaving=!0;const D=()=>B(L,R);St(L,f),Ku(),St(L,p),Ms(()=>{L._isLeaving&&(jt(L,f),St(L,v),$s(k)||Ns(L,o,x,D))}),Bt(k,[L,D])},onEnterCancelled(L){F(L,!1),Bt(I,[L])},onAppearCancelled(L){F(L,!0),Bt(m,[L])},onLeaveCancelled(L){B(L),Bt(W,[L])}})}function Fu(e){if(e==null)return null;if(ye(e))return[Go(e.enter),Go(e.leave)];{const t=Go(e);return[t,t]}}function Go(e){return Ca(e)}function St(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e._vtc||(e._vtc=new Set)).add(t)}function jt(e,t){t.split(/\s+/).forEach(o=>o&&e.classList.remove(o));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function Ms(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let zu=0;function Ns(e,t,n,o){const r=e._endId=++zu,s=()=>{r===e._endId&&o()};if(n)return setTimeout(s,n);const{type:i,timeout:l,propCount:a}=Uu(e,t);if(!i)return o();const c=i+"end";let u=0;const f=()=>{e.removeEventListener(c,p),s()},p=v=>{v.target===e&&++u>=a&&f()};setTimeout(()=>{u(n[y]||"").split(", "),r=o(`${Ot}Delay`),s=o(`${Ot}Duration`),i=Hs(r,s),l=o(`${yn}Delay`),a=o(`${yn}Duration`),c=Hs(l,a);let u=null,f=0,p=0;t===Ot?i>0&&(u=Ot,f=i,p=s.length):t===yn?c>0&&(u=yn,f=c,p=a.length):(f=Math.max(i,c),u=f>0?i>c?Ot:yn:null,p=u?u===Ot?s.length:a.length:0);const v=u===Ot&&/\b(transform|all)(,|$)/.test(o(`${Ot}Property`).toString());return{type:u,timeout:f,propCount:p,hasTransform:v}}function Hs(e,t){for(;e.lengthBs(n)+Bs(e[o])))}function Bs(e){return Number(e.slice(0,-1).replace(",","."))*1e3}function Ku(){return document.body.offsetHeight}const js=e=>{const t=e.props["onUpdate:modelValue"]||!1;return q(t)?n=>fo(t,n):t},qu={deep:!0,created(e,{value:t,modifiers:{number:n}},o){const r=So(t);Cl(e,"change",()=>{const s=Array.prototype.filter.call(e.options,i=>i.selected).map(i=>n?Si(To(i)):To(i));e._assign(e.multiple?r?new Set(s):s:s[0])}),e._assign=js(o)},mounted(e,{value:t}){Vs(e,t)},beforeUpdate(e,t,n){e._assign=js(n)},updated(e,{value:t}){Vs(e,t)}};function Vs(e,t){const n=e.multiple;if(!(n&&!q(t)&&!So(t))){for(let o=0,r=e.options.length;o-1:s.selected=t.has(i);else if(Ao(To(s),t)){e.selectedIndex!==o&&(e.selectedIndex=o);return}}!n&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function To(e){return"_value"in e?e._value:e.value}const Wu={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},Ju=(e,t)=>n=>{if(!("key"in n))return;const o=Yt(n.key);if(t.some(r=>r===o||Wu[r]===o))return e(n)},Lo={beforeMount(e,{value:t},{transition:n}){e._vod=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):En(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:o}){!t!=!n&&(o?t?(o.beforeEnter(e),En(e,!0),o.enter(e)):o.leave(e,()=>{En(e,!1)}):En(e,t))},beforeUnmount(e,{value:t}){En(e,t)}};function En(e,t){e.style.display=t?e._vod:"none"}const Ll=Se({patchProp:Bu},xu);let kn,Fs=!1;function Qu(){return kn||(kn=su(Ll))}function Yu(){return kn=Fs?kn:iu(Ll),Fs=!0,kn}const Gu=(...e)=>{const t=Qu().createApp(...e),{mount:n}=t;return t.mount=o=>{const r=xl(o);if(!r)return;const s=t._component;!re(s)&&!s.render&&!s.template&&(s.template=r.innerHTML),r.innerHTML="";const i=n(r,!1,r instanceof SVGElement);return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),i},t},Zu=(...e)=>{const t=Yu().createApp(...e),{mount:n}=t;return t.mount=o=>{const r=xl(o);if(r)return n(r,!0,r instanceof SVGElement)},t};function xl(e){return me(e)?document.querySelector(e):e}const Xu={"v-8daa1a0e":()=>O(()=>import("./index.html-692e45cb.js"),[]).then(({data:e})=>e),"v-14ac19b5":()=>O(()=>import("./index.html-7d2ae716.js"),[]).then(({data:e})=>e),"v-e5065f60":()=>O(()=>import("./index.html-7dc4890d.js"),[]).then(({data:e})=>e),"v-eb8745ce":()=>O(()=>import("./index.html-15fde261.js"),[]).then(({data:e})=>e),"v-828730c2":()=>O(()=>import("./index.html-93ff6c80.js"),[]).then(({data:e})=>e),"v-563ef996":()=>O(()=>import("./index.html-09f857ca.js"),[]).then(({data:e})=>e),"v-04b96fc8":()=>O(()=>import("./index.html-70c61dd5.js"),[]).then(({data:e})=>e),"v-fe45a0b8":()=>O(()=>import("./index.html-a7337040.js"),[]).then(({data:e})=>e),"v-7ed00a2a":()=>O(()=>import("./index.html-f8b7a659.js"),[]).then(({data:e})=>e),"v-326db923":()=>O(()=>import("./index.html-ced62edd.js"),[]).then(({data:e})=>e),"v-587df7db":()=>O(()=>import("./index.html-88fcd172.js"),[]).then(({data:e})=>e),"v-0b2aad78":()=>O(()=>import("./index.html-a2d4ab5c.js"),[]).then(({data:e})=>e),"v-23f62a3a":()=>O(()=>import("./index.html-6b858d80.js"),[]).then(({data:e})=>e),"v-1963670f":()=>O(()=>import("./index.html-549ff1f9.js"),[]).then(({data:e})=>e),"v-4cf2565c":()=>O(()=>import("./index.html-6e94f439.js"),[]).then(({data:e})=>e),"v-17bdcfe6":()=>O(()=>import("./index.html-c46be575.js"),[]).then(({data:e})=>e),"v-3481b484":()=>O(()=>import("./index.html-a70b6cf1.js"),[]).then(({data:e})=>e),"v-b6a1f058":()=>O(()=>import("./index.html-a9eb55a1.js"),[]).then(({data:e})=>e),"v-94b7dab4":()=>O(()=>import("./index.html-f9768eb1.js"),[]).then(({data:e})=>e),"v-391365f4":()=>O(()=>import("./index.html-6bb621c5.js"),[]).then(({data:e})=>e),"v-32b5e2dd":()=>O(()=>import("./index.html-bc520f9b.js"),[]).then(({data:e})=>e),"v-02a19d2b":()=>O(()=>import("./index.html-42466a4a.js"),[]).then(({data:e})=>e),"v-26e624c6":()=>O(()=>import("./index.html-3bc7b1c6.js"),[]).then(({data:e})=>e),"v-6165843c":()=>O(()=>import("./index.html-caa38aac.js"),[]).then(({data:e})=>e),"v-22e054a1":()=>O(()=>import("./index.html-34c15120.js"),[]).then(({data:e})=>e),"v-147825fb":()=>O(()=>import("./index.html-2fb78bd6.js"),[]).then(({data:e})=>e),"v-255f131a":()=>O(()=>import("./index.html-963a4ecf.js"),[]).then(({data:e})=>e),"v-6768263b":()=>O(()=>import("./index.html-1d87c569.js"),[]).then(({data:e})=>e),"v-6a4de75f":()=>O(()=>import("./index.html-a0e9a4ad.js"),[]).then(({data:e})=>e),"v-a20dfce8":()=>O(()=>import("./index.html-76d114a5.js"),[]).then(({data:e})=>e),"v-9a955b1e":()=>O(()=>import("./index.html-e1863c04.js"),[]).then(({data:e})=>e),"v-ca3407c4":()=>O(()=>import("./index.html-fae03536.js"),[]).then(({data:e})=>e),"v-3cf0fa66":()=>O(()=>import("./index.html-6ca7c7e1.js"),[]).then(({data:e})=>e),"v-b09aba04":()=>O(()=>import("./index.html-568e376b.js"),[]).then(({data:e})=>e),"v-b341ee2c":()=>O(()=>import("./index.html-ccd9b97c.js"),[]).then(({data:e})=>e),"v-0c9564ec":()=>O(()=>import("./index.html-a48fd168.js"),[]).then(({data:e})=>e),"v-36e2ae9d":()=>O(()=>import("./index.html-c67caa0a.js"),[]).then(({data:e})=>e),"v-5b6d532c":()=>O(()=>import("./index.html-9094b141.js"),[]).then(({data:e})=>e),"v-9712b6e4":()=>O(()=>import("./index.html-602c2540.js"),[]).then(({data:e})=>e),"v-bdba93e6":()=>O(()=>import("./filterQueryParam.html-d51cd871.js"),[]).then(({data:e})=>e),"v-31ddcbc0":()=>O(()=>import("./filterQueryParamCode.html-293978be.js"),[]).then(({data:e})=>e),"v-0e79de1b":()=>O(()=>import("./filterQueryParamExample.html-c90c301f.js"),[]).then(({data:e})=>e),"v-9a1a7988":()=>O(()=>import("./jmespathFilter.html-7c48b0c1.js"),[]).then(({data:e})=>e),"v-4395d380":()=>O(()=>import("./throttle.html-4a67c62e.js"),[]).then(({data:e})=>e),"v-17bf8008":()=>O(()=>import("./whereQueryParam.html-10f3d62d.js"),[]).then(({data:e})=>e),"v-0b4a148f":()=>O(()=>import("./whereQueryParamCode.html-33176c09.js"),[]).then(({data:e})=>e),"v-5119194c":()=>O(()=>import("./whereQueryParamExample.html-68248e1c.js"),[]).then(({data:e})=>e),"v-3706649a":()=>O(()=>import("./404.html-60b35caa.js"),[]).then(({data:e})=>e)},ef=JSON.parse('{"base":"/NotifyBC/","lang":"en-US","title":"NotifyBC","description":"A versatile notification API server","head":[["meta",{"name":"theme-color","content":"#3eaf7c"}],["meta",{"name":"apple-mobile-web-app-capable","content":"yes"}],["meta",{"name":"apple-mobile-web-app-status-bar-style","content":"black"}],["link",{"rel":"icon","type":"image/x-icon","href":"/NotifyBC/favicon.ico"}],["link",{"rel":"stylesheet","href":"https://fonts.googleapis.com/icon?family=Material+Icons"}]],"locales":{}}');var tf=([e,t,n])=>e==="meta"&&t.name?`${e}.${t.name}`:["title","base"].includes(e)?e:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,t,n]),nf=e=>{const t=new Set,n=[];return e.forEach(o=>{const r=tf(o);t.has(r)||(t.add(r),n.push(o))}),n},Yn=e=>/^(https?:)?\/\//.test(e),of=e=>/^mailto:/.test(e),rf=e=>/^tel:/.test(e),Vr=e=>Object.prototype.toString.call(e)==="[object Object]",Pl=e=>e[e.length-1]==="/"?e.slice(0,-1):e,Ol=e=>e[0]==="/"?e.slice(1):e,Sl=(e,t)=>{const n=Object.keys(e).sort((o,r)=>{const s=r.split("/").length-o.split("/").length;return s!==0?s:r.length-o.length});for(const o of n)if(t.startsWith(o))return o;return"/"},zs=(e,t="/")=>{const n=e.replace(/^(https?:)?\/\/[^/]*/,"");return n.startsWith(t)?`/${n.slice(t.length)}`:n};const kl={"v-8daa1a0e":te(()=>O(()=>import("./index.html-9cf75b13.js"),[])),"v-14ac19b5":te(()=>O(()=>import("./index.html-698e3d15.js"),[])),"v-e5065f60":te(()=>O(()=>import("./index.html-f2c18423.js"),[])),"v-eb8745ce":te(()=>O(()=>import("./index.html-29e78a52.js"),[])),"v-828730c2":te(()=>O(()=>import("./index.html-d4f673ca.js"),[])),"v-563ef996":te(()=>O(()=>import("./index.html-e9ca5832.js"),[])),"v-04b96fc8":te(()=>O(()=>import("./index.html-59f066f2.js"),[])),"v-fe45a0b8":te(()=>O(()=>import("./index.html-6906b24f.js"),[])),"v-7ed00a2a":te(()=>O(()=>import("./index.html-287c16ec.js"),[])),"v-326db923":te(()=>O(()=>import("./index.html-b9f3ea8d.js"),[])),"v-587df7db":te(()=>O(()=>import("./index.html-c60b591d.js"),[])),"v-0b2aad78":te(()=>O(()=>import("./index.html-9fc267c0.js"),[])),"v-23f62a3a":te(()=>O(()=>import("./index.html-00524c3e.js"),[])),"v-1963670f":te(()=>O(()=>import("./index.html-599e9037.js"),[])),"v-4cf2565c":te(()=>O(()=>import("./index.html-4a2d667d.js"),[])),"v-17bdcfe6":te(()=>O(()=>import("./index.html-15bf84b8.js"),[])),"v-3481b484":te(()=>O(()=>import("./index.html-e0ff510f.js"),[])),"v-b6a1f058":te(()=>O(()=>import("./index.html-f201ed52.js"),[])),"v-94b7dab4":te(()=>O(()=>import("./index.html-73d191ff.js"),[])),"v-391365f4":te(()=>O(()=>import("./index.html-ea71ab91.js"),[])),"v-32b5e2dd":te(()=>O(()=>import("./index.html-1a8bbf38.js"),[])),"v-02a19d2b":te(()=>O(()=>import("./index.html-4b695468.js"),[])),"v-26e624c6":te(()=>O(()=>import("./index.html-1bba510e.js"),[])),"v-6165843c":te(()=>O(()=>import("./index.html-45a39212.js"),[])),"v-22e054a1":te(()=>O(()=>import("./index.html-566c81a6.js"),[])),"v-147825fb":te(()=>O(()=>import("./index.html-db6fe449.js"),[])),"v-255f131a":te(()=>O(()=>import("./index.html-219bb653.js"),[])),"v-6768263b":te(()=>O(()=>import("./index.html-3031d490.js"),[])),"v-6a4de75f":te(()=>O(()=>import("./index.html-255cab24.js"),[])),"v-a20dfce8":te(()=>O(()=>import("./index.html-aaab02f3.js"),[])),"v-9a955b1e":te(()=>O(()=>import("./index.html-a85cf3ae.js"),[])),"v-ca3407c4":te(()=>O(()=>import("./index.html-bf54ea9c.js"),[])),"v-3cf0fa66":te(()=>O(()=>import("./index.html-200962d8.js"),[])),"v-b09aba04":te(()=>O(()=>import("./index.html-421ecf2b.js"),[])),"v-b341ee2c":te(()=>O(()=>import("./index.html-a40f24b5.js"),[])),"v-0c9564ec":te(()=>O(()=>import("./index.html-64244dc0.js"),[])),"v-36e2ae9d":te(()=>O(()=>import("./index.html-aad2a358.js"),[])),"v-5b6d532c":te(()=>O(()=>import("./index.html-d1f8fc1e.js"),[])),"v-9712b6e4":te(()=>O(()=>import("./index.html-c8dd00de.js"),[])),"v-bdba93e6":te(()=>O(()=>import("./filterQueryParam.html-be5e74b7.js"),[])),"v-31ddcbc0":te(()=>O(()=>import("./filterQueryParamCode.html-5188a76f.js"),[])),"v-0e79de1b":te(()=>O(()=>import("./filterQueryParamExample.html-30c0817c.js"),[])),"v-9a1a7988":te(()=>O(()=>import("./jmespathFilter.html-f6652c45.js"),[])),"v-4395d380":te(()=>O(()=>import("./throttle.html-2d038ede.js"),[])),"v-17bf8008":te(()=>O(()=>import("./whereQueryParam.html-6115caa0.js"),[])),"v-0b4a148f":te(()=>O(()=>import("./whereQueryParamCode.html-ec3065e2.js"),[])),"v-5119194c":te(()=>O(()=>import("./whereQueryParamExample.html-8a4f7134.js"),[])),"v-3706649a":te(()=>O(()=>import("./404.html-0303a6b9.js"),[]))};var sf=Symbol(""),lf=be(Xu),Il=_n({key:"",path:"",title:"",lang:"",frontmatter:{},headers:[]}),It=be(Il),$t=()=>It,Al=Symbol(""),vt=()=>{const e=Oe(Al);if(!e)throw new Error("usePageFrontmatter() is called without provider.");return e},Rl=Symbol(""),af=()=>{const e=Oe(Rl);if(!e)throw new Error("usePageHead() is called without provider.");return e},cf=Symbol(""),Dl=Symbol(""),$l=()=>{const e=Oe(Dl);if(!e)throw new Error("usePageLang() is called without provider.");return e},Ml=Symbol(""),uf=()=>{const e=Oe(Ml);if(!e)throw new Error("usePageLayout() is called without provider.");return e},Fr=Symbol(""),Gn=()=>{const e=Oe(Fr);if(!e)throw new Error("useRouteLocale() is called without provider.");return e},on=be(ef),Nl=()=>on,Hl=Symbol(""),zr=()=>{const e=Oe(Hl);if(!e)throw new Error("useSiteLocaleData() is called without provider.");return e},ff=Symbol(""),df="Layout",pf="NotFound",pt=Kn({resolveLayouts:e=>e.reduce((t,n)=>({...t,...n.layouts}),{}),resolvePageData:async e=>{const t=lf.value[e];return await(t==null?void 0:t())??Il},resolvePageFrontmatter:e=>e.frontmatter,resolvePageHead:(e,t,n)=>{const o=me(t.description)?t.description:n.description,r=[...q(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:o}]];return nf(r)},resolvePageHeadTitle:(e,t)=>[e.title,t.title].filter(n=>!!n).join(" | "),resolvePageLang:(e,t)=>e.lang||t.lang||"en-US",resolvePageLayout:(e,t)=>{let n;if(e.path){const o=e.frontmatter.layout;me(o)?n=o:n=df}else n=pf;return t[n]},resolveRouteLocale:(e,t)=>Sl(e,t),resolveSiteLocaleData:(e,t)=>({...e,...e.locales[t]})}),Ur=fe({name:"ClientOnly",setup(e,t){const n=be(!1);return We(()=>{n.value=!0}),()=>{var o,r;return n.value?(r=(o=t.slots).default)==null?void 0:r.call(o):null}}}),hf=fe({name:"Content",props:{pageKey:{type:String,required:!1,default:""}},setup(e){const t=$t(),n=z(()=>kl[e.pageKey||t.value.key]);return()=>n.value?ge(n.value):ge("div","404 Not Found")}}),Ct=(e={})=>e,Kr=e=>Yn(e)?e:`/NotifyBC/${Ol(e)}`;function qr(e,t,n){var o,r,s;t===void 0&&(t=50),n===void 0&&(n={});var i=(o=n.isImmediate)!=null&&o,l=(r=n.callback)!=null&&r,a=n.maxWait,c=Date.now(),u=[];function f(){if(a!==void 0){var v=Date.now()-c;if(v+t>=a)return a-v}return t}var p=function(){var v=[].slice.call(arguments),y=this;return new Promise(function(w,x){var g=i&&s===void 0;if(s!==void 0&&clearTimeout(s),s=setTimeout(function(){if(s=void 0,c=Date.now(),!i){var I=e.apply(y,v);l&&l(I),u.forEach(function(k){return(0,k.resolve)(I)}),u=[]}},f()),g){var b=e.apply(y,v);return l&&l(b),w(b)}u.push({resolve:w,reject:x})})};return p.cancel=function(v){s!==void 0&&clearTimeout(s),u.forEach(function(y){return(0,y.reject)(v)}),u=[]},p}/*! * vue-router v4.2.4 * (c) 2023 Eduardo San Martin Morote * @license MIT @@ -7,4 +7,4 @@ Expects a CSS selector, a Node element, a NodeList or an array. See: https://github.com/francoischalifour/medium-zoom`)}},Id=function(t){var n=document.createElement("div");return n.classList.add("medium-zoom-overlay"),n.style.background=t,n},Ad=function(t){var n=t.getBoundingClientRect(),o=n.top,r=n.left,s=n.width,i=n.height,l=t.cloneNode(),a=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,c=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;return l.removeAttribute("id"),l.style.position="absolute",l.style.top=o+a+"px",l.style.left=r+c+"px",l.style.width=s+"px",l.style.height=i+"px",l.style.transform="",l},tn=function(t,n){var o=Vt({bubbles:!1,cancelable:!1,detail:void 0},n);if(typeof window.CustomEvent=="function")return new CustomEvent(t,o);var r=document.createEvent("CustomEvent");return r.initCustomEvent(t,o.bubbles,o.cancelable,o.detail),r},Rd=function e(t){var n=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},o=window.Promise||function(L){function R(){}L(R,R)},r=function(L){var R=L.target;if(R===F){y();return}I.indexOf(R)!==-1&&w({target:R})},s=function(){if(!(W||!m.original)){var L=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(ee-L)>N.scrollOffset&&setTimeout(y,150)}},i=function(L){var R=L.key||L.keyCode;(R==="Escape"||R==="Esc"||R===27)&&y()},l=function(){var L=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},R=L;if(L.background&&(F.style.background=L.background),L.container&&L.container instanceof Object&&(R.container=Vt({},N.container,L.container)),L.template){var D=ho(L.template)?L.template:document.querySelector(L.template);R.template=D}return N=Vt({},N,R),I.forEach(function(le){le.dispatchEvent(tn("medium-zoom:update",{detail:{zoom:B}}))}),B},a=function(){var L=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};return e(Vt({},N,L))},c=function(){for(var L=arguments.length,R=Array(L),D=0;D0?R.reduce(function(U,se){return[].concat(U,ci(se))},[]):I;return le.forEach(function(U){U.classList.remove("medium-zoom-image"),U.dispatchEvent(tn("medium-zoom:detach",{detail:{zoom:B}}))}),I=I.filter(function(U){return le.indexOf(U)===-1}),B},f=function(L,R){var D=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return I.forEach(function(le){le.addEventListener("medium-zoom:"+L,R,D)}),k.push({type:"medium-zoom:"+L,listener:R,options:D}),B},p=function(L,R){var D=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return I.forEach(function(le){le.removeEventListener("medium-zoom:"+L,R,D)}),k=k.filter(function(le){return!(le.type==="medium-zoom:"+L&&le.listener.toString()===R.toString())}),B},v=function(){var L=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},R=L.target,D=function(){var U={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},se=void 0,ie=void 0;if(N.container)if(N.container instanceof Object)U=Vt({},U,N.container),se=U.width-U.left-U.right-N.margin*2,ie=U.height-U.top-U.bottom-N.margin*2;else{var He=ho(N.container)?N.container:document.querySelector(N.container),$e=He.getBoundingClientRect(),Ue=$e.width,Be=$e.height,Tt=$e.left,Lt=$e.top;U=Vt({},U,{width:Ue,height:Be,left:Tt,top:Lt})}se=se||U.width-N.margin*2,ie=ie||U.height-N.margin*2;var lt=m.zoomedHd||m.original,De=ai(lt)?se:lt.naturalWidth||se,C=ai(lt)?ie:lt.naturalHeight||ie,V=lt.getBoundingClientRect(),$=V.top,J=V.left,ue=V.width,d=V.height,h=Math.min(Math.max(ue,De),se)/ue,_=Math.min(Math.max(d,C),ie)/d,E=Math.min(h,_),T=(-J+(se-ue)/2+N.margin+U.left)/E,P=(-$+(ie-d)/2+N.margin+U.top)/E,j="scale("+E+") translate3d("+T+"px, "+P+"px, 0)";m.zoomed.style.transform=j,m.zoomedHd&&(m.zoomedHd.style.transform=j)};return new o(function(le){if(R&&I.indexOf(R)===-1){le(B);return}var U=function Ue(){W=!1,m.zoomed.removeEventListener("transitionend",Ue),m.original.dispatchEvent(tn("medium-zoom:opened",{detail:{zoom:B}})),le(B)};if(m.zoomed){le(B);return}if(R)m.original=R;else if(I.length>0){var se=I;m.original=se[0]}else{le(B);return}if(m.original.dispatchEvent(tn("medium-zoom:open",{detail:{zoom:B}})),ee=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,W=!0,m.zoomed=Ad(m.original),document.body.appendChild(F),N.template){var ie=ho(N.template)?N.template:document.querySelector(N.template);m.template=document.createElement("div"),m.template.appendChild(ie.content.cloneNode(!0)),document.body.appendChild(m.template)}if(m.original.parentElement&&m.original.parentElement.tagName==="PICTURE"&&m.original.currentSrc&&(m.zoomed.src=m.original.currentSrc),document.body.appendChild(m.zoomed),window.requestAnimationFrame(function(){document.body.classList.add("medium-zoom--opened")}),m.original.classList.add("medium-zoom-image--hidden"),m.zoomed.classList.add("medium-zoom-image--opened"),m.zoomed.addEventListener("click",y),m.zoomed.addEventListener("transitionend",U),m.original.getAttribute("data-zoom-src")){m.zoomedHd=m.zoomed.cloneNode(),m.zoomedHd.removeAttribute("srcset"),m.zoomedHd.removeAttribute("sizes"),m.zoomedHd.removeAttribute("loading"),m.zoomedHd.src=m.zoomed.getAttribute("data-zoom-src"),m.zoomedHd.onerror=function(){clearInterval(He),console.warn("Unable to reach the zoom image target "+m.zoomedHd.src),m.zoomedHd=null,D()};var He=setInterval(function(){m.zoomedHd.complete&&(clearInterval(He),m.zoomedHd.classList.add("medium-zoom-image--opened"),m.zoomedHd.addEventListener("click",y),document.body.appendChild(m.zoomedHd),D())},10)}else if(m.original.hasAttribute("srcset")){m.zoomedHd=m.zoomed.cloneNode(),m.zoomedHd.removeAttribute("sizes"),m.zoomedHd.removeAttribute("loading");var $e=m.zoomedHd.addEventListener("load",function(){m.zoomedHd.removeEventListener("load",$e),m.zoomedHd.classList.add("medium-zoom-image--opened"),m.zoomedHd.addEventListener("click",y),document.body.appendChild(m.zoomedHd),D()})}else D()})},y=function(){return new o(function(L){if(W||!m.original){L(B);return}var R=function D(){m.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(m.zoomed),m.zoomedHd&&document.body.removeChild(m.zoomedHd),document.body.removeChild(F),m.zoomed.classList.remove("medium-zoom-image--opened"),m.template&&document.body.removeChild(m.template),W=!1,m.zoomed.removeEventListener("transitionend",D),m.original.dispatchEvent(tn("medium-zoom:closed",{detail:{zoom:B}})),m.original=null,m.zoomed=null,m.zoomedHd=null,m.template=null,L(B)};W=!0,document.body.classList.remove("medium-zoom--opened"),m.zoomed.style.transform="",m.zoomedHd&&(m.zoomedHd.style.transform=""),m.template&&(m.template.style.transition="opacity 150ms",m.template.style.opacity=0),m.original.dispatchEvent(tn("medium-zoom:close",{detail:{zoom:B}})),m.zoomed.addEventListener("transitionend",R)})},w=function(){var L=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},R=L.target;return m.original?y():v({target:R})},x=function(){return N},g=function(){return I},b=function(){return m.original},I=[],k=[],W=!1,ee=0,N=n,m={original:null,zoomed:null,zoomedHd:null,template:null};Object.prototype.toString.call(t)==="[object Object]"?N=t:(t||typeof t=="string")&&c(t),N=Vt({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},N);var F=Id(N.background);document.addEventListener("click",r),document.addEventListener("keyup",i),document.addEventListener("scroll",s),window.addEventListener("resize",y);var B={open:v,close:y,toggle:w,update:l,clone:a,attach:c,detach:u,on:f,off:p,getOptions:x,getImages:g,getZoomedImage:b};return B};function Dd(e,t){t===void 0&&(t={});var n=t.insertAt;if(!(!e||typeof document>"u")){var o=document.head||document.getElementsByTagName("head")[0],r=document.createElement("style");r.type="text/css",n==="top"&&o.firstChild?o.insertBefore(r,o.firstChild):o.appendChild(r),r.styleSheet?r.styleSheet.cssText=e:r.appendChild(document.createTextNode(e))}}var $d=".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}";Dd($d);const Md=Rd,Nd=Symbol("mediumZoom");const Hd=".theme-default-content > img, .theme-default-content :not(a) > img",Bd={},jd=300,Vd=Ct({enhance({app:e,router:t}){const n=Md(Bd);n.refresh=(o=Hd)=>{n.detach(),n.attach(o)},e.provide(Nd,n),t.afterEach(()=>{setTimeout(()=>n.refresh(),jd)})}});/** * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress * @license MIT - */const de={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
'},status:null,set:e=>{const t=de.isStarted();e=tr(e,de.settings.minimum,1),de.status=e===1?null:e;const n=de.render(!t),o=n.querySelector(de.settings.barSelector),r=de.settings.speed,s=de.settings.easing;return n.offsetWidth,Fd(i=>{lo(o,{transform:"translate3d("+ui(e)+"%,0,0)",transition:"all "+r+"ms "+s}),e===1?(lo(n,{transition:"none",opacity:"1"}),n.offsetWidth,setTimeout(function(){lo(n,{transition:"all "+r+"ms linear",opacity:"0"}),setTimeout(function(){de.remove(),i()},r)},r)):setTimeout(()=>i(),r)}),de},isStarted:()=>typeof de.status=="number",start:()=>{de.status||de.set(0);const e=()=>{setTimeout(()=>{de.status&&(de.trickle(),e())},de.settings.trickleSpeed)};return de.settings.trickle&&e(),de},done:e=>!e&&!de.status?de:de.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=de.status;return t?(typeof e!="number"&&(e=(1-t)*tr(Math.random()*t,.1,.95)),t=tr(t+e,0,.994),de.set(t)):de.start()},trickle:()=>de.inc(Math.random()*de.settings.trickleRate),render:e=>{if(de.isRendered())return document.getElementById("nprogress");fi(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=de.settings.template;const n=t.querySelector(de.settings.barSelector),o=e?"-100":ui(de.status||0),r=document.querySelector(de.settings.parent);return lo(n,{transition:"all 0 linear",transform:"translate3d("+o+"%,0,0)"}),r!==document.body&&fi(r,"nprogress-custom-parent"),r==null||r.appendChild(t),t},remove:()=>{di(document.documentElement,"nprogress-busy"),di(document.querySelector(de.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&zd(e)},isRendered:()=>!!document.getElementById("nprogress")},tr=(e,t,n)=>en?n:e,ui=e=>(-1+e)*100,Fd=function(){const e=[];function t(){const n=e.shift();n&&n(t)}return function(n){e.push(n),e.length===1&&t()}}(),lo=function(){const e=["Webkit","O","Moz","ms"],t={};function n(i){return i.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(l,a){return a.toUpperCase()})}function o(i){const l=document.body.style;if(i in l)return i;let a=e.length;const c=i.charAt(0).toUpperCase()+i.slice(1);let u;for(;a--;)if(u=e[a]+c,u in l)return u;return i}function r(i){return i=n(i),t[i]??(t[i]=o(i))}function s(i,l,a){l=r(l),i.style[l]=a}return function(i,l){for(const a in l){const c=l[a];c!==void 0&&Object.prototype.hasOwnProperty.call(l,a)&&s(i,a,c)}}}(),Yl=(e,t)=>(typeof e=="string"?e:Qr(e)).indexOf(" "+t+" ")>=0,fi=(e,t)=>{const n=Qr(e),o=n+t;Yl(n,t)||(e.className=o.substring(1))},di=(e,t)=>{const n=Qr(e);if(!Yl(e,t))return;const o=n.replace(" "+t+" "," ");e.className=o.substring(1,o.length-1)},Qr=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),zd=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)};const Ud=()=>{We(()=>{const e=Gt(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(n=>{t.has(n.path)||de.start()}),e.afterEach(n=>{t.add(n.path),de.done()})})},Kd=Ct({setup(){Ud()}}),qd=JSON.parse(`{"repo":"https://github.com/bcgov/notifybc","packageJson":{"name":"notify-bc","version":"5.1.2","dbSchemaVersion":"0.9.0","description":"A versatile notification API server","author":"f-w","private":true,"main":"dist/main.js","types":"dist/main.d.ts","engines":{"node":">=18"},"repository":{"type":"git","url":"https://github.com/bcgov/notifybc"},"license":"Apache-2.0","scripts":{"build":"nest build","build:client":"cd client && npm run build","build:docs":"cd docs && npm i && npm run build","postbuild":"npm run build:client","install:client":"cd client && npm i","install:docs":"cd docs && npm i","postinstall":"npm run install:client","format":"prettier --write \\"src/**/*.ts\\" \\"test/**/*.ts\\"","start":"nest start","start:dev":"nest start --watch","start:debug":"nest start --debug --watch","start:prod":"node dist/main","lint":"eslint \\"{src,apps,libs,test}/**/*.ts\\" --fix","test":"jest","test:watch":"jest --watch","test:cov":"jest --coverage","test:debug":"node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand","test:e2e":"jest --config ./test/jest-e2e.ts","test:e2e:cov":"jest --config ./test/jest-e2e.ts --coverage"},"dependencies":{"@nestjs/common":"^10.3.8","@nestjs/core":"^10.3.8","@nestjs/mongoose":"^10.0.6","@nestjs/platform-express":"^10.3.8","@nestjs/swagger":"^7.3.1","@nestjs/terminus":"^10.2.3","async":"^3.2.4","axios":"^1.6.8","bcryptjs":"^2.4.3","bottleneck":"^2.19.5","class-transformer":"^0.5.1","class-validator":"^0.14.0","compression":"^1.7.4","cron":"^3.1.5","crypto-random-string":"^3.3.0","ejs":"^3.1.9","feedparser":"^2.2.10","helmet":"^7.0.0","ioredis":"^5.3.2","ip-range-check":"^0.2.0","jmespath":"f-w/jmespath.js#semver:^1.0","js-base64":"^3.7.5","jsonwebtoken":"^9.0.2","lodash":"^4.17.21","mailparser":"^3.6.5","mongodb-memory-server":"^9.2.0","mongoose":"^7.4.5","morgan":"^1.10.0","nodemailer":"^6.9.5","nodemailer-direct-transport":"^3.3.2","pluralize":"^8.0.0","randexp":"^0.5.3","reflect-metadata":"^0.1.13","rxjs":"^7.8.1","semver":"^7.5.4","smtp-server":"^3.13.0","twilio":"^3.7.0","underscore.string":"^3.3.6"},"devDependencies":{"@nestjs/cli":"^10.3.2","@nestjs/schematics":"^10.1.1","@nestjs/testing":"^10.3.8","@types/bcryptjs":"^2.4.3","@types/express":"^4.17.17","@types/jest":"^29.5.2","@types/lodash":"^4.14.197","@types/node":"^20.3.1","@types/supertest":"^2.0.12","@typescript-eslint/eslint-plugin":"^5.59.11","@typescript-eslint/parser":"^5.59.11","commander":"^11.1.0","csvtojson":"^2.0.10","eslint":"^8.42.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^4.2.1","jest":"^29.7.0","prettier":"^2.8.8","source-map-support":"^0.5.21","supertest":"^6.3.3","ts-jest":"^29.1.0","ts-node":"^10.9.1","typescript":"^5.1.3"},"optionalDependencies":{"redis-memory-server":"^0.10.0"},"jest":{"moduleFileExtensions":["js","json","ts"],"rootDir":"src","testRegex":".*\\\\.spec\\\\.ts$","transform":{"^.+\\\\.(t|j)s$":"ts-jest"},"collectCoverageFrom":["**/*.(t|j)s"],"coverageDirectory":"../coverage","testEnvironment":"node"}},"logo":"/img/logo.svg","docsDir":"","editLink":false,"contributors":false,"lastUpdated":false,"navbar":[{"text":"Home","link":"/"},{"text":"Docs","link":"/docs/"},{"text":"Help","link":"/help/"}],"sidebarDepth":1,"sidebar":[{"text":"Getting Started","children":["/docs/","/docs/overview/","/docs/quickstart/","/docs/installation/","/docs/web-console/","/docs/what's-new/"]},{"text":"Configuration","children":["/docs/config-overview/","/docs/config-database/","/docs/config-adminIpList/","/docs/config-reverseProxyIpLists/","/docs/config-httpHost/","/docs/config-internalHttpHost/","/docs/config-email/","/docs/config-sms/","/docs/config-subscription/","/docs/config-notification/","/docs/config-nodeRoles/","/docs/config-cronJobs/","/docs/config-rsaKeys/","/docs/config-workerProcessCount/","/docs/config-middleware/","/docs/config-oidc/","/docs/config-certificates/"]},{"text":"API","collapsed":false,"children":["/docs/api-overview/","/docs/api-subscription/","/docs/api-notification/","/docs/api-config/","/docs/api-administrator/","/docs/api-bounce/"]},{"text":"Miscellaneous","children":["/docs/health-check/","/docs/memory-dump/","/docs/benchmarks/","/docs/bulk-import/","/docs/developer-notes/","/docs/upgrade/"]},{"text":"Meta","children":["/docs/conduct/","/docs/acknowledgments/"]}],"locales":{"/":{"selectLanguageName":"English"}},"colorMode":"auto","colorModeSwitch":true,"selectLanguageText":"Languages","selectLanguageAriaLabel":"Select language","editLinkText":"Edit this page","lastUpdatedText":"Last Updated","contributorsText":"Contributors","notFound":["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."],"backToHome":"Take me home","openInNewWindow":"open in new window","toggleColorMode":"toggle color mode","toggleSidebar":"toggle sidebar"}`),Wd=be(qd),Gl=()=>Wd,Zl=Symbol(""),Jd=()=>{const e=Oe(Zl);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},Qd=(e,t)=>{const{locales:n,...o}=e;return{...o,...n==null?void 0:n[t]}},Yd=Ct({enhance({app:e}){const t=Gl(),n=e._context.provides[Fr],o=z(()=>Qd(t.value,n.value));e.provide(Zl,o),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return o.value}}})}}),Gd=fe({__name:"Badge",props:{type:{type:String,required:!1,default:"tip"},text:{type:String,required:!1,default:""},vertical:{type:String,required:!1,default:void 0}},setup(e){return(t,n)=>(H(),Q("span",{class:ze(["badge",e.type]),style:Qt({verticalAlign:e.vertical})},[Ce(t.$slots,"default",{},()=>[Et(Ie(e.text),1)])],6))}}),Le=(e,t)=>{const n=e.__vccOpts||e;for(const[o,r]of t)n[o]=r;return n},Zd=Le(Gd,[["__file","Badge.vue"]]),Xd=fe({name:"CodeGroup",slots:Object,setup(e,{slots:t}){const n=be(-1),o=be([]),r=(l=n.value)=>{l{l>0?n.value=l-1:n.value=o.value.length-1,o.value[n.value].focus()},i=(l,a)=>{l.key===" "||l.key==="Enter"?(l.preventDefault(),n.value=a):l.key==="ArrowRight"?(l.preventDefault(),r(a)):l.key==="ArrowLeft"&&(l.preventDefault(),s(a))};return()=>{var a;const l=(((a=t.default)==null?void 0:a.call(t))||[]).filter(c=>c.type.name==="CodeGroupItem").map(c=>(c.props===null&&(c.props={}),c));return l.length===0?null:(n.value<0||n.value>l.length-1?(n.value=l.findIndex(c=>c.props.active===""||c.props.active===!0),n.value===-1&&(n.value=0)):l.forEach((c,u)=>{c.props.active=u===n.value}),ge("div",{class:"code-group"},[ge("div",{class:"code-group__nav"},ge("ul",{class:"code-group__ul"},l.map((c,u)=>{const f=u===n.value;return ge("li",{class:"code-group__li"},ge("button",{ref:p=>{p&&(o.value[u]=p)},class:{"code-group__nav-tab":!0,"code-group__nav-tab-active":f},ariaPressed:f,ariaExpanded:f,onClick:()=>n.value=u,onKeydown:p=>i(p,u)},c.props.title))}))),l]))}}}),ep=["aria-selected"],tp=fe({name:"CodeGroupItem"}),np=fe({...tp,props:{title:{type:String,required:!0},active:{type:Boolean,required:!1,default:!1}},setup(e){return(t,n)=>(H(),Q("div",{class:ze(["code-group-item",{"code-group-item__active":e.active}]),"aria-selected":e.active},[Ce(t.$slots,"default")],10,ep))}}),op=Le(np,[["__file","CodeGroupItem.vue"]]);var rp=Object.defineProperty,sp=Object.defineProperties,ip=Object.getOwnPropertyDescriptors,pi=Object.getOwnPropertySymbols,lp=Object.prototype.hasOwnProperty,ap=Object.prototype.propertyIsEnumerable,hi=(e,t,n)=>t in e?rp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,cp=(e,t)=>{for(var n in t||(t={}))lp.call(t,n)&&hi(e,n,t[n]);if(pi)for(var n of pi(t))ap.call(t,n)&&hi(e,n,t[n]);return e},up=(e,t)=>sp(e,ip(t));function mi(e,t){var n;const o=Rr();return el(()=>{o.value=e()},up(cp({},t),{flush:(n=t==null?void 0:t.flush)!=null?n:"sync"})),_n(o)}function Xl(e){return Ai()?(Da(e),!0):!1}function Vn(e){return typeof e=="function"?e():X(e)}const fp=typeof window<"u",ea=()=>{};function dp(e,t){function n(...o){return new Promise((r,s)=>{Promise.resolve(e(()=>t.apply(this,o),{fn:t,thisArg:this,args:o})).then(r).catch(s)})}return n}const ta=e=>e();function pp(e=ta){const t=be(!0);function n(){t.value=!1}function o(){t.value=!0}const r=(...s)=>{t.value&&e(...s)};return{isActive:_n(t),pause:n,resume:o,eventFilter:r}}function hp(...e){if(e.length!==1)return hc(...e);const t=e[0];return typeof t=="function"?_n(fc(()=>({get:t,set:ea}))):be(t)}function mp(e=!1,t={}){const{truthyValue:n=!0,falsyValue:o=!1}=t,r=Ae(e),s=be(e);function i(l){if(arguments.length)return s.value=l,s.value;{const a=Vn(n);return s.value=s.value===a?Vn(o):a,s.value}}return r?i:[s,i]}var vi=Object.getOwnPropertySymbols,vp=Object.prototype.hasOwnProperty,gp=Object.prototype.propertyIsEnumerable,_p=(e,t)=>{var n={};for(var o in e)vp.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(e!=null&&vi)for(var o of vi(e))t.indexOf(o)<0&&gp.call(e,o)&&(n[o]=e[o]);return n};function bp(e,t,n={}){const o=n,{eventFilter:r=ta}=o,s=_p(o,["eventFilter"]);return et(e,dp(r,t),s)}var yp=Object.defineProperty,Ep=Object.defineProperties,wp=Object.getOwnPropertyDescriptors,Po=Object.getOwnPropertySymbols,na=Object.prototype.hasOwnProperty,oa=Object.prototype.propertyIsEnumerable,gi=(e,t,n)=>t in e?yp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Cp=(e,t)=>{for(var n in t||(t={}))na.call(t,n)&&gi(e,n,t[n]);if(Po)for(var n of Po(t))oa.call(t,n)&&gi(e,n,t[n]);return e},Tp=(e,t)=>Ep(e,wp(t)),Lp=(e,t)=>{var n={};for(var o in e)na.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(e!=null&&Po)for(var o of Po(e))t.indexOf(o)<0&&oa.call(e,o)&&(n[o]=e[o]);return n};function xp(e,t,n={}){const o=n,{eventFilter:r}=o,s=Lp(o,["eventFilter"]),{eventFilter:i,pause:l,resume:a,isActive:c}=pp(r);return{stop:bp(e,t,Tp(Cp({},s),{eventFilter:i})),pause:l,resume:a,isActive:c}}function Pp(e){var t;const n=Vn(e);return(t=n==null?void 0:n.$el)!=null?t:n}const Oo=fp?window:void 0;function br(...e){let t,n,o,r;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,o,r]=e,t=Oo):[t,n,o,r]=e,!t)return ea;Array.isArray(n)||(n=[n]),Array.isArray(o)||(o=[o]);const s=[],i=()=>{s.forEach(u=>u()),s.length=0},l=(u,f,p,v)=>(u.addEventListener(f,p,v),()=>u.removeEventListener(f,p,v)),a=et(()=>[Pp(t),Vn(r)],([u,f])=>{i(),u&&s.push(...n.flatMap(p=>o.map(v=>l(u,p,v,f))))},{immediate:!0,flush:"post"}),c=()=>{a(),i()};return Xl(c),c}function Op(){const e=be(!1);return yl()&&We(()=>{e.value=!0}),e}function Sp(e){const t=Op();return z(()=>(t.value,!!e()))}function kp(e,t={}){const{window:n=Oo}=t,o=Sp(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let r;const s=be(!1),i=()=>{r&&("removeEventListener"in r?r.removeEventListener("change",l):r.removeListener(l))},l=()=>{o.value&&(i(),r=n.matchMedia(hp(e).value),s.value=!!(r!=null&&r.matches),r&&("addEventListener"in r?r.addEventListener("change",l):r.addListener(l)))};return el(l),Xl(()=>i()),s}const ao=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},co="__vueuse_ssr_handlers__",Ip=Ap();function Ap(){return co in ao||(ao[co]=ao[co]||{}),ao[co]}function Rp(e,t){return Ip[e]||t}function Dp(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}var $p=Object.defineProperty,_i=Object.getOwnPropertySymbols,Mp=Object.prototype.hasOwnProperty,Np=Object.prototype.propertyIsEnumerable,bi=(e,t,n)=>t in e?$p(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,yi=(e,t)=>{for(var n in t||(t={}))Mp.call(t,n)&&bi(e,n,t[n]);if(_i)for(var n of _i(t))Np.call(t,n)&&bi(e,n,t[n]);return e};const Hp={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},Ei="vueuse-storage";function Bp(e,t,n,o={}){var r;const{flush:s="pre",deep:i=!0,listenToStorageChanges:l=!0,writeDefaults:a=!0,mergeDefaults:c=!1,shallow:u,window:f=Oo,eventFilter:p,onError:v=m=>{console.error(m)}}=o,y=(u?Rr:be)(t);if(!n)try{n=Rp("getDefaultStorage",()=>{var m;return(m=Oo)==null?void 0:m.localStorage})()}catch(m){v(m)}if(!n)return y;const w=Vn(t),x=Dp(w),g=(r=o.serializer)!=null?r:Hp[x],{pause:b,resume:I}=xp(y,()=>k(y.value),{flush:s,deep:i,eventFilter:p});return f&&l&&(br(f,"storage",N),br(f,Ei,ee)),N(),y;function k(m){try{if(m==null)n.removeItem(e);else{const F=g.write(m),B=n.getItem(e);B!==F&&(n.setItem(e,F),f&&f.dispatchEvent(new CustomEvent(Ei,{detail:{key:e,oldValue:B,newValue:F,storageArea:n}})))}}catch(F){v(F)}}function W(m){const F=m?m.newValue:n.getItem(e);if(F==null)return a&&w!==null&&n.setItem(e,g.write(w)),w;if(!m&&c){const B=g.read(F);return typeof c=="function"?c(B,w):x==="object"&&!Array.isArray(B)?yi(yi({},w),B):B}else return typeof F!="string"?F:g.read(F)}function ee(m){N(m.detail)}function N(m){if(!(m&&m.storageArea!==n)){if(m&&m.key==null){y.value=w;return}if(!(m&&m.key!==e)){b();try{y.value=W(m)}catch(F){v(F)}finally{m?Do(I):I()}}}}}function jp(e){return kp("(prefers-color-scheme: dark)",e)}const Vp=()=>Gl(),Fe=()=>Jd(),ra=Symbol(""),Yr=()=>{const e=Oe(ra);if(!e)throw new Error("useDarkMode() is called without provider.");return e},Fp=()=>{const e=Fe(),t=jp(),n=Bp("vuepress-color-scheme",e.value.colorMode),o=z({get(){return e.value.colorModeSwitch?n.value==="auto"?t.value:n.value==="dark":e.value.colorMode==="dark"},set(r){r===t.value?n.value="auto":n.value=r?"dark":"light"}});Wt(ra,o),zp(o)},zp=e=>{const t=(n=e.value)=>{const o=window==null?void 0:window.document.querySelector("html");o==null||o.classList.toggle("dark",n)};We(()=>{et(e,t,{immediate:!0})}),Bo(()=>t())},sa=(...e)=>{const n=Gt().resolve(...e),o=n.matched[n.matched.length-1];if(!(o!=null&&o.redirect))return n;const{redirect:r}=o,s=re(r)?r(n):r,i=me(s)?{path:s}:s;return sa({hash:n.hash,query:n.query,params:n.params,...i})},Gr=e=>{const t=sa(encodeURI(e));return{text:t.meta.title||e,link:t.name==="404"?e:t.fullPath}};let nr=null,Cn=null;const Up={wait:()=>nr,pending:()=>{nr=new Promise(e=>Cn=e)},resolve:()=>{Cn==null||Cn(),nr=null,Cn=null}},ia=()=>Up,la=Symbol("sidebarItems"),Zr=()=>{const e=Oe(la);if(!e)throw new Error("useSidebarItems() is called without provider.");return e},Kp=()=>{const e=Fe(),t=vt(),n=z(()=>qp(t.value,e.value));Wt(la,n)},qp=(e,t)=>{const n=e.sidebar??t.sidebar??"auto",o=e.sidebarDepth??t.sidebarDepth??2;return e.home||n===!1?[]:n==="auto"?Jp(o):q(n)?aa(n,o):Vr(n)?Qp(n,o):[]},Wp=(e,t)=>({text:e.title,link:e.link,children:Xr(e.children,t)}),Xr=(e,t)=>t>0?e.map(n=>Wp(n,t-1)):[],Jp=e=>{const t=$t();return[{text:t.value.title,children:Xr(t.value.headers,e)}]},aa=(e,t)=>{const n=Zt(),o=$t(),r=s=>{var l;let i;if(me(s)?i=Gr(s):i=s,i.children)return{...i,children:i.children.map(a=>r(a))};if(i.link===n.path){const a=((l=o.value.headers[0])==null?void 0:l.level)===1?o.value.headers[0].children:o.value.headers;return{...i,children:Xr(a,t)}}return i};return e.map(s=>r(s))},Qp=(e,t)=>{const n=Zt(),o=Sl(e,n.path),r=e[o]??[];return aa(r,t)},Yp="719px",Gp={mobile:Yp};var Fn;(function(e){e.MOBILE="mobile"})(Fn||(Fn={}));var Li;const Zp={[Fn.MOBILE]:Number.parseInt((Li=Gp.mobile)==null?void 0:Li.replace("px",""),10)},ca=(e,t)=>{const n=Zp[e];Number.isInteger(n)&&We(()=>{t(n),window.addEventListener("resize",()=>t(n),!1),window.addEventListener("orientationchange",()=>t(n),!1)})},Xp={},eh={class:"theme-default-content"};function th(e,t){const n=bt("Content");return H(),Q("div",eh,[ne(n)])}const nh=Le(Xp,[["render",th],["__file","HomeContent.vue"]]),oh={key:0,class:"features"},rh=["innerHTML"],sh=fe({__name:"myHomeFeatures",setup(e){const t=vt(),n=z(()=>q(t.value.features)?t.value.features:[]);return(o,r)=>n.value.length?(H(),Q("div",oh,[(H(!0),Q(Ee,null,yt(n.value,s=>(H(),Q("div",{key:s.title,class:"feature"},[ae("h2",null,Ie(s.title),1),ae("div",{innerHTML:s.details},null,8,rh)]))),128))])):xe("v-if",!0)}}),ih=Le(sh,[["__file","myHomeFeatures.vue"]]),lh=["innerHTML"],ah=["textContent"],ch=fe({__name:"HomeFooter",setup(e){const t=vt(),n=z(()=>t.value.footer),o=z(()=>t.value.footerHtml);return(r,s)=>n.value?(H(),Q(Ee,{key:0},[xe(" eslint-disable-next-line vue/no-v-html "),o.value?(H(),Q("div",{key:0,class:"footer",innerHTML:n.value},null,8,lh)):(H(),Q("div",{key:1,class:"footer",textContent:Ie(n.value)},null,8,ah))],64)):xe("v-if",!0)}}),uh=Le(ch,[["__file","HomeFooter.vue"]]),fh=["href","rel","target","aria-label"],dh=fe({inheritAttrs:!1}),ph=fe({...dh,__name:"AutoLink",props:{item:{type:Object,required:!0}},setup(e){const t=e,n=Zt(),o=Nl(),{item:r}=Dr(t),s=z(()=>Yn(r.value.link)),i=z(()=>of(r.value.link)||rf(r.value.link)),l=z(()=>{if(!i.value){if(r.value.target)return r.value.target;if(s.value)return"_blank"}}),a=z(()=>l.value==="_blank"),c=z(()=>!s.value&&!i.value&&!a.value),u=z(()=>{if(!i.value){if(r.value.rel)return r.value.rel;if(a.value)return"noopener noreferrer"}}),f=z(()=>r.value.ariaLabel||r.value.text),p=z(()=>{const w=Object.keys(o.value.locales);return w.length?!w.some(x=>x===r.value.link):r.value.link!=="/"}),v=z(()=>p.value?n.path.startsWith(r.value.link):!1),y=z(()=>c.value?r.value.activeMatch?new RegExp(r.value.activeMatch).test(n.path):v.value:!1);return(w,x)=>{const g=bt("RouterLink"),b=bt("AutoLinkExternalIcon");return c.value?(H(),Re(g,hr({key:0,class:{"router-link-active":y.value},to:X(r).link,"aria-label":f.value},w.$attrs),{default:Me(()=>[Ce(w.$slots,"before"),Et(" "+Ie(X(r).text)+" ",1),Ce(w.$slots,"after")]),_:3},16,["class","to","aria-label"])):(H(),Q("a",hr({key:1,class:"external-link",href:X(r).link,rel:u.value,target:l.value,"aria-label":f.value},w.$attrs),[Ce(w.$slots,"before"),Et(" "+Ie(X(r).text)+" ",1),a.value?(H(),Re(b,{key:0})):xe("v-if",!0),Ce(w.$slots,"after")],16,fh))}}}),gt=Le(ph,[["__file","AutoLink.vue"]]),hh={class:"hero"},mh={key:0,id:"main-title"},vh={key:1,class:"description"},gh={key:2,class:"actions"},_h=fe({__name:"HomeHero",setup(e){const t=vt(),n=zr(),o=Yr(),r=z(()=>o.value&&t.value.heroImageDark!==void 0?t.value.heroImageDark:t.value.heroImage),s=z(()=>t.value.heroAlt||l.value||"hero"),i=z(()=>t.value.heroHeight||280),l=z(()=>t.value.heroText===null?null:t.value.heroText||n.value.title||"Hello"),a=z(()=>t.value.tagline===null?null:t.value.tagline||n.value.description||"Welcome to your VuePress site"),c=z(()=>q(t.value.actions)?t.value.actions.map(({text:f,link:p,type:v="primary"})=>({text:f,link:p,type:v})):[]),u=()=>{if(!r.value)return null;const f=ge("img",{src:Kr(r.value),alt:s.value,height:i.value});return t.value.heroImageDark===void 0?f:ge(Ur,()=>f)};return(f,p)=>(H(),Q("header",hh,[ne(u),l.value?(H(),Q("h1",mh,Ie(l.value),1)):xe("v-if",!0),a.value?(H(),Q("p",vh,Ie(a.value),1)):xe("v-if",!0),c.value.length?(H(),Q("p",gh,[(H(!0),Q(Ee,null,yt(c.value,v=>(H(),Re(gt,{key:v.text,class:ze(["action-button",[v.type]]),item:v},null,8,["class","item"]))),128))])):xe("v-if",!0)]))}}),bh=Le(_h,[["__file","HomeHero.vue"]]),yh={class:"home"},Eh=fe({__name:"Home",setup(e){return(t,n)=>(H(),Q("main",yh,[ne(bh),ne(ih),ne(nh),ne(uh)]))}}),wh=Le(Eh,[["__file","Home.vue"]]);const Ch={data(){return{selected:void 0,options:[]}},created:async function(){try{let e;const t=sessionStorage.getItem("versions");if(t)try{e=JSON.parse(t)}catch{}if(!e){let o=await(await fetch("https://api.github.com/repos/bcgov/NotifyBC/git/trees/gh-pages")).json();const r=o.tree.find(s=>s.path.toLowerCase()==="version");o=await(await fetch(r.url)).json(),e=o.tree.map(s=>({value:s.path,text:s.path})),e.sort((s,i)=>{const l=s.text.split("."),a=i.text.split(".");for(let c=0;c=0&&(o=r+9);const s=n.indexOf("/",o);window.location.pathname=window.location.pathname.substring(0,9)+t+window.location.pathname.substring(s)}}},Th={key:0},Lh=["value"];function xh(e,t,n,o,r,s){return r.options&&r.options.length>0?(H(),Q("span",Th,[Et(" Version: "),Hn(ae("select",{"onUpdate:modelValue":t[0]||(t[0]=i=>r.selected=i),onChange:t[1]||(t[1]=(...i)=>s.onChange&&s.onChange(...i))},[(H(!0),Q(Ee,null,yt(r.options,i=>(H(),Q("option",{key:i.value,value:i.value},Ie(i.text),9,Lh))),128))],544),[[qu,r.selected]])])):xe("v-if",!0)}const Ph=Le(Ch,[["render",xh],["__scopeId","data-v-888e697c"],["__file","versions.vue"]]),Oh={class:"nb-navbar-brand"},Sh=fe({__name:"myNavbarBrand",setup(e){const t=Gn(),n=zr(),o=Fe(),r=Yr(),s=z(()=>o.value.home||t.value),i=z(()=>n.value.title),l=z(()=>r.value&&o.value.logoDark!==void 0?o.value.logoDark:o.value.logo),a=()=>{if(!l.value)return null;const c=ge("img",{class:"logo",src:Kr(l.value),alt:i.value});return o.value.logoDark===void 0?c:ge(Ur,()=>c)};return(c,u)=>{const f=bt("RouterLink");return H(),Q("div",Oh,[ne(f,{to:s.value},{default:Me(()=>[ne(a)]),_:1},8,["to"]),ne(Ph)])}}});const kh=Le(Sh,[["__file","myNavbarBrand.vue"]]),Ih=fe({__name:"DropdownTransition",setup(e){const t=o=>{o.style.height=o.scrollHeight+"px"},n=o=>{o.style.height=""};return(o,r)=>(H(),Re(Qn,{name:"dropdown",onEnter:t,onAfterEnter:n,onBeforeLeave:t},{default:Me(()=>[Ce(o.$slots,"default")]),_:3}))}}),ua=Le(Ih,[["__file","DropdownTransition.vue"]]),Ah=["aria-label"],Rh={class:"title"},Dh=ae("span",{class:"arrow down"},null,-1),$h=["aria-label"],Mh={class:"title"},Nh={class:"navbar-dropdown"},Hh={class:"navbar-dropdown-subtitle"},Bh={key:1},jh={class:"navbar-dropdown-subitem-wrapper"},Vh=fe({__name:"NavbarDropdown",props:{item:{type:Object,required:!0}},setup(e){const t=e,{item:n}=Dr(t),o=z(()=>n.value.ariaLabel||n.value.text),r=be(!1),s=Zt();et(()=>s.path,()=>{r.value=!1});const i=a=>{a.detail===0?r.value=!r.value:r.value=!1},l=(a,c)=>c[c.length-1]===a;return(a,c)=>(H(),Q("div",{class:ze(["navbar-dropdown-wrapper",{open:r.value}])},[ae("button",{class:"navbar-dropdown-title",type:"button","aria-label":o.value,onClick:i},[ae("span",Rh,Ie(X(n).text),1),Dh],8,Ah),ae("button",{class:"navbar-dropdown-title-mobile",type:"button","aria-label":o.value,onClick:c[0]||(c[0]=u=>r.value=!r.value)},[ae("span",Mh,Ie(X(n).text),1),ae("span",{class:ze(["arrow",r.value?"down":"right"])},null,2)],8,$h),ne(ua,null,{default:Me(()=>[Hn(ae("ul",Nh,[(H(!0),Q(Ee,null,yt(X(n).children,u=>(H(),Q("li",{key:u.text,class:"navbar-dropdown-item"},[u.children?(H(),Q(Ee,{key:0},[ae("h4",Hh,[u.link?(H(),Re(gt,{key:0,item:u,onFocusout:f=>l(u,X(n).children)&&u.children.length===0&&(r.value=!1)},null,8,["item","onFocusout"])):(H(),Q("span",Bh,Ie(u.text),1))]),ae("ul",jh,[(H(!0),Q(Ee,null,yt(u.children,f=>(H(),Q("li",{key:f.link,class:"navbar-dropdown-subitem"},[ne(gt,{item:f,onFocusout:p=>l(f,u.children)&&l(u,X(n).children)&&(r.value=!1)},null,8,["item","onFocusout"])]))),128))])],64)):(H(),Re(gt,{key:1,item:u,onFocusout:f=>l(u,X(n).children)&&(r.value=!1)},null,8,["item","onFocusout"]))]))),128))],512),[[Lo,r.value]])]),_:1})],2))}}),Fh=Le(Vh,[["__file","NavbarDropdown.vue"]]),wi=e=>decodeURI(e).replace(/#.*$/,"").replace(/(index)?\.(md|html)$/,""),zh=(e,t)=>{if(t.hash===e)return!0;const n=wi(t.path),o=wi(e);return n===o},fa=(e,t)=>e.link&&zh(e.link,t)?!0:e.children?e.children.some(n=>fa(n,t)):!1,da=e=>!Yn(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,Uh={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},Kh=({docsRepo:e,editLinkPattern:t})=>{if(t)return t;const n=da(e);return n!==null?Uh[n]:null},qh=({docsRepo:e,docsBranch:t,docsDir:n,filePathRelative:o,editLinkPattern:r})=>{if(!o)return null;const s=Kh({docsRepo:e,editLinkPattern:r});return s?s.replace(/:repo/,Yn(e)?e:`https://github.com/${e}`).replace(/:branch/,t).replace(/:path/,Ol(`${Pl(n)}/${o}`)):null},Wh={key:0,class:"navbar-items"},Jh=fe({__name:"NavbarItems",setup(e){const t=()=>{const u=Gt(),f=Gn(),p=Nl(),v=zr(),y=Vp(),w=Fe();return z(()=>{const x=Object.keys(p.value.locales);if(x.length<2)return[];const g=u.currentRoute.value.path,b=u.currentRoute.value.fullPath;return[{text:`${w.value.selectLanguageText}`,ariaLabel:`${w.value.selectLanguageAriaLabel??w.value.selectLanguageText}`,children:x.map(k=>{var B,Y;const W=((B=p.value.locales)==null?void 0:B[k])??{},ee=((Y=y.value.locales)==null?void 0:Y[k])??{},N=`${W.lang}`,m=ee.selectLanguageName??N;let F;if(N===v.value.lang)F=b;else{const L=g.replace(f.value,k);u.getRoutes().some(R=>R.path===L)?F=b.replace(g,L):F=ee.home??k}return{text:m,link:F}})}]})},n=()=>{const u=Fe(),f=z(()=>u.value.repo),p=z(()=>f.value?da(f.value):null),v=z(()=>f.value&&!Yn(f.value)?`https://github.com/${f.value}`:f.value),y=z(()=>v.value?u.value.repoLabel?u.value.repoLabel:p.value===null?"Source":p.value:null);return z(()=>!v.value||!y.value?[]:[{text:y.value,link:v.value}])},o=u=>me(u)?Gr(u):u.children?{...u,children:u.children.map(o)}:u,r=()=>{const u=Fe();return z(()=>(u.value.navbar||[]).map(o))},s=be(!1),i=r(),l=t(),a=n(),c=z(()=>[...i.value,...l.value,...a.value]);return ca(Fn.MOBILE,u=>{window.innerWidthc.value.length?(H(),Q("nav",Wh,[(H(!0),Q(Ee,null,yt(c.value,p=>(H(),Q("div",{key:p.text,class:"navbar-item"},[p.children?(H(),Re(Fh,{key:0,item:p,class:ze(s.value?"mobile":"")},null,8,["item","class"])):(H(),Re(gt,{key:1,item:p},null,8,["item"]))]))),128))])):xe("v-if",!0)}}),pa=Le(Jh,[["__file","NavbarItems.vue"]]),Qh=["title"],Yh={class:"icon",focusable:"false",viewBox:"0 0 32 32"},Gh=du('',9),Zh=[Gh],Xh={class:"icon",focusable:"false",viewBox:"0 0 32 32"},em=ae("path",{d:"M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3z",fill:"currentColor"},null,-1),tm=[em],nm=fe({__name:"ToggleColorModeButton",setup(e){const t=Fe(),n=Yr(),o=()=>{n.value=!n.value};return(r,s)=>(H(),Q("button",{class:"toggle-color-mode-button",title:X(t).toggleColorMode,onClick:o},[Hn((H(),Q("svg",Yh,Zh,512)),[[Lo,!X(n)]]),Hn((H(),Q("svg",Xh,tm,512)),[[Lo,X(n)]])],8,Qh))}}),om=Le(nm,[["__file","ToggleColorModeButton.vue"]]),rm=["title"],sm=ae("div",{class:"icon","aria-hidden":"true"},[ae("span"),ae("span"),ae("span")],-1),im=[sm],lm=fe({__name:"ToggleSidebarButton",emits:["toggle"],setup(e){const t=Fe();return(n,o)=>(H(),Q("div",{class:"toggle-sidebar-button",title:X(t).toggleSidebar,"aria-expanded":"false",role:"button",tabindex:"0",onClick:o[0]||(o[0]=r=>n.$emit("toggle"))},im,8,rm))}}),am=Le(lm,[["__file","ToggleSidebarButton.vue"]]),cm=fe({__name:"Navbar",emits:["toggle-sidebar"],setup(e){const t=Fe(),n=be(null),o=be(null),r=be(0),s=z(()=>r.value?{maxWidth:r.value+"px"}:{});ca(Fn.MOBILE,l=>{var c;const a=i(n.value,"paddingLeft")+i(n.value,"paddingRight");window.innerWidth{const c=bt("NavbarSearch");return H(),Q("header",{ref_key:"navbar",ref:n,class:"navbar"},[ne(am,{onToggle:a[0]||(a[0]=u=>l.$emit("toggle-sidebar"))}),ae("span",{ref_key:"navbarBrand",ref:o},[ne(kh)],512),ae("div",{class:"navbar-items-wrapper",style:Qt(s.value)},[Ce(l.$slots,"before"),ne(pa,{class:"can-hide"}),Ce(l.$slots,"after"),X(t).colorModeSwitch?(H(),Re(om,{key:0})):xe("v-if",!0),ne(c)],4)],512)}}}),um=Le(cm,[["__file","Navbar.vue"]]),fm={class:"page-meta"},dm={key:0,class:"meta-item edit-link"},pm={key:1,class:"meta-item last-updated"},hm={class:"meta-item-label"},mm={class:"meta-item-info"},vm={key:2,class:"meta-item contributors"},gm={class:"meta-item-label"},_m={class:"meta-item-info"},bm=["title"],ym=fe({__name:"PageMeta",setup(e){const t=()=>{const a=Fe(),c=$t(),u=vt();return z(()=>{if(!(u.value.editLink??a.value.editLink??!0))return null;const{repo:p,docsRepo:v=p,docsBranch:y="main",docsDir:w="",editLinkText:x}=a.value;if(!v)return null;const g=qh({docsRepo:v,docsBranch:y,docsDir:w,filePathRelative:c.value.filePathRelative,editLinkPattern:u.value.editLinkPattern??a.value.editLinkPattern});return g?{text:x??"Edit this page",link:g}:null})},n=()=>{const a=Fe(),c=$t(),u=vt();return z(()=>{var v,y;return!(u.value.lastUpdated??a.value.lastUpdated??!0)||!((v=c.value.git)!=null&&v.updatedTime)?null:new Date((y=c.value.git)==null?void 0:y.updatedTime).toLocaleString()})},o=()=>{const a=Fe(),c=$t(),u=vt();return z(()=>{var p;return u.value.contributors??a.value.contributors??!0?((p=c.value.git)==null?void 0:p.contributors)??null:null})},r=Fe(),s=t(),i=n(),l=o();return(a,c)=>{const u=bt("ClientOnly");return H(),Q("footer",fm,[X(s)?(H(),Q("div",dm,[ne(gt,{class:"meta-item-label",item:X(s)},null,8,["item"])])):xe("v-if",!0),X(i)?(H(),Q("div",pm,[ae("span",hm,Ie(X(r).lastUpdatedText)+": ",1),ne(u,null,{default:Me(()=>[ae("span",mm,Ie(X(i)),1)]),_:1})])):xe("v-if",!0),X(l)&&X(l).length?(H(),Q("div",vm,[ae("span",gm,Ie(X(r).contributorsText)+": ",1),ae("span",_m,[(H(!0),Q(Ee,null,yt(X(l),(f,p)=>(H(),Q(Ee,{key:p},[ae("span",{class:"contributor",title:`email: ${f.email}`},Ie(f.name),9,bm),p!==X(l).length-1?(H(),Q(Ee,{key:0},[Et(", ")],64)):xe("v-if",!0)],64))),128))])])):xe("v-if",!0)])}}}),Em=Le(ym,[["__file","PageMeta.vue"]]),wm={key:0,class:"page-nav"},Cm={class:"inner"},Tm={key:0,class:"prev"},Lm={key:1,class:"next"},xm=fe({__name:"PageNav",setup(e){const t=a=>a===!1?null:me(a)?Gr(a):Vr(a)?a:!1,n=(a,c,u)=>{const f=a.findIndex(p=>p.link===c);if(f!==-1){const p=a[f+u];return p!=null&&p.link?p:null}for(const p of a)if(p.children){const v=n(p.children,c,u);if(v)return v}return null},o=vt(),r=Zr(),s=Zt(),i=z(()=>{const a=t(o.value.prev);return a!==!1?a:n(r.value,s.path,-1)}),l=z(()=>{const a=t(o.value.next);return a!==!1?a:n(r.value,s.path,1)});return(a,c)=>i.value||l.value?(H(),Q("nav",wm,[ae("p",Cm,[i.value?(H(),Q("span",Tm,[ne(gt,{item:i.value},null,8,["item"])])):xe("v-if",!0),l.value?(H(),Q("span",Lm,[ne(gt,{item:l.value},null,8,["item"])])):xe("v-if",!0)])])):xe("v-if",!0)}}),Pm=Le(xm,[["__file","PageNav.vue"]]),Om={class:"page"},Sm={class:"theme-default-content"},km=fe({__name:"Page",setup(e){return(t,n)=>{const o=bt("Content");return H(),Q("main",Om,[Ce(t.$slots,"top"),ae("div",Sm,[Ce(t.$slots,"content-top"),ne(o),Ce(t.$slots,"content-bottom")]),ne(Em),ne(Pm),Ce(t.$slots,"bottom")])}}}),Im=Le(km,[["__file","Page.vue"]]),Am=["onKeydown"],Rm={class:"sidebar-item-children"},Dm=fe({__name:"SidebarItem",props:{item:{type:Object,required:!0},depth:{type:Number,required:!1,default:0}},setup(e){const t=e,{item:n,depth:o}=Dr(t),r=Zt(),s=Gt(),i=z(()=>fa(n.value,r)),l=z(()=>({"sidebar-item":!0,"sidebar-heading":o.value===0,active:i.value,collapsible:n.value.collapsible})),a=z(()=>n.value.collapsible?i.value:!0),[c,u]=mp(a.value),f=v=>{n.value.collapsible&&(v.preventDefault(),u())},p=s.afterEach(v=>{Do(()=>{c.value=a.value})});return Jn(()=>{p()}),(v,y)=>{var x;const w=bt("SidebarItem",!0);return H(),Q("li",null,[X(n).link?(H(),Re(gt,{key:0,class:ze(l.value),item:X(n)},null,8,["class","item"])):(H(),Q("p",{key:1,tabindex:"0",class:ze(l.value),onClick:f,onKeydown:Ju(f,["enter"])},[Et(Ie(X(n).text)+" ",1),X(n).collapsible?(H(),Q("span",{key:0,class:ze(["arrow",X(c)?"down":"right"])},null,2)):xe("v-if",!0)],42,Am)),(x=X(n).children)!=null&&x.length?(H(),Re(ua,{key:2},{default:Me(()=>[Hn(ae("ul",Rm,[(H(!0),Q(Ee,null,yt(X(n).children,g=>(H(),Re(w,{key:`${X(o)}${g.text}${g.link}`,item:g,depth:X(o)+1},null,8,["item","depth"]))),128))],512),[[Lo,X(c)]])]),_:1})):xe("v-if",!0)])}}}),$m=Le(Dm,[["__file","SidebarItem.vue"]]),Mm={key:0,class:"sidebar-items"},Nm=fe({__name:"SidebarItems",setup(e){const t=Zt(),n=Zr();return We(()=>{et(()=>t.hash,o=>{const r=document.querySelector(".sidebar");if(!r)return;const s=document.querySelector(`.sidebar a.sidebar-item[href="${t.path}${o}"]`);if(!s)return;const{top:i,height:l}=r.getBoundingClientRect(),{top:a,height:c}=s.getBoundingClientRect();ai+l&&s.scrollIntoView(!1)})}),(o,r)=>X(n).length?(H(),Q("ul",Mm,[(H(!0),Q(Ee,null,yt(X(n),s=>(H(),Re($m,{key:`${s.text}${s.link}`,item:s},null,8,["item"]))),128))])):xe("v-if",!0)}}),Hm=Le(Nm,[["__file","SidebarItems.vue"]]),Bm={class:"sidebar"},jm=fe({__name:"Sidebar",setup(e){return(t,n)=>(H(),Q("aside",Bm,[ne(pa),Ce(t.$slots,"top"),ne(Hm),Ce(t.$slots,"bottom")]))}}),Vm=Le(jm,[["__file","Sidebar.vue"]]),Fm=fe({__name:"Layout",setup(e){const t=$t(),n=vt(),o=Fe(),r=z(()=>n.value.navbar!==!1&&o.value.navbar!==!1),s=Zr(),i=be(!1),l=x=>{i.value=typeof x=="boolean"?x:!i.value},a={x:0,y:0},c=x=>{a.x=x.changedTouches[0].clientX,a.y=x.changedTouches[0].clientY},u=x=>{const g=x.changedTouches[0].clientX-a.x,b=x.changedTouches[0].clientY-a.y;Math.abs(g)>Math.abs(b)&&Math.abs(g)>40&&(g>0&&a.x<=80?l(!0):l(!1))},f=z(()=>[{"no-navbar":!r.value,"no-sidebar":!s.value.length,"sidebar-open":i.value},n.value.pageClass]);let p;We(()=>{p=Gt().afterEach(()=>{l(!1)})}),Bo(()=>{p()});const v=ia(),y=v.resolve,w=v.pending;return(x,g)=>(H(),Q("div",{class:ze(["theme-container",f.value]),onTouchstart:c,onTouchend:u},[Ce(x.$slots,"navbar",{},()=>[r.value?(H(),Re(um,{key:0,onToggleSidebar:l},{before:Me(()=>[Ce(x.$slots,"navbar-before")]),after:Me(()=>[Ce(x.$slots,"navbar-after")]),_:3})):xe("v-if",!0)]),ae("div",{class:"sidebar-mask",onClick:g[0]||(g[0]=b=>l(!1))}),Ce(x.$slots,"sidebar",{},()=>[ne(Vm,null,{top:Me(()=>[Ce(x.$slots,"sidebar-top")]),bottom:Me(()=>[Ce(x.$slots,"sidebar-bottom")]),_:3})]),Ce(x.$slots,"page",{},()=>[X(n).home?(H(),Re(wh,{key:0})):(H(),Re(Qn,{key:1,name:"fade-slide-y",mode:"out-in",onBeforeEnter:X(y),onBeforeLeave:X(w)},{default:Me(()=>[(H(),Re(Im,{key:X(t).path},{top:Me(()=>[Ce(x.$slots,"page-top")]),"content-top":Me(()=>[Ce(x.$slots,"page-content-top")]),"content-bottom":Me(()=>[Ce(x.$slots,"page-content-bottom")]),bottom:Me(()=>[Ce(x.$slots,"page-bottom")]),_:3}))]),_:3},8,["onBeforeEnter","onBeforeLeave"]))])],34))}}),zm=Le(Fm,[["__file","Layout.vue"]]),Um={class:"theme-container"},Km={class:"page"},qm={class:"theme-default-content"},Wm=ae("h1",null,"404",-1),Jm=fe({__name:"NotFound",setup(e){const t=Gn(),n=Fe(),o=n.value.notFound??["Not Found"],r=()=>o[Math.floor(Math.random()*o.length)],s=n.value.home??t.value,i=n.value.backToHome??"Back to home";return(l,a)=>{const c=bt("RouterLink");return H(),Q("div",Um,[ae("main",Km,[ae("div",qm,[Wm,ae("blockquote",null,Ie(r()),1),ne(c,{to:X(s)},{default:Me(()=>[Et(Ie(X(i)),1)]),_:1},8,["to"])])])])}}}),Qm=Le(Jm,[["__file","NotFound.vue"]]);const Ym=Ct({enhance({app:e,router:t}){e.component("Badge",Zd),e.component("CodeGroup",Xd),e.component("CodeGroupItem",op),e.component("AutoLinkExternalIcon",()=>{const o=e.component("ExternalLinkIcon");return o?ge(o):null}),e.component("NavbarSearch",()=>{const o=e.component("Docsearch")||e.component("SearchBox");return o?ge(o):null});const n=t.options.scrollBehavior;t.options.scrollBehavior=async(...o)=>(await ia().wait(),n(...o))},setup(){Fp(),Kp()},layouts:{Layout:zm,NotFound:Qm}}),Gm=e=>{const t=br("keydown",n=>{const o=n.key==="k"&&(n.ctrlKey||n.metaKey);!(n.key==="/")&&!o||(n.preventDefault(),e(),t())})},Zm=e=>e.button===1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey,Xm=()=>{const e=Gt();return{hitComponent:({hit:t,children:n})=>({type:"a",ref:void 0,constructor:void 0,key:void 0,props:{href:t.url,onClick:o=>{Zm(o)||(o.preventDefault(),e.push(zs(t.url,"/NotifyBC/")))},children:n},__v:null}),navigator:{navigate:({itemUrl:t})=>{e.push(zs(t,"/NotifyBC/"))}},transformSearchClient:t=>{const n=qr(t.search,500);return{...t,search:async(...o)=>n(...o)}}}},ev=(e=[],t)=>[`lang:${t}`,...q(e)?e:[e]],tv=({buttonText:e="Search",buttonAriaLabel:t=e}={})=>``,nv=16,ha=()=>{if(document.querySelector(".DocSearch-Modal"))return;const e=new Event("keydown");e.key="k",e.metaKey=!0,window.dispatchEvent(e),setTimeout(ha,nv)},ov=e=>{const t="algolia-preconnect";(window.requestIdleCallback||setTimeout)(()=>{if(document.head.querySelector(`#${t}`))return;const o=document.createElement("link");o.id=t,o.rel="preconnect",o.href=`https://${e}-dsn.algolia.net`,o.crossOrigin="",document.head.appendChild(o)})},rv={apiKey:"c28cbfc8ec48e407e775c3a574dcd775",appId:"JNUID4IQ3B",indexName:"notifybc"};O(()=>import("./style-e9220a04.js"),[]),O(()=>import("./docsearch-1d421ddb.js"),[]);const sv=fe({name:"Docsearch",props:{containerId:{type:String,required:!1,default:"docsearch-container"},options:{type:Object,required:!1,default:()=>rv}},setup(e){const t=Xm(),n=$l(),o=Gn(),r=be(!1),s=be(!1),i=z(()=>{var c;return{...e.options,...(c=e.options.locales)==null?void 0:c[o.value]}}),l=async()=>{var u;const{default:c}=await O(()=>import("./index-5161ad19.js"),[]);c({...t,...i.value,container:`#${e.containerId}`,searchParameters:{...i.value.searchParameters,facetFilters:ev((u=i.value.searchParameters)==null?void 0:u.facetFilters,n.value)}}),r.value=!0},a=()=>{s.value||r.value||(s.value=!0,l(),ha(),et(o,l))};return Gm(a),We(()=>ov(i.value.appId)),()=>{var c;return[ge("div",{id:e.containerId,style:{display:r.value?"block":"none"}}),r.value?null:ge("div",{onClick:a,innerHTML:tv((c=i.value.translations)==null?void 0:c.button)})]}}}),iv=Ct({enhance({app:e}){e.component("Docsearch",sv)}});const lv={name:"CodeCopy",props:{parent:Object,code:String,options:{align:String,color:String,backgroundTransition:Boolean,backgroundColor:String,successText:String,successTextColor:String,staticIcon:Boolean}},data(){return{success:!1,originalBackground:null,originalTransition:null}},computed:{alignStyle(){let e={};return e[this.options.align]="7.5px",e},iconClass(){return this.options.staticIcon?"":"hover"}},mounted(){this.originalTransition=this.parent.style.transition,this.originalBackground=this.parent.style.background},beforeDestroy(){this.parent.style.transition=this.originalTransition,this.parent.style.background=this.originalBackground},methods:{hexToRgb(e){let t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null},copyToClipboard(e){if(navigator.clipboard)navigator.clipboard.writeText(this.code).then(()=>{this.setSuccessTransitions()},()=>{});else{let t=document.createElement("textarea");document.body.appendChild(t),t.value=this.code,t.select(),document.execCommand("Copy"),t.remove(),this.setSuccessTransitions()}},setSuccessTransitions(){if(clearTimeout(this.successTimeout),this.options.backgroundTransition){this.parent.style.transition="background 350ms";let e=this.hexToRgb(this.options.backgroundColor);this.parent.style.background=`rgba(${e.r}, ${e.g}, ${e.b}, 0.1)`}this.success=!0,this.successTimeout=setTimeout(()=>{this.options.backgroundTransition&&(this.parent.style.background=this.originalBackground,this.parent.style.transition=this.originalTransition),this.success=!1},500)}}},av=e=>(Cc("data-v-1b705319"),e=e(),Tc(),e),cv={class:"code-copy"},uv=av(()=>ae("path",{fill:"none",d:"M0 0h24v24H0z"},null,-1)),fv=["fill"];function dv(e,t,n,o,r,s){return H(),Q("div",cv,[(H(),Q("svg",{onClick:t[0]||(t[0]=(...i)=>s.copyToClipboard&&s.copyToClipboard(...i)),xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24",class:ze(s.iconClass),style:Qt(s.alignStyle)},[uv,ae("path",{fill:n.options.color,d:"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"},null,8,fv)],6)),ae("span",{class:ze(r.success?"success":""),style:Qt({color:n.options.successTextColor,...s.alignStyle})},Ie(n.options.successText),7)])}const Ci=Le(lv,[["render",dv],["__scopeId","data-v-1b705319"],["__file","CodeCopy.vue"]]);const pv=Ct({enhance({app:e}){e.component("CodeCopy",Ci)},setup(){const e=$t(),t=()=>{setTimeout(()=>{document.querySelectorAll('div[class*="language-"]').forEach(n=>{if(n.classList.contains("code-copy-added"))return;let o={align:"bottom",color:"#27b1ff",backgroundTransition:!0,backgroundColor:"#0075b8",successText:"Copied!",successTextColor:"#27b1ff",staticIcon:!1},r=Gu(Ci,{parent:n,code:n.querySelector("pre").innerText,options:o}),s=document.createElement("div");n.appendChild(s),r.mount(s),n.classList.add("code-copy-added")})},500)};We(()=>{t(),window.addEventListener("snippetors-vuepress-plugin-code-copy-update-event",t)}),Jn(()=>{window.removeEventListener("snippetors-vuepress-plugin-code-copy-update-event",t)}),il(()=>{t()}),et(()=>e.value.path,t)}}),uo=[wd,Ld,Sd,Vd,Kd,Yd,Ym,iv,pv],hv=[["v-8daa1a0e","/",{title:""},["/index.md"]],["v-14ac19b5","/help/",{title:""},["/help/index.md"]],["v-e5065f60","/docs/api-administrator/",{title:"Administrator"},["/docs/api/administrator.html","/docs/api/administrator.md"]],["v-eb8745ce","/docs/api-bounce/",{title:"Bounce"},["/docs/api/bounce.html","/docs/api/bounce.md"]],["v-828730c2","/docs/api-config/",{title:"Configuration"},["/docs/api/config.html","/docs/api/config.md"]],["v-563ef996","/docs/api-notification/",{title:"Notification"},["/docs/api/notification.html","/docs/api/notification.md"]],["v-04b96fc8","/docs/api-overview/",{title:"API Overview"},["/docs/api/overview.html","/docs/api/overview.md"]],["v-fe45a0b8","/docs/api-subscription/",{title:"Subscription"},["/docs/api/subscription.html","/docs/api/subscription.md"]],["v-7ed00a2a","/docs/config-adminIpList/",{title:"Admin IP List"},["/docs/config/adminIpList.html","/docs/config/adminIpList.md"]],["v-326db923","/docs/config-certificates/",{title:"TLS Certificates"},["/docs/config/certificates.html","/docs/config/certificates.md"]],["v-587df7db","/docs/config-cronJobs/",{title:"Cron Jobs"},["/docs/config/cronJobs.html","/docs/config/cronJobs.md"]],["v-0b2aad78","/docs/config-database/",{title:"Database"},["/docs/config/database.html","/docs/config/database.md"]],["v-23f62a3a","/docs/config-email/",{title:"Email"},["/docs/config/email.html","/docs/config/email.md"]],["v-1963670f","/docs/config-httpHost/",{title:"HTTP Host"},["/docs/config/httpHost.html","/docs/config/httpHost.md"]],["v-4cf2565c","/docs/config-internalHttpHost/",{title:"Internal HTTP Host"},["/docs/config/internalHttpHost.html","/docs/config/internalHttpHost.md"]],["v-17bdcfe6","/docs/config-middleware/",{title:"Middleware"},["/docs/config/middleware.html","/docs/config/middleware.md"]],["v-3481b484","/docs/config-nodeRoles/",{title:"Node Roles"},["/docs/config/nodeRoles.html","/docs/config/nodeRoles.md"]],["v-b6a1f058","/docs/config-notification/",{title:"Notification"},["/docs/config/notification.html","/docs/config/notification.md"]],["v-94b7dab4","/docs/config-oidc/",{title:"OIDC"},["/docs/config/oidc.html","/docs/config/oidc.md"]],["v-391365f4","/docs/config-overview/",{title:"Configuration Overview"},["/docs/config/overview.html","/docs/config/overview.md"]],["v-32b5e2dd","/docs/config-reverseProxyIpLists/",{title:"Reverse Proxy IP Lists"},["/docs/config/reverseProxyIpLists.html","/docs/config/reverseProxyIpLists.md"]],["v-02a19d2b","/docs/config-rsaKeys/",{title:"RSA Keys"},["/docs/config/rsaKeys.html","/docs/config/rsaKeys.md"]],["v-26e624c6","/docs/config-sms/",{title:"SMS"},["/docs/config/sms.html","/docs/config/sms.md"]],["v-6165843c","/docs/config-subscription/",{title:"Subscription"},["/docs/config/subscription.html","/docs/config/subscription.md"]],["v-22e054a1","/docs/config-workerProcessCount/",{title:"Worker Process Count"},["/docs/config/workerProcessCount.html","/docs/config/workerProcessCount.md"]],["v-ca3407c4","/docs/acknowledgments/",{title:"Acknowledgments"},["/docs/meta/acknowledgments.html","/docs/meta/acknowledgments.md"]],["v-3cf0fa66","/docs/conduct/",{title:"Code of Conduct"},["/docs/meta/conduct.html","/docs/meta/conduct.md"]],["v-147825fb","/docs/",{title:"Welcome"},["/docs/getting-started/","/docs/getting-started/index.md"]],["v-255f131a","/docs/installation/",{title:"Installation"},["/docs/getting-started/installation.html","/docs/getting-started/installation.md"]],["v-6768263b","/docs/overview/",{title:"Overview"},["/docs/getting-started/overview.html","/docs/getting-started/overview.md"]],["v-6a4de75f","/docs/quickstart/",{title:"Quick Start"},["/docs/getting-started/quickstart.html","/docs/getting-started/quickstart.md"]],["v-a20dfce8","/docs/web-console/",{title:"Web Console"},["/docs/getting-started/web-console.html","/docs/getting-started/web-console.md"]],["v-9a955b1e","/docs/what's-new/",{title:"What's New"},["/docs/getting-started/what's-new.html","/docs/getting-started/what's-new.md"]],["v-b09aba04","/docs/benchmarks/",{title:"Benchmarks"},["/docs/miscellaneous/benchmarks.html","/docs/miscellaneous/benchmarks.md"]],["v-b341ee2c","/docs/bulk-import/",{title:"Bulk Import"},["/docs/miscellaneous/bulk-import.html","/docs/miscellaneous/bulk-import.md"]],["v-0c9564ec","/docs/developer-notes/",{title:"Developer Notes"},["/docs/miscellaneous/developer-notes.html","/docs/miscellaneous/developer-notes.md"]],["v-36e2ae9d","/docs/health-check/",{title:"Health Check"},["/docs/miscellaneous/health-check.html","/docs/miscellaneous/health-check.md"]],["v-5b6d532c","/docs/memory-dump/",{title:"Memory Dump"},["/docs/miscellaneous/memory-dump.html","/docs/miscellaneous/memory-dump.md"]],["v-9712b6e4","/docs/upgrade/",{title:"Upgrade Guide"},["/docs/miscellaneous/upgrade.html","/docs/miscellaneous/upgrade.md"]],["v-bdba93e6","/docs/shared/filterQueryParam.html",{title:""},[":md"]],["v-31ddcbc0","/docs/shared/filterQueryParamCode.html",{title:""},[":md"]],["v-0e79de1b","/docs/shared/filterQueryParamExample.html",{title:""},[":md"]],["v-9a1a7988","/docs/shared/jmespathFilter.html",{title:""},[":md"]],["v-4395d380","/docs/shared/throttle.html",{title:""},[":md"]],["v-17bf8008","/docs/shared/whereQueryParam.html",{title:""},[":md"]],["v-0b4a148f","/docs/shared/whereQueryParamCode.html",{title:""},[":md"]],["v-5119194c","/docs/shared/whereQueryParamExample.html",{title:""},[":md"]],["v-3706649a","/404.html",{title:""},[]]];var Ti=fe({name:"Vuepress",setup(){const e=uf();return()=>ge(e.value)}}),mv=()=>hv.reduce((e,[t,n,o,r])=>(e.push({name:t,path:n,component:Ti,meta:o},{path:n.endsWith("/")?n+"index.html":n.substring(0,n.length-5),redirect:n},...r.map(s=>({path:s===":md"?n.substring(0,n.length-5)+".md":s,redirect:n}))),e),[{name:"404",path:"/:catchAll(.*)",component:Ti}]),vv=Af,gv=()=>{const e=md({history:vv(Pl("/NotifyBC/")),routes:mv(),scrollBehavior:(t,n,o)=>o||(t.hash?{el:t.hash}:{top:0})});return e.beforeResolve(async(t,n)=>{var o;(t.path!==n.path||n===ht)&&([It.value]=await Promise.all([pt.resolvePageData(t.name),(o=kl[t.name])==null?void 0:o.__asyncLoader()]))}),e},_v=e=>{e.component("ClientOnly",Ur),e.component("Content",hf)},bv=(e,t,n)=>{const o=z(()=>pt.resolveLayouts(n)),r=mi(()=>t.currentRoute.value.path),s=mi(()=>pt.resolveRouteLocale(on.value.locales,r.value)),i=z(()=>pt.resolveSiteLocaleData(on.value,s.value)),l=z(()=>pt.resolvePageFrontmatter(It.value)),a=z(()=>pt.resolvePageHeadTitle(It.value,i.value)),c=z(()=>pt.resolvePageHead(a.value,l.value,i.value)),u=z(()=>pt.resolvePageLang(It.value,i.value)),f=z(()=>pt.resolvePageLayout(It.value,o.value));return e.provide(sf,o),e.provide(Al,l),e.provide(cf,a),e.provide(Rl,c),e.provide(Dl,u),e.provide(Ml,f),e.provide(Fr,s),e.provide(Hl,i),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>l.value},$head:{get:()=>c.value},$headTitle:{get:()=>a.value},$lang:{get:()=>u.value},$page:{get:()=>It.value},$routeLocale:{get:()=>s.value},$site:{get:()=>on.value},$siteLocale:{get:()=>i.value},$withBase:{get:()=>Kr}}),{layouts:o,pageData:It,pageFrontmatter:l,pageHead:c,pageHeadTitle:a,pageLang:u,pageLayout:f,routeLocale:s,siteData:on,siteLocaleData:i}},yv=()=>{const e=af(),t=$l(),n=be([]),o=()=>{e.value.forEach(s=>{const i=Ev(s);i&&n.value.push(i)})},r=()=>{document.documentElement.lang=t.value,n.value.forEach(s=>{s.parentNode===document.head&&document.head.removeChild(s)}),n.value.splice(0,n.value.length),e.value.forEach(s=>{const i=wv(s);i!==null&&(document.head.appendChild(i),n.value.push(i))})};Wt(ff,r),We(()=>{o(),r(),et(()=>e.value,r)})},Ev=([e,t,n=""])=>{const o=Object.entries(t).map(([l,a])=>me(a)?`[${l}=${JSON.stringify(a)}]`:a===!0?`[${l}]`:"").join(""),r=`head > ${e}${o}`;return Array.from(document.querySelectorAll(r)).find(l=>l.innerText===n)||null},wv=([e,t,n])=>{if(!me(e))return null;const o=document.createElement(e);return Vr(t)&&Object.entries(t).forEach(([r,s])=>{me(s)?o.setAttribute(r,s):s===!0&&o.setAttribute(r,"")}),me(n)&&o.appendChild(document.createTextNode(n)),o},Cv=Zu,Tv=async()=>{var n;const e=Cv({name:"VuepressApp",setup(){var o;yv();for(const r of uo)(o=r.setup)==null||o.call(r);return()=>[ge(Ql),...uo.flatMap(({rootComponents:r=[]})=>r.map(s=>ge(s)))]}}),t=gv();_v(e),bv(e,t,uo);for(const o of uo)await((n=o.enhance)==null?void 0:n.call(o,{app:e,router:t,siteData:on}));return e.use(t),{app:e,router:t}};Tv().then(({app:e,router:t})=>{t.isReady().then(()=>{e.mount("#app")})});export{Le as _,ae as a,Et as b,Q as c,Tv as createVueApp,ne as d,du as e,Gl as f,H as o,bt as r,Ie as t,X as u,Me as w}; + */const de={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
'},status:null,set:e=>{const t=de.isStarted();e=tr(e,de.settings.minimum,1),de.status=e===1?null:e;const n=de.render(!t),o=n.querySelector(de.settings.barSelector),r=de.settings.speed,s=de.settings.easing;return n.offsetWidth,Fd(i=>{lo(o,{transform:"translate3d("+ui(e)+"%,0,0)",transition:"all "+r+"ms "+s}),e===1?(lo(n,{transition:"none",opacity:"1"}),n.offsetWidth,setTimeout(function(){lo(n,{transition:"all "+r+"ms linear",opacity:"0"}),setTimeout(function(){de.remove(),i()},r)},r)):setTimeout(()=>i(),r)}),de},isStarted:()=>typeof de.status=="number",start:()=>{de.status||de.set(0);const e=()=>{setTimeout(()=>{de.status&&(de.trickle(),e())},de.settings.trickleSpeed)};return de.settings.trickle&&e(),de},done:e=>!e&&!de.status?de:de.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=de.status;return t?(typeof e!="number"&&(e=(1-t)*tr(Math.random()*t,.1,.95)),t=tr(t+e,0,.994),de.set(t)):de.start()},trickle:()=>de.inc(Math.random()*de.settings.trickleRate),render:e=>{if(de.isRendered())return document.getElementById("nprogress");fi(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=de.settings.template;const n=t.querySelector(de.settings.barSelector),o=e?"-100":ui(de.status||0),r=document.querySelector(de.settings.parent);return lo(n,{transition:"all 0 linear",transform:"translate3d("+o+"%,0,0)"}),r!==document.body&&fi(r,"nprogress-custom-parent"),r==null||r.appendChild(t),t},remove:()=>{di(document.documentElement,"nprogress-busy"),di(document.querySelector(de.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&zd(e)},isRendered:()=>!!document.getElementById("nprogress")},tr=(e,t,n)=>en?n:e,ui=e=>(-1+e)*100,Fd=function(){const e=[];function t(){const n=e.shift();n&&n(t)}return function(n){e.push(n),e.length===1&&t()}}(),lo=function(){const e=["Webkit","O","Moz","ms"],t={};function n(i){return i.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(l,a){return a.toUpperCase()})}function o(i){const l=document.body.style;if(i in l)return i;let a=e.length;const c=i.charAt(0).toUpperCase()+i.slice(1);let u;for(;a--;)if(u=e[a]+c,u in l)return u;return i}function r(i){return i=n(i),t[i]??(t[i]=o(i))}function s(i,l,a){l=r(l),i.style[l]=a}return function(i,l){for(const a in l){const c=l[a];c!==void 0&&Object.prototype.hasOwnProperty.call(l,a)&&s(i,a,c)}}}(),Yl=(e,t)=>(typeof e=="string"?e:Qr(e)).indexOf(" "+t+" ")>=0,fi=(e,t)=>{const n=Qr(e),o=n+t;Yl(n,t)||(e.className=o.substring(1))},di=(e,t)=>{const n=Qr(e);if(!Yl(e,t))return;const o=n.replace(" "+t+" "," ");e.className=o.substring(1,o.length-1)},Qr=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),zd=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)};const Ud=()=>{We(()=>{const e=Gt(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(n=>{t.has(n.path)||de.start()}),e.afterEach(n=>{t.add(n.path),de.done()})})},Kd=Ct({setup(){Ud()}}),qd=JSON.parse(`{"repo":"https://github.com/bcgov/notifybc","packageJson":{"name":"notify-bc","version":"5.1.2","dbSchemaVersion":"0.9.0","description":"A versatile notification API server","author":"f-w","private":true,"main":"dist/main.js","types":"dist/main.d.ts","engines":{"node":">=18"},"repository":{"type":"git","url":"https://github.com/bcgov/notifybc"},"license":"Apache-2.0","scripts":{"build":"nest build","build:client":"cd client && npm run build","build:docs":"cd docs && npm i && npm run build","postbuild":"npm run build:client","install:client":"cd client && npm i","install:docs":"cd docs && npm i","postinstall":"npm run install:client","format":"prettier --write \\"src/**/*.ts\\" \\"test/**/*.ts\\"","start":"nest start","start:dev":"nest start --watch","start:debug":"nest start --debug --watch","start:prod":"node dist/main","lint":"eslint \\"{src,apps,libs,test}/**/*.ts\\" --fix","test":"jest","test:watch":"jest --watch","test:cov":"jest --coverage","test:debug":"node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand","test:e2e":"jest --config ./test/jest-e2e.ts","test:e2e:cov":"jest --config ./test/jest-e2e.ts --coverage"},"dependencies":{"@nestjs/common":"^10.3.8","@nestjs/core":"^10.3.8","@nestjs/mongoose":"^10.0.6","@nestjs/platform-express":"^10.3.8","@nestjs/swagger":"^7.3.1","@nestjs/terminus":"^10.2.3","async":"^3.2.4","axios":"^1.6.8","bcryptjs":"^2.4.3","bottleneck":"^2.19.5","class-transformer":"^0.5.1","class-validator":"^0.14.0","compression":"^1.7.4","cron":"^3.1.5","crypto-random-string":"^3.3.0","ejs":"^3.1.9","feedparser":"^2.2.10","helmet":"^7.0.0","ioredis":"^5.3.2","ip-range-check":"^0.2.0","jmespath":"f-w/jmespath.js#semver:^1.0","js-base64":"^3.7.5","jsonwebtoken":"^9.0.2","lodash":"^4.17.21","mailparser":"^3.6.5","mongodb-memory-server":"^9.2.0","mongoose":"^7.4.5","morgan":"^1.10.0","nodemailer":"^6.9.5","nodemailer-direct-transport":"^3.3.2","pluralize":"^8.0.0","randexp":"^0.5.3","reflect-metadata":"^0.1.13","rxjs":"^7.8.1","semver":"^7.5.4","smtp-server":"^3.13.0","twilio":"^3.7.0","underscore.string":"^3.3.6"},"devDependencies":{"@nestjs/cli":"^10.3.2","@nestjs/schematics":"^10.1.1","@nestjs/testing":"^10.3.8","@types/bcryptjs":"^2.4.3","@types/express":"^4.17.17","@types/jest":"^29.5.2","@types/lodash":"^4.14.197","@types/node":"^20.3.1","@types/supertest":"^2.0.12","@typescript-eslint/eslint-plugin":"^5.59.11","@typescript-eslint/parser":"^5.59.11","commander":"^11.1.0","csvtojson":"^2.0.10","eslint":"^8.42.0","eslint-config-prettier":"^8.8.0","eslint-plugin-prettier":"^4.2.1","jest":"^29.7.0","prettier":"^2.8.8","source-map-support":"^0.5.21","supertest":"^6.3.3","ts-jest":"^29.1.0","ts-node":"^10.9.1","typescript":"^5.1.3"},"optionalDependencies":{"redis-memory-server":"^0.10.0"},"jest":{"moduleFileExtensions":["js","json","ts"],"rootDir":"src","testRegex":".*\\\\.spec\\\\.ts$","transform":{"^.+\\\\.(t|j)s$":"ts-jest"},"collectCoverageFrom":["**/*.(t|j)s"],"coverageDirectory":"../coverage","testEnvironment":"node"}},"logo":"/img/logo.svg","docsDir":"","editLink":false,"contributors":false,"lastUpdated":false,"navbar":[{"text":"Home","link":"/"},{"text":"Docs","link":"/docs/"},{"text":"Help","link":"/help/"}],"sidebarDepth":1,"sidebar":[{"text":"Getting Started","children":["/docs/","/docs/overview/","/docs/quickstart/","/docs/installation/","/docs/web-console/","/docs/what's-new/"]},{"text":"Configuration","children":["/docs/config-overview/","/docs/config-database/","/docs/config-adminIpList/","/docs/config-reverseProxyIpLists/","/docs/config-httpHost/","/docs/config-internalHttpHost/","/docs/config-email/","/docs/config-sms/","/docs/config-subscription/","/docs/config-notification/","/docs/config-nodeRoles/","/docs/config-cronJobs/","/docs/config-rsaKeys/","/docs/config-workerProcessCount/","/docs/config-middleware/","/docs/config-oidc/","/docs/config-certificates/"]},{"text":"API","collapsed":false,"children":["/docs/api-overview/","/docs/api-subscription/","/docs/api-notification/","/docs/api-config/","/docs/api-administrator/","/docs/api-bounce/"]},{"text":"Miscellaneous","children":["/docs/health-check/","/docs/memory-dump/","/docs/benchmarks/","/docs/bulk-import/","/docs/developer-notes/","/docs/upgrade/"]},{"text":"Meta","children":["/docs/conduct/","/docs/acknowledgments/"]}],"locales":{"/":{"selectLanguageName":"English"}},"colorMode":"auto","colorModeSwitch":true,"selectLanguageText":"Languages","selectLanguageAriaLabel":"Select language","editLinkText":"Edit this page","lastUpdatedText":"Last Updated","contributorsText":"Contributors","notFound":["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."],"backToHome":"Take me home","openInNewWindow":"open in new window","toggleColorMode":"toggle color mode","toggleSidebar":"toggle sidebar"}`),Wd=be(qd),Gl=()=>Wd,Zl=Symbol(""),Jd=()=>{const e=Oe(Zl);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},Qd=(e,t)=>{const{locales:n,...o}=e;return{...o,...n==null?void 0:n[t]}},Yd=Ct({enhance({app:e}){const t=Gl(),n=e._context.provides[Fr],o=z(()=>Qd(t.value,n.value));e.provide(Zl,o),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return o.value}}})}}),Gd=fe({__name:"Badge",props:{type:{type:String,required:!1,default:"tip"},text:{type:String,required:!1,default:""},vertical:{type:String,required:!1,default:void 0}},setup(e){return(t,n)=>(H(),Q("span",{class:ze(["badge",e.type]),style:Qt({verticalAlign:e.vertical})},[Ce(t.$slots,"default",{},()=>[Et(Ie(e.text),1)])],6))}}),Le=(e,t)=>{const n=e.__vccOpts||e;for(const[o,r]of t)n[o]=r;return n},Zd=Le(Gd,[["__file","Badge.vue"]]),Xd=fe({name:"CodeGroup",slots:Object,setup(e,{slots:t}){const n=be(-1),o=be([]),r=(l=n.value)=>{l{l>0?n.value=l-1:n.value=o.value.length-1,o.value[n.value].focus()},i=(l,a)=>{l.key===" "||l.key==="Enter"?(l.preventDefault(),n.value=a):l.key==="ArrowRight"?(l.preventDefault(),r(a)):l.key==="ArrowLeft"&&(l.preventDefault(),s(a))};return()=>{var a;const l=(((a=t.default)==null?void 0:a.call(t))||[]).filter(c=>c.type.name==="CodeGroupItem").map(c=>(c.props===null&&(c.props={}),c));return l.length===0?null:(n.value<0||n.value>l.length-1?(n.value=l.findIndex(c=>c.props.active===""||c.props.active===!0),n.value===-1&&(n.value=0)):l.forEach((c,u)=>{c.props.active=u===n.value}),ge("div",{class:"code-group"},[ge("div",{class:"code-group__nav"},ge("ul",{class:"code-group__ul"},l.map((c,u)=>{const f=u===n.value;return ge("li",{class:"code-group__li"},ge("button",{ref:p=>{p&&(o.value[u]=p)},class:{"code-group__nav-tab":!0,"code-group__nav-tab-active":f},ariaPressed:f,ariaExpanded:f,onClick:()=>n.value=u,onKeydown:p=>i(p,u)},c.props.title))}))),l]))}}}),ep=["aria-selected"],tp=fe({name:"CodeGroupItem"}),np=fe({...tp,props:{title:{type:String,required:!0},active:{type:Boolean,required:!1,default:!1}},setup(e){return(t,n)=>(H(),Q("div",{class:ze(["code-group-item",{"code-group-item__active":e.active}]),"aria-selected":e.active},[Ce(t.$slots,"default")],10,ep))}}),op=Le(np,[["__file","CodeGroupItem.vue"]]);var rp=Object.defineProperty,sp=Object.defineProperties,ip=Object.getOwnPropertyDescriptors,pi=Object.getOwnPropertySymbols,lp=Object.prototype.hasOwnProperty,ap=Object.prototype.propertyIsEnumerable,hi=(e,t,n)=>t in e?rp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,cp=(e,t)=>{for(var n in t||(t={}))lp.call(t,n)&&hi(e,n,t[n]);if(pi)for(var n of pi(t))ap.call(t,n)&&hi(e,n,t[n]);return e},up=(e,t)=>sp(e,ip(t));function mi(e,t){var n;const o=Rr();return el(()=>{o.value=e()},up(cp({},t),{flush:(n=t==null?void 0:t.flush)!=null?n:"sync"})),_n(o)}function Xl(e){return Ai()?(Da(e),!0):!1}function Vn(e){return typeof e=="function"?e():X(e)}const fp=typeof window<"u",ea=()=>{};function dp(e,t){function n(...o){return new Promise((r,s)=>{Promise.resolve(e(()=>t.apply(this,o),{fn:t,thisArg:this,args:o})).then(r).catch(s)})}return n}const ta=e=>e();function pp(e=ta){const t=be(!0);function n(){t.value=!1}function o(){t.value=!0}const r=(...s)=>{t.value&&e(...s)};return{isActive:_n(t),pause:n,resume:o,eventFilter:r}}function hp(...e){if(e.length!==1)return hc(...e);const t=e[0];return typeof t=="function"?_n(fc(()=>({get:t,set:ea}))):be(t)}function mp(e=!1,t={}){const{truthyValue:n=!0,falsyValue:o=!1}=t,r=Ae(e),s=be(e);function i(l){if(arguments.length)return s.value=l,s.value;{const a=Vn(n);return s.value=s.value===a?Vn(o):a,s.value}}return r?i:[s,i]}var vi=Object.getOwnPropertySymbols,vp=Object.prototype.hasOwnProperty,gp=Object.prototype.propertyIsEnumerable,_p=(e,t)=>{var n={};for(var o in e)vp.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(e!=null&&vi)for(var o of vi(e))t.indexOf(o)<0&&gp.call(e,o)&&(n[o]=e[o]);return n};function bp(e,t,n={}){const o=n,{eventFilter:r=ta}=o,s=_p(o,["eventFilter"]);return et(e,dp(r,t),s)}var yp=Object.defineProperty,Ep=Object.defineProperties,wp=Object.getOwnPropertyDescriptors,Po=Object.getOwnPropertySymbols,na=Object.prototype.hasOwnProperty,oa=Object.prototype.propertyIsEnumerable,gi=(e,t,n)=>t in e?yp(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Cp=(e,t)=>{for(var n in t||(t={}))na.call(t,n)&&gi(e,n,t[n]);if(Po)for(var n of Po(t))oa.call(t,n)&&gi(e,n,t[n]);return e},Tp=(e,t)=>Ep(e,wp(t)),Lp=(e,t)=>{var n={};for(var o in e)na.call(e,o)&&t.indexOf(o)<0&&(n[o]=e[o]);if(e!=null&&Po)for(var o of Po(e))t.indexOf(o)<0&&oa.call(e,o)&&(n[o]=e[o]);return n};function xp(e,t,n={}){const o=n,{eventFilter:r}=o,s=Lp(o,["eventFilter"]),{eventFilter:i,pause:l,resume:a,isActive:c}=pp(r);return{stop:bp(e,t,Tp(Cp({},s),{eventFilter:i})),pause:l,resume:a,isActive:c}}function Pp(e){var t;const n=Vn(e);return(t=n==null?void 0:n.$el)!=null?t:n}const Oo=fp?window:void 0;function br(...e){let t,n,o,r;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,o,r]=e,t=Oo):[t,n,o,r]=e,!t)return ea;Array.isArray(n)||(n=[n]),Array.isArray(o)||(o=[o]);const s=[],i=()=>{s.forEach(u=>u()),s.length=0},l=(u,f,p,v)=>(u.addEventListener(f,p,v),()=>u.removeEventListener(f,p,v)),a=et(()=>[Pp(t),Vn(r)],([u,f])=>{i(),u&&s.push(...n.flatMap(p=>o.map(v=>l(u,p,v,f))))},{immediate:!0,flush:"post"}),c=()=>{a(),i()};return Xl(c),c}function Op(){const e=be(!1);return yl()&&We(()=>{e.value=!0}),e}function Sp(e){const t=Op();return z(()=>(t.value,!!e()))}function kp(e,t={}){const{window:n=Oo}=t,o=Sp(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let r;const s=be(!1),i=()=>{r&&("removeEventListener"in r?r.removeEventListener("change",l):r.removeListener(l))},l=()=>{o.value&&(i(),r=n.matchMedia(hp(e).value),s.value=!!(r!=null&&r.matches),r&&("addEventListener"in r?r.addEventListener("change",l):r.addListener(l)))};return el(l),Xl(()=>i()),s}const ao=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},co="__vueuse_ssr_handlers__",Ip=Ap();function Ap(){return co in ao||(ao[co]=ao[co]||{}),ao[co]}function Rp(e,t){return Ip[e]||t}function Dp(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}var $p=Object.defineProperty,_i=Object.getOwnPropertySymbols,Mp=Object.prototype.hasOwnProperty,Np=Object.prototype.propertyIsEnumerable,bi=(e,t,n)=>t in e?$p(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,yi=(e,t)=>{for(var n in t||(t={}))Mp.call(t,n)&&bi(e,n,t[n]);if(_i)for(var n of _i(t))Np.call(t,n)&&bi(e,n,t[n]);return e};const Hp={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},Ei="vueuse-storage";function Bp(e,t,n,o={}){var r;const{flush:s="pre",deep:i=!0,listenToStorageChanges:l=!0,writeDefaults:a=!0,mergeDefaults:c=!1,shallow:u,window:f=Oo,eventFilter:p,onError:v=m=>{console.error(m)}}=o,y=(u?Rr:be)(t);if(!n)try{n=Rp("getDefaultStorage",()=>{var m;return(m=Oo)==null?void 0:m.localStorage})()}catch(m){v(m)}if(!n)return y;const w=Vn(t),x=Dp(w),g=(r=o.serializer)!=null?r:Hp[x],{pause:b,resume:I}=xp(y,()=>k(y.value),{flush:s,deep:i,eventFilter:p});return f&&l&&(br(f,"storage",N),br(f,Ei,ee)),N(),y;function k(m){try{if(m==null)n.removeItem(e);else{const F=g.write(m),B=n.getItem(e);B!==F&&(n.setItem(e,F),f&&f.dispatchEvent(new CustomEvent(Ei,{detail:{key:e,oldValue:B,newValue:F,storageArea:n}})))}}catch(F){v(F)}}function W(m){const F=m?m.newValue:n.getItem(e);if(F==null)return a&&w!==null&&n.setItem(e,g.write(w)),w;if(!m&&c){const B=g.read(F);return typeof c=="function"?c(B,w):x==="object"&&!Array.isArray(B)?yi(yi({},w),B):B}else return typeof F!="string"?F:g.read(F)}function ee(m){N(m.detail)}function N(m){if(!(m&&m.storageArea!==n)){if(m&&m.key==null){y.value=w;return}if(!(m&&m.key!==e)){b();try{y.value=W(m)}catch(F){v(F)}finally{m?Do(I):I()}}}}}function jp(e){return kp("(prefers-color-scheme: dark)",e)}const Vp=()=>Gl(),Fe=()=>Jd(),ra=Symbol(""),Yr=()=>{const e=Oe(ra);if(!e)throw new Error("useDarkMode() is called without provider.");return e},Fp=()=>{const e=Fe(),t=jp(),n=Bp("vuepress-color-scheme",e.value.colorMode),o=z({get(){return e.value.colorModeSwitch?n.value==="auto"?t.value:n.value==="dark":e.value.colorMode==="dark"},set(r){r===t.value?n.value="auto":n.value=r?"dark":"light"}});Wt(ra,o),zp(o)},zp=e=>{const t=(n=e.value)=>{const o=window==null?void 0:window.document.querySelector("html");o==null||o.classList.toggle("dark",n)};We(()=>{et(e,t,{immediate:!0})}),Bo(()=>t())},sa=(...e)=>{const n=Gt().resolve(...e),o=n.matched[n.matched.length-1];if(!(o!=null&&o.redirect))return n;const{redirect:r}=o,s=re(r)?r(n):r,i=me(s)?{path:s}:s;return sa({hash:n.hash,query:n.query,params:n.params,...i})},Gr=e=>{const t=sa(encodeURI(e));return{text:t.meta.title||e,link:t.name==="404"?e:t.fullPath}};let nr=null,Cn=null;const Up={wait:()=>nr,pending:()=>{nr=new Promise(e=>Cn=e)},resolve:()=>{Cn==null||Cn(),nr=null,Cn=null}},ia=()=>Up,la=Symbol("sidebarItems"),Zr=()=>{const e=Oe(la);if(!e)throw new Error("useSidebarItems() is called without provider.");return e},Kp=()=>{const e=Fe(),t=vt(),n=z(()=>qp(t.value,e.value));Wt(la,n)},qp=(e,t)=>{const n=e.sidebar??t.sidebar??"auto",o=e.sidebarDepth??t.sidebarDepth??2;return e.home||n===!1?[]:n==="auto"?Jp(o):q(n)?aa(n,o):Vr(n)?Qp(n,o):[]},Wp=(e,t)=>({text:e.title,link:e.link,children:Xr(e.children,t)}),Xr=(e,t)=>t>0?e.map(n=>Wp(n,t-1)):[],Jp=e=>{const t=$t();return[{text:t.value.title,children:Xr(t.value.headers,e)}]},aa=(e,t)=>{const n=Zt(),o=$t(),r=s=>{var l;let i;if(me(s)?i=Gr(s):i=s,i.children)return{...i,children:i.children.map(a=>r(a))};if(i.link===n.path){const a=((l=o.value.headers[0])==null?void 0:l.level)===1?o.value.headers[0].children:o.value.headers;return{...i,children:Xr(a,t)}}return i};return e.map(s=>r(s))},Qp=(e,t)=>{const n=Zt(),o=Sl(e,n.path),r=e[o]??[];return aa(r,t)},Yp="719px",Gp={mobile:Yp};var Fn;(function(e){e.MOBILE="mobile"})(Fn||(Fn={}));var Li;const Zp={[Fn.MOBILE]:Number.parseInt((Li=Gp.mobile)==null?void 0:Li.replace("px",""),10)},ca=(e,t)=>{const n=Zp[e];Number.isInteger(n)&&We(()=>{t(n),window.addEventListener("resize",()=>t(n),!1),window.addEventListener("orientationchange",()=>t(n),!1)})},Xp={},eh={class:"theme-default-content"};function th(e,t){const n=bt("Content");return H(),Q("div",eh,[ne(n)])}const nh=Le(Xp,[["render",th],["__file","HomeContent.vue"]]),oh={key:0,class:"features"},rh=["innerHTML"],sh=fe({__name:"myHomeFeatures",setup(e){const t=vt(),n=z(()=>q(t.value.features)?t.value.features:[]);return(o,r)=>n.value.length?(H(),Q("div",oh,[(H(!0),Q(Ee,null,yt(n.value,s=>(H(),Q("div",{key:s.title,class:"feature"},[ae("h2",null,Ie(s.title),1),ae("div",{innerHTML:s.details},null,8,rh)]))),128))])):xe("v-if",!0)}}),ih=Le(sh,[["__file","myHomeFeatures.vue"]]),lh=["innerHTML"],ah=["textContent"],ch=fe({__name:"HomeFooter",setup(e){const t=vt(),n=z(()=>t.value.footer),o=z(()=>t.value.footerHtml);return(r,s)=>n.value?(H(),Q(Ee,{key:0},[xe(" eslint-disable-next-line vue/no-v-html "),o.value?(H(),Q("div",{key:0,class:"footer",innerHTML:n.value},null,8,lh)):(H(),Q("div",{key:1,class:"footer",textContent:Ie(n.value)},null,8,ah))],64)):xe("v-if",!0)}}),uh=Le(ch,[["__file","HomeFooter.vue"]]),fh=["href","rel","target","aria-label"],dh=fe({inheritAttrs:!1}),ph=fe({...dh,__name:"AutoLink",props:{item:{type:Object,required:!0}},setup(e){const t=e,n=Zt(),o=Nl(),{item:r}=Dr(t),s=z(()=>Yn(r.value.link)),i=z(()=>of(r.value.link)||rf(r.value.link)),l=z(()=>{if(!i.value){if(r.value.target)return r.value.target;if(s.value)return"_blank"}}),a=z(()=>l.value==="_blank"),c=z(()=>!s.value&&!i.value&&!a.value),u=z(()=>{if(!i.value){if(r.value.rel)return r.value.rel;if(a.value)return"noopener noreferrer"}}),f=z(()=>r.value.ariaLabel||r.value.text),p=z(()=>{const w=Object.keys(o.value.locales);return w.length?!w.some(x=>x===r.value.link):r.value.link!=="/"}),v=z(()=>p.value?n.path.startsWith(r.value.link):!1),y=z(()=>c.value?r.value.activeMatch?new RegExp(r.value.activeMatch).test(n.path):v.value:!1);return(w,x)=>{const g=bt("RouterLink"),b=bt("AutoLinkExternalIcon");return c.value?(H(),Re(g,hr({key:0,class:{"router-link-active":y.value},to:X(r).link,"aria-label":f.value},w.$attrs),{default:Me(()=>[Ce(w.$slots,"before"),Et(" "+Ie(X(r).text)+" ",1),Ce(w.$slots,"after")]),_:3},16,["class","to","aria-label"])):(H(),Q("a",hr({key:1,class:"external-link",href:X(r).link,rel:u.value,target:l.value,"aria-label":f.value},w.$attrs),[Ce(w.$slots,"before"),Et(" "+Ie(X(r).text)+" ",1),a.value?(H(),Re(b,{key:0})):xe("v-if",!0),Ce(w.$slots,"after")],16,fh))}}}),gt=Le(ph,[["__file","AutoLink.vue"]]),hh={class:"hero"},mh={key:0,id:"main-title"},vh={key:1,class:"description"},gh={key:2,class:"actions"},_h=fe({__name:"HomeHero",setup(e){const t=vt(),n=zr(),o=Yr(),r=z(()=>o.value&&t.value.heroImageDark!==void 0?t.value.heroImageDark:t.value.heroImage),s=z(()=>t.value.heroAlt||l.value||"hero"),i=z(()=>t.value.heroHeight||280),l=z(()=>t.value.heroText===null?null:t.value.heroText||n.value.title||"Hello"),a=z(()=>t.value.tagline===null?null:t.value.tagline||n.value.description||"Welcome to your VuePress site"),c=z(()=>q(t.value.actions)?t.value.actions.map(({text:f,link:p,type:v="primary"})=>({text:f,link:p,type:v})):[]),u=()=>{if(!r.value)return null;const f=ge("img",{src:Kr(r.value),alt:s.value,height:i.value});return t.value.heroImageDark===void 0?f:ge(Ur,()=>f)};return(f,p)=>(H(),Q("header",hh,[ne(u),l.value?(H(),Q("h1",mh,Ie(l.value),1)):xe("v-if",!0),a.value?(H(),Q("p",vh,Ie(a.value),1)):xe("v-if",!0),c.value.length?(H(),Q("p",gh,[(H(!0),Q(Ee,null,yt(c.value,v=>(H(),Re(gt,{key:v.text,class:ze(["action-button",[v.type]]),item:v},null,8,["class","item"]))),128))])):xe("v-if",!0)]))}}),bh=Le(_h,[["__file","HomeHero.vue"]]),yh={class:"home"},Eh=fe({__name:"Home",setup(e){return(t,n)=>(H(),Q("main",yh,[ne(bh),ne(ih),ne(nh),ne(uh)]))}}),wh=Le(Eh,[["__file","Home.vue"]]);const Ch={data(){return{selected:void 0,options:[]}},created:async function(){try{let e;const t=sessionStorage.getItem("versions");if(t)try{e=JSON.parse(t)}catch{}if(!e){let o=await(await fetch("https://api.github.com/repos/bcgov/NotifyBC/git/trees/gh-pages")).json();const r=o.tree.find(s=>s.path.toLowerCase()==="version");o=await(await fetch(r.url)).json(),e=o.tree.map(s=>({value:s.path,text:s.path})),e.sort((s,i)=>{const l=s.text.split("."),a=i.text.split(".");for(let c=0;c=0&&(o=r+9);const s=n.indexOf("/",o);window.location.pathname=window.location.pathname.substring(0,9)+t+window.location.pathname.substring(s)}}},Th={key:0},Lh=["value"];function xh(e,t,n,o,r,s){return r.options&&r.options.length>0?(H(),Q("span",Th,[Et(" Version: "),Hn(ae("select",{"onUpdate:modelValue":t[0]||(t[0]=i=>r.selected=i),onChange:t[1]||(t[1]=(...i)=>s.onChange&&s.onChange(...i))},[(H(!0),Q(Ee,null,yt(r.options,i=>(H(),Q("option",{key:i.value,value:i.value},Ie(i.text),9,Lh))),128))],544),[[qu,r.selected]])])):xe("v-if",!0)}const Ph=Le(Ch,[["render",xh],["__scopeId","data-v-888e697c"],["__file","versions.vue"]]),Oh={class:"nb-navbar-brand"},Sh=fe({__name:"myNavbarBrand",setup(e){const t=Gn(),n=zr(),o=Fe(),r=Yr(),s=z(()=>o.value.home||t.value),i=z(()=>n.value.title),l=z(()=>r.value&&o.value.logoDark!==void 0?o.value.logoDark:o.value.logo),a=()=>{if(!l.value)return null;const c=ge("img",{class:"logo",src:Kr(l.value),alt:i.value});return o.value.logoDark===void 0?c:ge(Ur,()=>c)};return(c,u)=>{const f=bt("RouterLink");return H(),Q("div",Oh,[ne(f,{to:s.value},{default:Me(()=>[ne(a)]),_:1},8,["to"]),ne(Ph)])}}});const kh=Le(Sh,[["__file","myNavbarBrand.vue"]]),Ih=fe({__name:"DropdownTransition",setup(e){const t=o=>{o.style.height=o.scrollHeight+"px"},n=o=>{o.style.height=""};return(o,r)=>(H(),Re(Qn,{name:"dropdown",onEnter:t,onAfterEnter:n,onBeforeLeave:t},{default:Me(()=>[Ce(o.$slots,"default")]),_:3}))}}),ua=Le(Ih,[["__file","DropdownTransition.vue"]]),Ah=["aria-label"],Rh={class:"title"},Dh=ae("span",{class:"arrow down"},null,-1),$h=["aria-label"],Mh={class:"title"},Nh={class:"navbar-dropdown"},Hh={class:"navbar-dropdown-subtitle"},Bh={key:1},jh={class:"navbar-dropdown-subitem-wrapper"},Vh=fe({__name:"NavbarDropdown",props:{item:{type:Object,required:!0}},setup(e){const t=e,{item:n}=Dr(t),o=z(()=>n.value.ariaLabel||n.value.text),r=be(!1),s=Zt();et(()=>s.path,()=>{r.value=!1});const i=a=>{a.detail===0?r.value=!r.value:r.value=!1},l=(a,c)=>c[c.length-1]===a;return(a,c)=>(H(),Q("div",{class:ze(["navbar-dropdown-wrapper",{open:r.value}])},[ae("button",{class:"navbar-dropdown-title",type:"button","aria-label":o.value,onClick:i},[ae("span",Rh,Ie(X(n).text),1),Dh],8,Ah),ae("button",{class:"navbar-dropdown-title-mobile",type:"button","aria-label":o.value,onClick:c[0]||(c[0]=u=>r.value=!r.value)},[ae("span",Mh,Ie(X(n).text),1),ae("span",{class:ze(["arrow",r.value?"down":"right"])},null,2)],8,$h),ne(ua,null,{default:Me(()=>[Hn(ae("ul",Nh,[(H(!0),Q(Ee,null,yt(X(n).children,u=>(H(),Q("li",{key:u.text,class:"navbar-dropdown-item"},[u.children?(H(),Q(Ee,{key:0},[ae("h4",Hh,[u.link?(H(),Re(gt,{key:0,item:u,onFocusout:f=>l(u,X(n).children)&&u.children.length===0&&(r.value=!1)},null,8,["item","onFocusout"])):(H(),Q("span",Bh,Ie(u.text),1))]),ae("ul",jh,[(H(!0),Q(Ee,null,yt(u.children,f=>(H(),Q("li",{key:f.link,class:"navbar-dropdown-subitem"},[ne(gt,{item:f,onFocusout:p=>l(f,u.children)&&l(u,X(n).children)&&(r.value=!1)},null,8,["item","onFocusout"])]))),128))])],64)):(H(),Re(gt,{key:1,item:u,onFocusout:f=>l(u,X(n).children)&&(r.value=!1)},null,8,["item","onFocusout"]))]))),128))],512),[[Lo,r.value]])]),_:1})],2))}}),Fh=Le(Vh,[["__file","NavbarDropdown.vue"]]),wi=e=>decodeURI(e).replace(/#.*$/,"").replace(/(index)?\.(md|html)$/,""),zh=(e,t)=>{if(t.hash===e)return!0;const n=wi(t.path),o=wi(e);return n===o},fa=(e,t)=>e.link&&zh(e.link,t)?!0:e.children?e.children.some(n=>fa(n,t)):!1,da=e=>!Yn(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,Uh={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},Kh=({docsRepo:e,editLinkPattern:t})=>{if(t)return t;const n=da(e);return n!==null?Uh[n]:null},qh=({docsRepo:e,docsBranch:t,docsDir:n,filePathRelative:o,editLinkPattern:r})=>{if(!o)return null;const s=Kh({docsRepo:e,editLinkPattern:r});return s?s.replace(/:repo/,Yn(e)?e:`https://github.com/${e}`).replace(/:branch/,t).replace(/:path/,Ol(`${Pl(n)}/${o}`)):null},Wh={key:0,class:"navbar-items"},Jh=fe({__name:"NavbarItems",setup(e){const t=()=>{const u=Gt(),f=Gn(),p=Nl(),v=zr(),y=Vp(),w=Fe();return z(()=>{const x=Object.keys(p.value.locales);if(x.length<2)return[];const g=u.currentRoute.value.path,b=u.currentRoute.value.fullPath;return[{text:`${w.value.selectLanguageText}`,ariaLabel:`${w.value.selectLanguageAriaLabel??w.value.selectLanguageText}`,children:x.map(k=>{var B,Y;const W=((B=p.value.locales)==null?void 0:B[k])??{},ee=((Y=y.value.locales)==null?void 0:Y[k])??{},N=`${W.lang}`,m=ee.selectLanguageName??N;let F;if(N===v.value.lang)F=b;else{const L=g.replace(f.value,k);u.getRoutes().some(R=>R.path===L)?F=b.replace(g,L):F=ee.home??k}return{text:m,link:F}})}]})},n=()=>{const u=Fe(),f=z(()=>u.value.repo),p=z(()=>f.value?da(f.value):null),v=z(()=>f.value&&!Yn(f.value)?`https://github.com/${f.value}`:f.value),y=z(()=>v.value?u.value.repoLabel?u.value.repoLabel:p.value===null?"Source":p.value:null);return z(()=>!v.value||!y.value?[]:[{text:y.value,link:v.value}])},o=u=>me(u)?Gr(u):u.children?{...u,children:u.children.map(o)}:u,r=()=>{const u=Fe();return z(()=>(u.value.navbar||[]).map(o))},s=be(!1),i=r(),l=t(),a=n(),c=z(()=>[...i.value,...l.value,...a.value]);return ca(Fn.MOBILE,u=>{window.innerWidthc.value.length?(H(),Q("nav",Wh,[(H(!0),Q(Ee,null,yt(c.value,p=>(H(),Q("div",{key:p.text,class:"navbar-item"},[p.children?(H(),Re(Fh,{key:0,item:p,class:ze(s.value?"mobile":"")},null,8,["item","class"])):(H(),Re(gt,{key:1,item:p},null,8,["item"]))]))),128))])):xe("v-if",!0)}}),pa=Le(Jh,[["__file","NavbarItems.vue"]]),Qh=["title"],Yh={class:"icon",focusable:"false",viewBox:"0 0 32 32"},Gh=du('',9),Zh=[Gh],Xh={class:"icon",focusable:"false",viewBox:"0 0 32 32"},em=ae("path",{d:"M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3z",fill:"currentColor"},null,-1),tm=[em],nm=fe({__name:"ToggleColorModeButton",setup(e){const t=Fe(),n=Yr(),o=()=>{n.value=!n.value};return(r,s)=>(H(),Q("button",{class:"toggle-color-mode-button",title:X(t).toggleColorMode,onClick:o},[Hn((H(),Q("svg",Yh,Zh,512)),[[Lo,!X(n)]]),Hn((H(),Q("svg",Xh,tm,512)),[[Lo,X(n)]])],8,Qh))}}),om=Le(nm,[["__file","ToggleColorModeButton.vue"]]),rm=["title"],sm=ae("div",{class:"icon","aria-hidden":"true"},[ae("span"),ae("span"),ae("span")],-1),im=[sm],lm=fe({__name:"ToggleSidebarButton",emits:["toggle"],setup(e){const t=Fe();return(n,o)=>(H(),Q("div",{class:"toggle-sidebar-button",title:X(t).toggleSidebar,"aria-expanded":"false",role:"button",tabindex:"0",onClick:o[0]||(o[0]=r=>n.$emit("toggle"))},im,8,rm))}}),am=Le(lm,[["__file","ToggleSidebarButton.vue"]]),cm=fe({__name:"Navbar",emits:["toggle-sidebar"],setup(e){const t=Fe(),n=be(null),o=be(null),r=be(0),s=z(()=>r.value?{maxWidth:r.value+"px"}:{});ca(Fn.MOBILE,l=>{var c;const a=i(n.value,"paddingLeft")+i(n.value,"paddingRight");window.innerWidth{const c=bt("NavbarSearch");return H(),Q("header",{ref_key:"navbar",ref:n,class:"navbar"},[ne(am,{onToggle:a[0]||(a[0]=u=>l.$emit("toggle-sidebar"))}),ae("span",{ref_key:"navbarBrand",ref:o},[ne(kh)],512),ae("div",{class:"navbar-items-wrapper",style:Qt(s.value)},[Ce(l.$slots,"before"),ne(pa,{class:"can-hide"}),Ce(l.$slots,"after"),X(t).colorModeSwitch?(H(),Re(om,{key:0})):xe("v-if",!0),ne(c)],4)],512)}}}),um=Le(cm,[["__file","Navbar.vue"]]),fm={class:"page-meta"},dm={key:0,class:"meta-item edit-link"},pm={key:1,class:"meta-item last-updated"},hm={class:"meta-item-label"},mm={class:"meta-item-info"},vm={key:2,class:"meta-item contributors"},gm={class:"meta-item-label"},_m={class:"meta-item-info"},bm=["title"],ym=fe({__name:"PageMeta",setup(e){const t=()=>{const a=Fe(),c=$t(),u=vt();return z(()=>{if(!(u.value.editLink??a.value.editLink??!0))return null;const{repo:p,docsRepo:v=p,docsBranch:y="main",docsDir:w="",editLinkText:x}=a.value;if(!v)return null;const g=qh({docsRepo:v,docsBranch:y,docsDir:w,filePathRelative:c.value.filePathRelative,editLinkPattern:u.value.editLinkPattern??a.value.editLinkPattern});return g?{text:x??"Edit this page",link:g}:null})},n=()=>{const a=Fe(),c=$t(),u=vt();return z(()=>{var v,y;return!(u.value.lastUpdated??a.value.lastUpdated??!0)||!((v=c.value.git)!=null&&v.updatedTime)?null:new Date((y=c.value.git)==null?void 0:y.updatedTime).toLocaleString()})},o=()=>{const a=Fe(),c=$t(),u=vt();return z(()=>{var p;return u.value.contributors??a.value.contributors??!0?((p=c.value.git)==null?void 0:p.contributors)??null:null})},r=Fe(),s=t(),i=n(),l=o();return(a,c)=>{const u=bt("ClientOnly");return H(),Q("footer",fm,[X(s)?(H(),Q("div",dm,[ne(gt,{class:"meta-item-label",item:X(s)},null,8,["item"])])):xe("v-if",!0),X(i)?(H(),Q("div",pm,[ae("span",hm,Ie(X(r).lastUpdatedText)+": ",1),ne(u,null,{default:Me(()=>[ae("span",mm,Ie(X(i)),1)]),_:1})])):xe("v-if",!0),X(l)&&X(l).length?(H(),Q("div",vm,[ae("span",gm,Ie(X(r).contributorsText)+": ",1),ae("span",_m,[(H(!0),Q(Ee,null,yt(X(l),(f,p)=>(H(),Q(Ee,{key:p},[ae("span",{class:"contributor",title:`email: ${f.email}`},Ie(f.name),9,bm),p!==X(l).length-1?(H(),Q(Ee,{key:0},[Et(", ")],64)):xe("v-if",!0)],64))),128))])])):xe("v-if",!0)])}}}),Em=Le(ym,[["__file","PageMeta.vue"]]),wm={key:0,class:"page-nav"},Cm={class:"inner"},Tm={key:0,class:"prev"},Lm={key:1,class:"next"},xm=fe({__name:"PageNav",setup(e){const t=a=>a===!1?null:me(a)?Gr(a):Vr(a)?a:!1,n=(a,c,u)=>{const f=a.findIndex(p=>p.link===c);if(f!==-1){const p=a[f+u];return p!=null&&p.link?p:null}for(const p of a)if(p.children){const v=n(p.children,c,u);if(v)return v}return null},o=vt(),r=Zr(),s=Zt(),i=z(()=>{const a=t(o.value.prev);return a!==!1?a:n(r.value,s.path,-1)}),l=z(()=>{const a=t(o.value.next);return a!==!1?a:n(r.value,s.path,1)});return(a,c)=>i.value||l.value?(H(),Q("nav",wm,[ae("p",Cm,[i.value?(H(),Q("span",Tm,[ne(gt,{item:i.value},null,8,["item"])])):xe("v-if",!0),l.value?(H(),Q("span",Lm,[ne(gt,{item:l.value},null,8,["item"])])):xe("v-if",!0)])])):xe("v-if",!0)}}),Pm=Le(xm,[["__file","PageNav.vue"]]),Om={class:"page"},Sm={class:"theme-default-content"},km=fe({__name:"Page",setup(e){return(t,n)=>{const o=bt("Content");return H(),Q("main",Om,[Ce(t.$slots,"top"),ae("div",Sm,[Ce(t.$slots,"content-top"),ne(o),Ce(t.$slots,"content-bottom")]),ne(Em),ne(Pm),Ce(t.$slots,"bottom")])}}}),Im=Le(km,[["__file","Page.vue"]]),Am=["onKeydown"],Rm={class:"sidebar-item-children"},Dm=fe({__name:"SidebarItem",props:{item:{type:Object,required:!0},depth:{type:Number,required:!1,default:0}},setup(e){const t=e,{item:n,depth:o}=Dr(t),r=Zt(),s=Gt(),i=z(()=>fa(n.value,r)),l=z(()=>({"sidebar-item":!0,"sidebar-heading":o.value===0,active:i.value,collapsible:n.value.collapsible})),a=z(()=>n.value.collapsible?i.value:!0),[c,u]=mp(a.value),f=v=>{n.value.collapsible&&(v.preventDefault(),u())},p=s.afterEach(v=>{Do(()=>{c.value=a.value})});return Jn(()=>{p()}),(v,y)=>{var x;const w=bt("SidebarItem",!0);return H(),Q("li",null,[X(n).link?(H(),Re(gt,{key:0,class:ze(l.value),item:X(n)},null,8,["class","item"])):(H(),Q("p",{key:1,tabindex:"0",class:ze(l.value),onClick:f,onKeydown:Ju(f,["enter"])},[Et(Ie(X(n).text)+" ",1),X(n).collapsible?(H(),Q("span",{key:0,class:ze(["arrow",X(c)?"down":"right"])},null,2)):xe("v-if",!0)],42,Am)),(x=X(n).children)!=null&&x.length?(H(),Re(ua,{key:2},{default:Me(()=>[Hn(ae("ul",Rm,[(H(!0),Q(Ee,null,yt(X(n).children,g=>(H(),Re(w,{key:`${X(o)}${g.text}${g.link}`,item:g,depth:X(o)+1},null,8,["item","depth"]))),128))],512),[[Lo,X(c)]])]),_:1})):xe("v-if",!0)])}}}),$m=Le(Dm,[["__file","SidebarItem.vue"]]),Mm={key:0,class:"sidebar-items"},Nm=fe({__name:"SidebarItems",setup(e){const t=Zt(),n=Zr();return We(()=>{et(()=>t.hash,o=>{const r=document.querySelector(".sidebar");if(!r)return;const s=document.querySelector(`.sidebar a.sidebar-item[href="${t.path}${o}"]`);if(!s)return;const{top:i,height:l}=r.getBoundingClientRect(),{top:a,height:c}=s.getBoundingClientRect();ai+l&&s.scrollIntoView(!1)})}),(o,r)=>X(n).length?(H(),Q("ul",Mm,[(H(!0),Q(Ee,null,yt(X(n),s=>(H(),Re($m,{key:`${s.text}${s.link}`,item:s},null,8,["item"]))),128))])):xe("v-if",!0)}}),Hm=Le(Nm,[["__file","SidebarItems.vue"]]),Bm={class:"sidebar"},jm=fe({__name:"Sidebar",setup(e){return(t,n)=>(H(),Q("aside",Bm,[ne(pa),Ce(t.$slots,"top"),ne(Hm),Ce(t.$slots,"bottom")]))}}),Vm=Le(jm,[["__file","Sidebar.vue"]]),Fm=fe({__name:"Layout",setup(e){const t=$t(),n=vt(),o=Fe(),r=z(()=>n.value.navbar!==!1&&o.value.navbar!==!1),s=Zr(),i=be(!1),l=x=>{i.value=typeof x=="boolean"?x:!i.value},a={x:0,y:0},c=x=>{a.x=x.changedTouches[0].clientX,a.y=x.changedTouches[0].clientY},u=x=>{const g=x.changedTouches[0].clientX-a.x,b=x.changedTouches[0].clientY-a.y;Math.abs(g)>Math.abs(b)&&Math.abs(g)>40&&(g>0&&a.x<=80?l(!0):l(!1))},f=z(()=>[{"no-navbar":!r.value,"no-sidebar":!s.value.length,"sidebar-open":i.value},n.value.pageClass]);let p;We(()=>{p=Gt().afterEach(()=>{l(!1)})}),Bo(()=>{p()});const v=ia(),y=v.resolve,w=v.pending;return(x,g)=>(H(),Q("div",{class:ze(["theme-container",f.value]),onTouchstart:c,onTouchend:u},[Ce(x.$slots,"navbar",{},()=>[r.value?(H(),Re(um,{key:0,onToggleSidebar:l},{before:Me(()=>[Ce(x.$slots,"navbar-before")]),after:Me(()=>[Ce(x.$slots,"navbar-after")]),_:3})):xe("v-if",!0)]),ae("div",{class:"sidebar-mask",onClick:g[0]||(g[0]=b=>l(!1))}),Ce(x.$slots,"sidebar",{},()=>[ne(Vm,null,{top:Me(()=>[Ce(x.$slots,"sidebar-top")]),bottom:Me(()=>[Ce(x.$slots,"sidebar-bottom")]),_:3})]),Ce(x.$slots,"page",{},()=>[X(n).home?(H(),Re(wh,{key:0})):(H(),Re(Qn,{key:1,name:"fade-slide-y",mode:"out-in",onBeforeEnter:X(y),onBeforeLeave:X(w)},{default:Me(()=>[(H(),Re(Im,{key:X(t).path},{top:Me(()=>[Ce(x.$slots,"page-top")]),"content-top":Me(()=>[Ce(x.$slots,"page-content-top")]),"content-bottom":Me(()=>[Ce(x.$slots,"page-content-bottom")]),bottom:Me(()=>[Ce(x.$slots,"page-bottom")]),_:3}))]),_:3},8,["onBeforeEnter","onBeforeLeave"]))])],34))}}),zm=Le(Fm,[["__file","Layout.vue"]]),Um={class:"theme-container"},Km={class:"page"},qm={class:"theme-default-content"},Wm=ae("h1",null,"404",-1),Jm=fe({__name:"NotFound",setup(e){const t=Gn(),n=Fe(),o=n.value.notFound??["Not Found"],r=()=>o[Math.floor(Math.random()*o.length)],s=n.value.home??t.value,i=n.value.backToHome??"Back to home";return(l,a)=>{const c=bt("RouterLink");return H(),Q("div",Um,[ae("main",Km,[ae("div",qm,[Wm,ae("blockquote",null,Ie(r()),1),ne(c,{to:X(s)},{default:Me(()=>[Et(Ie(X(i)),1)]),_:1},8,["to"])])])])}}}),Qm=Le(Jm,[["__file","NotFound.vue"]]);const Ym=Ct({enhance({app:e,router:t}){e.component("Badge",Zd),e.component("CodeGroup",Xd),e.component("CodeGroupItem",op),e.component("AutoLinkExternalIcon",()=>{const o=e.component("ExternalLinkIcon");return o?ge(o):null}),e.component("NavbarSearch",()=>{const o=e.component("Docsearch")||e.component("SearchBox");return o?ge(o):null});const n=t.options.scrollBehavior;t.options.scrollBehavior=async(...o)=>(await ia().wait(),n(...o))},setup(){Fp(),Kp()},layouts:{Layout:zm,NotFound:Qm}}),Gm=e=>{const t=br("keydown",n=>{const o=n.key==="k"&&(n.ctrlKey||n.metaKey);!(n.key==="/")&&!o||(n.preventDefault(),e(),t())})},Zm=e=>e.button===1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey,Xm=()=>{const e=Gt();return{hitComponent:({hit:t,children:n})=>({type:"a",ref:void 0,constructor:void 0,key:void 0,props:{href:t.url,onClick:o=>{Zm(o)||(o.preventDefault(),e.push(zs(t.url,"/NotifyBC/")))},children:n},__v:null}),navigator:{navigate:({itemUrl:t})=>{e.push(zs(t,"/NotifyBC/"))}},transformSearchClient:t=>{const n=qr(t.search,500);return{...t,search:async(...o)=>n(...o)}}}},ev=(e=[],t)=>[`lang:${t}`,...q(e)?e:[e]],tv=({buttonText:e="Search",buttonAriaLabel:t=e}={})=>``,nv=16,ha=()=>{if(document.querySelector(".DocSearch-Modal"))return;const e=new Event("keydown");e.key="k",e.metaKey=!0,window.dispatchEvent(e),setTimeout(ha,nv)},ov=e=>{const t="algolia-preconnect";(window.requestIdleCallback||setTimeout)(()=>{if(document.head.querySelector(`#${t}`))return;const o=document.createElement("link");o.id=t,o.rel="preconnect",o.href=`https://${e}-dsn.algolia.net`,o.crossOrigin="",document.head.appendChild(o)})},rv={apiKey:"c28cbfc8ec48e407e775c3a574dcd775",appId:"JNUID4IQ3B",indexName:"notifybc"};O(()=>import("./style-e9220a04.js"),[]),O(()=>import("./docsearch-1d421ddb.js"),[]);const sv=fe({name:"Docsearch",props:{containerId:{type:String,required:!1,default:"docsearch-container"},options:{type:Object,required:!1,default:()=>rv}},setup(e){const t=Xm(),n=$l(),o=Gn(),r=be(!1),s=be(!1),i=z(()=>{var c;return{...e.options,...(c=e.options.locales)==null?void 0:c[o.value]}}),l=async()=>{var u;const{default:c}=await O(()=>import("./index-5161ad19.js"),[]);c({...t,...i.value,container:`#${e.containerId}`,searchParameters:{...i.value.searchParameters,facetFilters:ev((u=i.value.searchParameters)==null?void 0:u.facetFilters,n.value)}}),r.value=!0},a=()=>{s.value||r.value||(s.value=!0,l(),ha(),et(o,l))};return Gm(a),We(()=>ov(i.value.appId)),()=>{var c;return[ge("div",{id:e.containerId,style:{display:r.value?"block":"none"}}),r.value?null:ge("div",{onClick:a,innerHTML:tv((c=i.value.translations)==null?void 0:c.button)})]}}}),iv=Ct({enhance({app:e}){e.component("Docsearch",sv)}});const lv={name:"CodeCopy",props:{parent:Object,code:String,options:{align:String,color:String,backgroundTransition:Boolean,backgroundColor:String,successText:String,successTextColor:String,staticIcon:Boolean}},data(){return{success:!1,originalBackground:null,originalTransition:null}},computed:{alignStyle(){let e={};return e[this.options.align]="7.5px",e},iconClass(){return this.options.staticIcon?"":"hover"}},mounted(){this.originalTransition=this.parent.style.transition,this.originalBackground=this.parent.style.background},beforeDestroy(){this.parent.style.transition=this.originalTransition,this.parent.style.background=this.originalBackground},methods:{hexToRgb(e){let t=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return t?{r:parseInt(t[1],16),g:parseInt(t[2],16),b:parseInt(t[3],16)}:null},copyToClipboard(e){if(navigator.clipboard)navigator.clipboard.writeText(this.code).then(()=>{this.setSuccessTransitions()},()=>{});else{let t=document.createElement("textarea");document.body.appendChild(t),t.value=this.code,t.select(),document.execCommand("Copy"),t.remove(),this.setSuccessTransitions()}},setSuccessTransitions(){if(clearTimeout(this.successTimeout),this.options.backgroundTransition){this.parent.style.transition="background 350ms";let e=this.hexToRgb(this.options.backgroundColor);this.parent.style.background=`rgba(${e.r}, ${e.g}, ${e.b}, 0.1)`}this.success=!0,this.successTimeout=setTimeout(()=>{this.options.backgroundTransition&&(this.parent.style.background=this.originalBackground,this.parent.style.transition=this.originalTransition),this.success=!1},500)}}},av=e=>(Cc("data-v-1b705319"),e=e(),Tc(),e),cv={class:"code-copy"},uv=av(()=>ae("path",{fill:"none",d:"M0 0h24v24H0z"},null,-1)),fv=["fill"];function dv(e,t,n,o,r,s){return H(),Q("div",cv,[(H(),Q("svg",{onClick:t[0]||(t[0]=(...i)=>s.copyToClipboard&&s.copyToClipboard(...i)),xmlns:"http://www.w3.org/2000/svg",width:"24",height:"24",viewBox:"0 0 24 24",class:ze(s.iconClass),style:Qt(s.alignStyle)},[uv,ae("path",{fill:n.options.color,d:"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm-1 4l6 6v10c0 1.1-.9 2-2 2H7.99C6.89 23 6 22.1 6 21l.01-14c0-1.1.89-2 1.99-2h7zm-1 7h5.5L14 6.5V12z"},null,8,fv)],6)),ae("span",{class:ze(r.success?"success":""),style:Qt({color:n.options.successTextColor,...s.alignStyle})},Ie(n.options.successText),7)])}const Ci=Le(lv,[["render",dv],["__scopeId","data-v-1b705319"],["__file","CodeCopy.vue"]]);const pv=Ct({enhance({app:e}){e.component("CodeCopy",Ci)},setup(){const e=$t(),t=()=>{setTimeout(()=>{document.querySelectorAll('div[class*="language-"]').forEach(n=>{if(n.classList.contains("code-copy-added"))return;let o={align:"bottom",color:"#27b1ff",backgroundTransition:!0,backgroundColor:"#0075b8",successText:"Copied!",successTextColor:"#27b1ff",staticIcon:!1},r=Gu(Ci,{parent:n,code:n.querySelector("pre").innerText,options:o}),s=document.createElement("div");n.appendChild(s),r.mount(s),n.classList.add("code-copy-added")})},500)};We(()=>{t(),window.addEventListener("snippetors-vuepress-plugin-code-copy-update-event",t)}),Jn(()=>{window.removeEventListener("snippetors-vuepress-plugin-code-copy-update-event",t)}),il(()=>{t()}),et(()=>e.value.path,t)}}),uo=[wd,Ld,Sd,Vd,Kd,Yd,Ym,iv,pv],hv=[["v-8daa1a0e","/",{title:""},["/index.md"]],["v-14ac19b5","/help/",{title:""},["/help/index.md"]],["v-e5065f60","/docs/api-administrator/",{title:"Administrator"},["/docs/api/administrator.html","/docs/api/administrator.md"]],["v-eb8745ce","/docs/api-bounce/",{title:"Bounce"},["/docs/api/bounce.html","/docs/api/bounce.md"]],["v-828730c2","/docs/api-config/",{title:"Configuration"},["/docs/api/config.html","/docs/api/config.md"]],["v-563ef996","/docs/api-notification/",{title:"Notification"},["/docs/api/notification.html","/docs/api/notification.md"]],["v-04b96fc8","/docs/api-overview/",{title:"API Overview"},["/docs/api/overview.html","/docs/api/overview.md"]],["v-fe45a0b8","/docs/api-subscription/",{title:"Subscription"},["/docs/api/subscription.html","/docs/api/subscription.md"]],["v-7ed00a2a","/docs/config-adminIpList/",{title:"Admin IP List"},["/docs/config/adminIpList.html","/docs/config/adminIpList.md"]],["v-326db923","/docs/config-certificates/",{title:"TLS Certificates"},["/docs/config/certificates.html","/docs/config/certificates.md"]],["v-587df7db","/docs/config-cronJobs/",{title:"Cron Jobs"},["/docs/config/cronJobs.html","/docs/config/cronJobs.md"]],["v-0b2aad78","/docs/config-database/",{title:"Database"},["/docs/config/database.html","/docs/config/database.md"]],["v-23f62a3a","/docs/config-email/",{title:"Email"},["/docs/config/email.html","/docs/config/email.md"]],["v-1963670f","/docs/config-httpHost/",{title:"HTTP Host"},["/docs/config/httpHost.html","/docs/config/httpHost.md"]],["v-4cf2565c","/docs/config-internalHttpHost/",{title:"Internal HTTP Host"},["/docs/config/internalHttpHost.html","/docs/config/internalHttpHost.md"]],["v-17bdcfe6","/docs/config-middleware/",{title:"Middleware"},["/docs/config/middleware.html","/docs/config/middleware.md"]],["v-3481b484","/docs/config-nodeRoles/",{title:"Node Roles"},["/docs/config/nodeRoles.html","/docs/config/nodeRoles.md"]],["v-b6a1f058","/docs/config-notification/",{title:"Notification"},["/docs/config/notification.html","/docs/config/notification.md"]],["v-94b7dab4","/docs/config-oidc/",{title:"OIDC"},["/docs/config/oidc.html","/docs/config/oidc.md"]],["v-391365f4","/docs/config-overview/",{title:"Configuration Overview"},["/docs/config/overview.html","/docs/config/overview.md"]],["v-32b5e2dd","/docs/config-reverseProxyIpLists/",{title:"Reverse Proxy IP Lists"},["/docs/config/reverseProxyIpLists.html","/docs/config/reverseProxyIpLists.md"]],["v-02a19d2b","/docs/config-rsaKeys/",{title:"RSA Keys"},["/docs/config/rsaKeys.html","/docs/config/rsaKeys.md"]],["v-26e624c6","/docs/config-sms/",{title:"SMS"},["/docs/config/sms.html","/docs/config/sms.md"]],["v-6165843c","/docs/config-subscription/",{title:"Subscription"},["/docs/config/subscription.html","/docs/config/subscription.md"]],["v-22e054a1","/docs/config-workerProcessCount/",{title:"Worker Process Count"},["/docs/config/workerProcessCount.html","/docs/config/workerProcessCount.md"]],["v-147825fb","/docs/",{title:"Welcome"},["/docs/getting-started/","/docs/getting-started/index.md"]],["v-255f131a","/docs/installation/",{title:"Installation"},["/docs/getting-started/installation.html","/docs/getting-started/installation.md"]],["v-6768263b","/docs/overview/",{title:"Overview"},["/docs/getting-started/overview.html","/docs/getting-started/overview.md"]],["v-6a4de75f","/docs/quickstart/",{title:"Quick Start"},["/docs/getting-started/quickstart.html","/docs/getting-started/quickstart.md"]],["v-a20dfce8","/docs/web-console/",{title:"Web Console"},["/docs/getting-started/web-console.html","/docs/getting-started/web-console.md"]],["v-9a955b1e","/docs/what's-new/",{title:"What's New"},["/docs/getting-started/what's-new.html","/docs/getting-started/what's-new.md"]],["v-ca3407c4","/docs/acknowledgments/",{title:"Acknowledgments"},["/docs/meta/acknowledgments.html","/docs/meta/acknowledgments.md"]],["v-3cf0fa66","/docs/conduct/",{title:"Code of Conduct"},["/docs/meta/conduct.html","/docs/meta/conduct.md"]],["v-b09aba04","/docs/benchmarks/",{title:"Benchmarks"},["/docs/miscellaneous/benchmarks.html","/docs/miscellaneous/benchmarks.md"]],["v-b341ee2c","/docs/bulk-import/",{title:"Bulk Import"},["/docs/miscellaneous/bulk-import.html","/docs/miscellaneous/bulk-import.md"]],["v-0c9564ec","/docs/developer-notes/",{title:"Developer Notes"},["/docs/miscellaneous/developer-notes.html","/docs/miscellaneous/developer-notes.md"]],["v-36e2ae9d","/docs/health-check/",{title:"Health Check"},["/docs/miscellaneous/health-check.html","/docs/miscellaneous/health-check.md"]],["v-5b6d532c","/docs/memory-dump/",{title:"Memory Dump"},["/docs/miscellaneous/memory-dump.html","/docs/miscellaneous/memory-dump.md"]],["v-9712b6e4","/docs/upgrade/",{title:"Upgrade Guide"},["/docs/miscellaneous/upgrade.html","/docs/miscellaneous/upgrade.md"]],["v-bdba93e6","/docs/shared/filterQueryParam.html",{title:""},[":md"]],["v-31ddcbc0","/docs/shared/filterQueryParamCode.html",{title:""},[":md"]],["v-0e79de1b","/docs/shared/filterQueryParamExample.html",{title:""},[":md"]],["v-9a1a7988","/docs/shared/jmespathFilter.html",{title:""},[":md"]],["v-4395d380","/docs/shared/throttle.html",{title:""},[":md"]],["v-17bf8008","/docs/shared/whereQueryParam.html",{title:""},[":md"]],["v-0b4a148f","/docs/shared/whereQueryParamCode.html",{title:""},[":md"]],["v-5119194c","/docs/shared/whereQueryParamExample.html",{title:""},[":md"]],["v-3706649a","/404.html",{title:""},[]]];var Ti=fe({name:"Vuepress",setup(){const e=uf();return()=>ge(e.value)}}),mv=()=>hv.reduce((e,[t,n,o,r])=>(e.push({name:t,path:n,component:Ti,meta:o},{path:n.endsWith("/")?n+"index.html":n.substring(0,n.length-5),redirect:n},...r.map(s=>({path:s===":md"?n.substring(0,n.length-5)+".md":s,redirect:n}))),e),[{name:"404",path:"/:catchAll(.*)",component:Ti}]),vv=Af,gv=()=>{const e=md({history:vv(Pl("/NotifyBC/")),routes:mv(),scrollBehavior:(t,n,o)=>o||(t.hash?{el:t.hash}:{top:0})});return e.beforeResolve(async(t,n)=>{var o;(t.path!==n.path||n===ht)&&([It.value]=await Promise.all([pt.resolvePageData(t.name),(o=kl[t.name])==null?void 0:o.__asyncLoader()]))}),e},_v=e=>{e.component("ClientOnly",Ur),e.component("Content",hf)},bv=(e,t,n)=>{const o=z(()=>pt.resolveLayouts(n)),r=mi(()=>t.currentRoute.value.path),s=mi(()=>pt.resolveRouteLocale(on.value.locales,r.value)),i=z(()=>pt.resolveSiteLocaleData(on.value,s.value)),l=z(()=>pt.resolvePageFrontmatter(It.value)),a=z(()=>pt.resolvePageHeadTitle(It.value,i.value)),c=z(()=>pt.resolvePageHead(a.value,l.value,i.value)),u=z(()=>pt.resolvePageLang(It.value,i.value)),f=z(()=>pt.resolvePageLayout(It.value,o.value));return e.provide(sf,o),e.provide(Al,l),e.provide(cf,a),e.provide(Rl,c),e.provide(Dl,u),e.provide(Ml,f),e.provide(Fr,s),e.provide(Hl,i),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>l.value},$head:{get:()=>c.value},$headTitle:{get:()=>a.value},$lang:{get:()=>u.value},$page:{get:()=>It.value},$routeLocale:{get:()=>s.value},$site:{get:()=>on.value},$siteLocale:{get:()=>i.value},$withBase:{get:()=>Kr}}),{layouts:o,pageData:It,pageFrontmatter:l,pageHead:c,pageHeadTitle:a,pageLang:u,pageLayout:f,routeLocale:s,siteData:on,siteLocaleData:i}},yv=()=>{const e=af(),t=$l(),n=be([]),o=()=>{e.value.forEach(s=>{const i=Ev(s);i&&n.value.push(i)})},r=()=>{document.documentElement.lang=t.value,n.value.forEach(s=>{s.parentNode===document.head&&document.head.removeChild(s)}),n.value.splice(0,n.value.length),e.value.forEach(s=>{const i=wv(s);i!==null&&(document.head.appendChild(i),n.value.push(i))})};Wt(ff,r),We(()=>{o(),r(),et(()=>e.value,r)})},Ev=([e,t,n=""])=>{const o=Object.entries(t).map(([l,a])=>me(a)?`[${l}=${JSON.stringify(a)}]`:a===!0?`[${l}]`:"").join(""),r=`head > ${e}${o}`;return Array.from(document.querySelectorAll(r)).find(l=>l.innerText===n)||null},wv=([e,t,n])=>{if(!me(e))return null;const o=document.createElement(e);return Vr(t)&&Object.entries(t).forEach(([r,s])=>{me(s)?o.setAttribute(r,s):s===!0&&o.setAttribute(r,"")}),me(n)&&o.appendChild(document.createTextNode(n)),o},Cv=Zu,Tv=async()=>{var n;const e=Cv({name:"VuepressApp",setup(){var o;yv();for(const r of uo)(o=r.setup)==null||o.call(r);return()=>[ge(Ql),...uo.flatMap(({rootComponents:r=[]})=>r.map(s=>ge(s)))]}}),t=gv();_v(e),bv(e,t,uo);for(const o of uo)await((n=o.enhance)==null?void 0:n.call(o,{app:e,router:t,siteData:on}));return e.use(t),{app:e,router:t}};Tv().then(({app:e,router:t})=>{t.isReady().then(()=>{e.mount("#app")})});export{Le as _,ae as a,Et as b,Q as c,Tv as createVueApp,ne as d,du as e,Gl as f,H as o,bt as r,Ie as t,X as u,Me as w}; diff --git a/assets/filterQueryParam.html-f6d30c08.js b/assets/filterQueryParam.html-be5e74b7.js similarity index 96% rename from assets/filterQueryParam.html-f6d30c08.js rename to assets/filterQueryParam.html-be5e74b7.js index a67a88376..8d9224ad4 100644 --- a/assets/filterQueryParam.html-f6d30c08.js +++ b/assets/filterQueryParam.html-be5e74b7.js @@ -1,4 +1,4 @@ -import{_ as r,o as t,c as n,a as e,b as o}from"./app-77c416fe.js";const s={},l=e("p",null,[o("a filter containing properties "),e("em",null,"where"),o(", "),e("em",null,"fields"),o(", "),e("em",null,"order"),o(", "),e("em",null,"skip"),o(", and "),e("em",null,"limit")],-1),a=e("pre",null,[e("code",null,`- parameter name: filter +import{_ as r,o as t,c as n,a as e,b as o}from"./app-be471a62.js";const s={},l=e("p",null,[o("a filter containing properties "),e("em",null,"where"),o(", "),e("em",null,"fields"),o(", "),e("em",null,"order"),o(", "),e("em",null,"skip"),o(", and "),e("em",null,"limit")],-1),a=e("pre",null,[e("code",null,`- parameter name: filter - required: false - parameter type: query - data type: object diff --git a/assets/filterQueryParamCode.html-63f24e00.js b/assets/filterQueryParamCode.html-5188a76f.js similarity index 81% rename from assets/filterQueryParamCode.html-63f24e00.js rename to assets/filterQueryParamCode.html-5188a76f.js index 571723c33..92683651f 100644 --- a/assets/filterQueryParamCode.html-63f24e00.js +++ b/assets/filterQueryParamCode.html-5188a76f.js @@ -1 +1 @@ -import{_ as e,o as t,c as r,a as o}from"./app-77c416fe.js";const a={},c=o("p",null,"?filter=%7B%22where%22%3A%7B%22created%22%3A%7B%22%24gte%22%3A%222023-01-01%22%2C%22%24lt%22%3A%222024-01-01%22%7D%7D%7D",-1),_=[c];function l(s,n){return t(),r("div",null,_)}const f=e(a,[["render",l],["__file","filterQueryParamCode.html.vue"]]);export{f as default}; +import{_ as e,o as t,c as r,a as o}from"./app-be471a62.js";const a={},c=o("p",null,"?filter=%7B%22where%22%3A%7B%22created%22%3A%7B%22%24gte%22%3A%222023-01-01%22%2C%22%24lt%22%3A%222024-01-01%22%7D%7D%7D",-1),_=[c];function l(s,n){return t(),r("div",null,_)}const f=e(a,[["render",l],["__file","filterQueryParamCode.html.vue"]]);export{f as default}; diff --git a/assets/filterQueryParamExample.html-3c9d0dce.js b/assets/filterQueryParamExample.html-30c0817c.js similarity index 86% rename from assets/filterQueryParamExample.html-3c9d0dce.js rename to assets/filterQueryParamExample.html-30c0817c.js index 1f8c0fee0..a54f9e444 100644 --- a/assets/filterQueryParamExample.html-3c9d0dce.js +++ b/assets/filterQueryParamExample.html-30c0817c.js @@ -1,4 +1,4 @@ -import{_ as t,o as n,c as r,a as e}from"./app-77c416fe.js";const o={},a=e("p",null,"the value of the filter query parameter is URL-encoded stringified JSON object",-1),l=e("pre",null,[e("code",null,`\`\`\`json +import{_ as t,o as n,c as r,a as e}from"./app-be471a62.js";const o={},a=e("p",null,"the value of the filter query parameter is URL-encoded stringified JSON object",-1),l=e("pre",null,[e("code",null,`\`\`\`json { "where": { "created": { diff --git a/assets/index.html-287f081e.js b/assets/index.html-00524c3e.js similarity index 99% rename from assets/index.html-287f081e.js rename to assets/index.html-00524c3e.js index 1d34d441f..9fa84cbac 100644 --- a/assets/index.html-287f081e.js +++ b/assets/index.html-00524c3e.js @@ -1,4 +1,4 @@ -import{_ as p,r as i,o as c,c as u,a as e,b as n,d as s,w as l,e as t}from"./app-77c416fe.js";const d={},m=t(`

Email

SMTP

By default NotifyBC acts as the SMTP server itself and connects directly to recipient's SMTP server. To setup SMTP relay to a host, say smtp.foo.com, add following smtp config object to /src/config.local.js

module.exports = {
+import{_ as p,r as i,o as c,c as u,a as e,b as n,d as s,w as l,e as t}from"./app-be471a62.js";const d={},m=t(`

Email

SMTP

By default NotifyBC acts as the SMTP server itself and connects directly to recipient's SMTP server. To setup SMTP relay to a host, say smtp.foo.com, add following smtp config object to /src/config.local.js

module.exports = {
   email: {
     smtp: {
       host: 'smtp.foo.com',
diff --git a/assets/index.html-ea9c36e4.js b/assets/index.html-15bf84b8.js
similarity index 99%
rename from assets/index.html-ea9c36e4.js
rename to assets/index.html-15bf84b8.js
index ff0acedbd..08f9d44b2 100644
--- a/assets/index.html-ea9c36e4.js
+++ b/assets/index.html-15bf84b8.js
@@ -1,4 +1,4 @@
-import{_ as t,r as p,o,c as l,a as n,b as s,d as e,e as c}from"./app-77c416fe.js";const i={},r=n("h1",{id:"middleware",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#middleware","aria-hidden":"true"},"#"),s(" Middleware")],-1),u=n("em",null,"NotifyBC",-1),d={href:"https://expressjs.com/",target:"_blank",rel:"noopener noreferrer"},m=n("em",null,"/src/middleware.ts",-1),k={href:"https://www.npmjs.com/package/compression",target:"_blank",rel:"noopener noreferrer"},v={href:"https://www.npmjs.com/package/helmet",target:"_blank",rel:"noopener noreferrer"},b={href:"https://www.npmjs.com/package/morgan",target:"_blank",rel:"noopener noreferrer"},g=c(`

/src/middleware.ts contains following default middleware settings

import path from 'path';
+import{_ as t,r as p,o,c as l,a as n,b as s,d as e,e as c}from"./app-be471a62.js";const i={},r=n("h1",{id:"middleware",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#middleware","aria-hidden":"true"},"#"),s(" Middleware")],-1),u=n("em",null,"NotifyBC",-1),d={href:"https://expressjs.com/",target:"_blank",rel:"noopener noreferrer"},m=n("em",null,"/src/middleware.ts",-1),k={href:"https://www.npmjs.com/package/compression",target:"_blank",rel:"noopener noreferrer"},v={href:"https://www.npmjs.com/package/helmet",target:"_blank",rel:"noopener noreferrer"},b={href:"https://www.npmjs.com/package/morgan",target:"_blank",rel:"noopener noreferrer"},g=c(`

/src/middleware.ts contains following default middleware settings

import path from 'path';
 module.exports = {
   all: {
     compression: {},
diff --git a/assets/index.html-03b54c01.js b/assets/index.html-1a8bbf38.js
similarity index 98%
rename from assets/index.html-03b54c01.js
rename to assets/index.html-1a8bbf38.js
index 74ab3247f..2006a174e 100644
--- a/assets/index.html-03b54c01.js
+++ b/assets/index.html-1a8bbf38.js
@@ -1,4 +1,4 @@
-import{_ as a,r as i,o,c as r,a as e,b as s,d as t,e as p}from"./app-77c416fe.js";const l={},c=e("h1",{id:"reverse-proxy-ip-lists",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#reverse-proxy-ip-lists","aria-hidden":"true"},"#"),s(" Reverse Proxy IP Lists")],-1),d={href:"https://en.wikipedia.org/wiki/Dot-decimal_notation",target:"_blank",rel:"noopener noreferrer"},u={href:"https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,[e("em",null,"siteMinderReverseProxyIps"),s(" contains a list of ips or ranges of SiteMinder Web Agents. If set, then the SiteMinder HTTP headers are trusted only if the request is routed from the listed nodes.")],-1),m=e("em",null,"trustedReverseProxyIps",-1),f=e("em",null,"NotifyBC",-1),_=e("em",null,"NotifyBC",-1),v={href:"https://expressjs.com/en/guide/behind-proxies.html",target:"_blank",rel:"noopener noreferrer"},k=p(`

By default trustedReverseProxyIps is empty and siteMinderReverseProxyIps contains only localhost as defined in /src/config.ts

module.exports = {
+import{_ as a,r as i,o,c as r,a as e,b as s,d as t,e as p}from"./app-be471a62.js";const l={},c=e("h1",{id:"reverse-proxy-ip-lists",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#reverse-proxy-ip-lists","aria-hidden":"true"},"#"),s(" Reverse Proxy IP Lists")],-1),d={href:"https://en.wikipedia.org/wiki/Dot-decimal_notation",target:"_blank",rel:"noopener noreferrer"},u={href:"https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation",target:"_blank",rel:"noopener noreferrer"},h=e("li",null,[e("em",null,"siteMinderReverseProxyIps"),s(" contains a list of ips or ranges of SiteMinder Web Agents. If set, then the SiteMinder HTTP headers are trusted only if the request is routed from the listed nodes.")],-1),m=e("em",null,"trustedReverseProxyIps",-1),f=e("em",null,"NotifyBC",-1),_=e("em",null,"NotifyBC",-1),v={href:"https://expressjs.com/en/guide/behind-proxies.html",target:"_blank",rel:"noopener noreferrer"},k=p(`

By default trustedReverseProxyIps is empty and siteMinderReverseProxyIps contains only localhost as defined in /src/config.ts

module.exports = {
   siteMinderReverseProxyIps: ['127.0.0.1'],
 };
 

To modify, add following objects to file /src/config.local.js

module.exports = {
diff --git a/assets/index.html-9b491f72.js b/assets/index.html-1bba510e.js
similarity index 99%
rename from assets/index.html-9b491f72.js
rename to assets/index.html-1bba510e.js
index ae1c32bc1..51521b99f 100644
--- a/assets/index.html-9b491f72.js
+++ b/assets/index.html-1bba510e.js
@@ -1,4 +1,4 @@
-import{_ as i,r as p,o as l,c as r,a as n,b as s,d as a,w as c,e as t}from"./app-77c416fe.js";const u={},d=n("h1",{id:"sms",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#sms","aria-hidden":"true"},"#"),s(" SMS")],-1),m=n("h2",{id:"provider",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#provider","aria-hidden":"true"},"#"),s(" Provider")],-1),k=n("p",null,[n("em",null,"NotifyBC"),s(" depends on underlying SMS service providers to deliver SMS messages. The supported service providers are")],-1),v={href:"https://twilio.com/",target:"_blank",rel:"noopener noreferrer"},b={href:"https://www.swiftsmsgateway.com",target:"_blank",rel:"noopener noreferrer"},h=t(`

Only one service provider can be chosen per installation. To change service provider, add following config to file /src/config.local.js

module.exports = {
+import{_ as i,r as p,o as l,c as r,a as n,b as s,d as a,w as c,e as t}from"./app-be471a62.js";const u={},d=n("h1",{id:"sms",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#sms","aria-hidden":"true"},"#"),s(" SMS")],-1),m=n("h2",{id:"provider",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#provider","aria-hidden":"true"},"#"),s(" Provider")],-1),k=n("p",null,[n("em",null,"NotifyBC"),s(" depends on underlying SMS service providers to deliver SMS messages. The supported service providers are")],-1),v={href:"https://twilio.com/",target:"_blank",rel:"noopener noreferrer"},b={href:"https://www.swiftsmsgateway.com",target:"_blank",rel:"noopener noreferrer"},h=t(`

Only one service provider can be chosen per installation. To change service provider, add following config to file /src/config.local.js

module.exports = {
   sms: {
     provider: 'swift',
   },
diff --git a/assets/index.html-ffb5aa93.js b/assets/index.html-200962d8.js
similarity index 97%
rename from assets/index.html-ffb5aa93.js
rename to assets/index.html-200962d8.js
index 729205c0b..0b39b09e1 100644
--- a/assets/index.html-ffb5aa93.js
+++ b/assets/index.html-200962d8.js
@@ -1 +1 @@
-import{_ as i,r,o as a,c as s,a as t,b as e,d as n,e as c}from"./app-77c416fe.js";const l={},p=c('

Code of Conduct

As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery
  • Personal attacks
  • Trolling or insulting/derogatory comments
  • Public or private harassment
  • Publishing other's private information, such as physical or electronic addresses, without explicit permission
  • Other unethical or unprofessional conduct

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting a project maintainer. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.

',9),d={href:"http://contributor-covenant.org",target:"_blank",rel:"noopener noreferrer"},h={href:"http://contributor-covenant.org/version/1/3/0/",target:"_blank",rel:"noopener noreferrer"};function u(m,f){const o=r("ExternalLinkIcon");return a(),s("div",null,[p,t("p",null,[e("This Code of Conduct is adapted from the "),t("a",d,[e("Contributor Covenant"),n(o)]),e(", version 1.3.0, available at "),t("a",h,[e("http://contributor-covenant.org/version/1/3/0/"),n(o)])])])}const b=i(l,[["render",u],["__file","index.html.vue"]]);export{b as default}; +import{_ as i,r,o as a,c as s,a as t,b as e,d as n,e as c}from"./app-be471a62.js";const l={},p=c('

Code of Conduct

As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.

Examples of unacceptable behavior by participants include:

  • The use of sexualized language or imagery
  • Personal attacks
  • Trolling or insulting/derogatory comments
  • Public or private harassment
  • Publishing other's private information, such as physical or electronic addresses, without explicit permission
  • Other unethical or unprofessional conduct

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting a project maintainer. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.

',9),d={href:"http://contributor-covenant.org",target:"_blank",rel:"noopener noreferrer"},h={href:"http://contributor-covenant.org/version/1/3/0/",target:"_blank",rel:"noopener noreferrer"};function u(m,f){const o=r("ExternalLinkIcon");return a(),s("div",null,[p,t("p",null,[e("This Code of Conduct is adapted from the "),t("a",d,[e("Contributor Covenant"),n(o)]),e(", version 1.3.0, available at "),t("a",h,[e("http://contributor-covenant.org/version/1/3/0/"),n(o)])])])}const b=i(l,[["render",u],["__file","index.html.vue"]]);export{b as default}; diff --git a/assets/index.html-4c7abb90.js b/assets/index.html-219bb653.js similarity index 99% rename from assets/index.html-4c7abb90.js rename to assets/index.html-219bb653.js index b4c30264e..e40b9af50 100644 --- a/assets/index.html-4c7abb90.js +++ b/assets/index.html-219bb653.js @@ -1,4 +1,4 @@ -import{_ as o,r as i,o as r,c as p,a as n,b as e,d as s,t as c,u,e as t,f as d}from"./app-77c416fe.js";const m=t('

Installation

NotifyBC can be installed in 3 ways:

  1. Deploy locally from Source Code
  2. Deploy to Kubernetes
  3. Deploy Docker Container

For the purpose of evaluation, both source code and docker container will do. For production, the recommendation is one of

  • deploying to Kubernetes
  • setting up a load balanced app cluster from source code build, backed by MongoDB.

To setup a development environment in order to contribute to NotifyBC, installing from source code is preferred.

Deploy locally from Source Code

System Requirements

',8),v=n("li",null,"Git",-1),h={href:"https://nodejs.org",target:"_blank",rel:"noopener noreferrer"},b=n("li",null,"openssl (if enable HTTPS)",-1),k=n("li",null,"MongoDB with replica set, required for production",-1),g=n("li",null,"A standard SMTP server to deliver outgoing email, required for production if email is enabled.",-1),f={href:"http://nginx.org/en/docs/stream/ngx_stream_proxy_module.html",target:"_blank",rel:"noopener noreferrer"},y=n("em",null,"NotifyBC",-1),_=n("li",null,[e("A SMS service provider if needs to enable SMS channel. The supported service providers are "),n("ul",null,[n("li",null,"Twilio (default)"),n("li",null,"Swift")])],-1),x=n("li",null,"Redis v6, required if email or sms throttling is enabled",-1),w=n("li",null,"SiteMinder, if needs SiteMinder authentication",-1),C=n("li",null,"An OIDC provider, if needs OIDC authentication",-1),B=t("
  • Network and Permissions
    • Minimum runtime firewall requirements:
      • outbound to your ISP DNS server
      • outbound to any on port 80 and 443 in order to run build scripts and send SMS messages
      • outbound to any on SMTP port 25 if using direct mail; for SMTP relay, outbound to your configured SMTP server and port only
      • inbound to listening port (3000 by default) from other authorized server ips
      • if NotifyBC instance will handle anonymous subscription from client browser, the listening port should be open to internet either directly or indirectly through a reverse proxy; If NotifyBC instance will only handle SiteMinder authenticated webapp requests, the listening port should NOT be open to internet. Instead, it should only open to SiteMinder web agent reverse proxy.
    • If list-unsubscribe by email is needed, then one of the following must be met
      • NotifyBC can bind to port 25 opening to internet
      • a tcp proxy server of which port 25 is open to internet. This proxy server can reach NotifyBC on a tcp port.
  • ",1),S=t(`

    Installation

    Run following commands

    git clone https://github.com/bcgov/NotifyBC.git
    +import{_ as o,r as i,o as r,c as p,a as n,b as e,d as s,t as c,u,e as t,f as d}from"./app-be471a62.js";const m=t('

    Installation

    NotifyBC can be installed in 3 ways:

    1. Deploy locally from Source Code
    2. Deploy to Kubernetes
    3. Deploy Docker Container

    For the purpose of evaluation, both source code and docker container will do. For production, the recommendation is one of

    • deploying to Kubernetes
    • setting up a load balanced app cluster from source code build, backed by MongoDB.

    To setup a development environment in order to contribute to NotifyBC, installing from source code is preferred.

    Deploy locally from Source Code

    System Requirements

    ',8),v=n("li",null,"Git",-1),h={href:"https://nodejs.org",target:"_blank",rel:"noopener noreferrer"},b=n("li",null,"openssl (if enable HTTPS)",-1),k=n("li",null,"MongoDB with replica set, required for production",-1),g=n("li",null,"A standard SMTP server to deliver outgoing email, required for production if email is enabled.",-1),f={href:"http://nginx.org/en/docs/stream/ngx_stream_proxy_module.html",target:"_blank",rel:"noopener noreferrer"},y=n("em",null,"NotifyBC",-1),_=n("li",null,[e("A SMS service provider if needs to enable SMS channel. The supported service providers are "),n("ul",null,[n("li",null,"Twilio (default)"),n("li",null,"Swift")])],-1),x=n("li",null,"Redis v6, required if email or sms throttling is enabled",-1),w=n("li",null,"SiteMinder, if needs SiteMinder authentication",-1),C=n("li",null,"An OIDC provider, if needs OIDC authentication",-1),B=t("
  • Network and Permissions
    • Minimum runtime firewall requirements:
      • outbound to your ISP DNS server
      • outbound to any on port 80 and 443 in order to run build scripts and send SMS messages
      • outbound to any on SMTP port 25 if using direct mail; for SMTP relay, outbound to your configured SMTP server and port only
      • inbound to listening port (3000 by default) from other authorized server ips
      • if NotifyBC instance will handle anonymous subscription from client browser, the listening port should be open to internet either directly or indirectly through a reverse proxy; If NotifyBC instance will only handle SiteMinder authenticated webapp requests, the listening port should NOT be open to internet. Instead, it should only open to SiteMinder web agent reverse proxy.
    • If list-unsubscribe by email is needed, then one of the following must be met
      • NotifyBC can bind to port 25 opening to internet
      • a tcp proxy server of which port 25 is open to internet. This proxy server can reach NotifyBC on a tcp port.
  • ",1),S=t(`

    Installation

    Run following commands

    git clone https://github.com/bcgov/NotifyBC.git
     cd NotifyBC
     npm i && npm run build
     npm run start
    diff --git a/assets/index.html-baba90e5.js b/assets/index.html-255cab24.js
    similarity index 95%
    rename from assets/index.html-baba90e5.js
    rename to assets/index.html-255cab24.js
    index 0941cee51..a0c425551 100644
    --- a/assets/index.html-baba90e5.js
    +++ b/assets/index.html-255cab24.js
    @@ -1,4 +1,4 @@
    -import{_ as a,r as s,o as t,c as i,a as o,b as n,d as c,w as l,e as r}from"./app-77c416fe.js";const d={},u=r(`

    Quick Start

    For the impatient, here's how to get a boilerplate NotifyBC instance up and running if you have git and node.js installed:

    git clone https://github.com/bcgov/NotifyBC.git
    +import{_ as a,r as s,o as t,c as i,a as o,b as n,d as c,w as l,e as r}from"./app-be471a62.js";const d={},u=r(`

    Quick Start

    For the impatient, here's how to get a boilerplate NotifyBC instance up and running if you have git and node.js installed:

    git clone https://github.com/bcgov/NotifyBC.git
     cd NotifyBC
     npm i && npm run build
     npm run start
    diff --git a/assets/index.html-4e48a4d5.js b/assets/index.html-287c16ec.js
    similarity index 97%
    rename from assets/index.html-4e48a4d5.js
    rename to assets/index.html-287c16ec.js
    index 617c4e6f3..4a30db4ac 100644
    --- a/assets/index.html-4e48a4d5.js
    +++ b/assets/index.html-287c16ec.js
    @@ -1,4 +1,4 @@
    -import{_ as a,r as t,o as i,c as o,a as e,b as s,d as p,w as c,e as l}from"./app-77c416fe.js";const r={},d=e("h1",{id:"admin-ip-list",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#admin-ip-list","aria-hidden":"true"},"#"),s(" Admin IP List")],-1),u=e("em",null,"NotifyBC",-1),m=e("em",null,"localhost",-1),h=e("em",null,"adminIps",-1),v=e("em",null,"/src/config.ts",-1),k=l(`
    module.exports = {
    +import{_ as a,r as t,o as i,c as o,a as e,b as s,d as p,w as c,e as l}from"./app-be471a62.js";const r={},d=e("h1",{id:"admin-ip-list",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#admin-ip-list","aria-hidden":"true"},"#"),s(" Admin IP List")],-1),u=e("em",null,"NotifyBC",-1),m=e("em",null,"localhost",-1),h=e("em",null,"adminIps",-1),v=e("em",null,"/src/config.ts",-1),k=l(`
    module.exports = {
       adminIps: ['127.0.0.1'],
     };
     

    to modify, create config object adminIps with updated list in file /src/config.local.js instead. For example, to add ip range 192.168.0.0/24 to the list

    module.exports = {
    diff --git a/assets/index.html-6cc49290.js b/assets/index.html-29e78a52.js
    similarity index 98%
    rename from assets/index.html-6cc49290.js
    rename to assets/index.html-29e78a52.js
    index 9bd18aba5..8060998d2 100644
    --- a/assets/index.html-6cc49290.js
    +++ b/assets/index.html-29e78a52.js
    @@ -1 +1 @@
    -import{_ as r,r as a,o as s,c as n,a as t,d as o,w as c,b as e,e as i}from"./app-77c416fe.js";const l={},p=t("h1",{id:"bounce",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#bounce","aria-hidden":"true"},"#"),e(" Bounce")],-1),u=i('

    Model Schema

    The API operates on following data model fields:

    NameAttributes

    channel

    name of the delivery channel. Valid values: email, sms.

    typestring
    requiredtrue

    userChannelId

    user's delivery channel id, for example, email address.
    typestring
    requiredtrue

    hardBounceCount

    number of hard bounces recorded so far

    typeinteger
    requiredtrue

    state

    bounce record state. Valid values: active, deleted.

    typestring
    requiredtrue

    bounceMessages

    array of recorded bounce messages. Each element is an object containing the date bounce message was received and the message itself.

    typearray
    requiredfalse

    latestNotificationStarted

    latest notification started date.

    typedate
    requiredfalse

    latestNotificationEnded

    latest notification ended date.

    typedate
    requiredfalse

    created

    date and time bounce record was created

    typedate
    auto-generatedtrue

    updated

    date and time of bounce record was last updated

    typedate
    auto-generatedtrue

    id

    config id

    typestring, format depends on db
    auto-generatedtrue
    ',3);function m(b,h){const d=a("RouterLink");return s(),n("div",null,[p,t("p",null,[o(d,{to:"/docs/config/email.html#bounce"},{default:c(()=>[e("Bounce")]),_:1}),e(" handling involves recording bounce messages into bounce records, which are implemented using this bounce API and model. Administrator can view bounce records in web console or through API explorer. Bounce record is for internal use and should be read-only under normal circumstances.")]),u])}const g=r(l,[["render",m],["__file","index.html.vue"]]);export{g as default}; +import{_ as r,r as a,o as s,c as n,a as t,d as o,w as c,b as e,e as i}from"./app-be471a62.js";const l={},p=t("h1",{id:"bounce",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#bounce","aria-hidden":"true"},"#"),e(" Bounce")],-1),u=i('

    Model Schema

    The API operates on following data model fields:

    NameAttributes

    channel

    name of the delivery channel. Valid values: email, sms.

    typestring
    requiredtrue

    userChannelId

    user's delivery channel id, for example, email address.
    typestring
    requiredtrue

    hardBounceCount

    number of hard bounces recorded so far

    typeinteger
    requiredtrue

    state

    bounce record state. Valid values: active, deleted.

    typestring
    requiredtrue

    bounceMessages

    array of recorded bounce messages. Each element is an object containing the date bounce message was received and the message itself.

    typearray
    requiredfalse

    latestNotificationStarted

    latest notification started date.

    typedate
    requiredfalse

    latestNotificationEnded

    latest notification ended date.

    typedate
    requiredfalse

    created

    date and time bounce record was created

    typedate
    auto-generatedtrue

    updated

    date and time of bounce record was last updated

    typedate
    auto-generatedtrue

    id

    config id

    typestring, format depends on db
    auto-generatedtrue
    ',3);function m(b,h){const d=a("RouterLink");return s(),n("div",null,[p,t("p",null,[o(d,{to:"/docs/config/email.html#bounce"},{default:c(()=>[e("Bounce")]),_:1}),e(" handling involves recording bounce messages into bounce records, which are implemented using this bounce API and model. Administrator can view bounce records in web console or through API explorer. Bounce record is for internal use and should be read-only under normal circumstances.")]),u])}const g=r(l,[["render",m],["__file","index.html.vue"]]);export{g as default}; diff --git a/assets/index.html-ffbaf637.js b/assets/index.html-3031d490.js similarity index 99% rename from assets/index.html-ffbaf637.js rename to assets/index.html-3031d490.js index eead9c51b..f4d9a2fb0 100644 --- a/assets/index.html-ffbaf637.js +++ b/assets/index.html-3031d490.js @@ -1 +1 @@ -import{_ as c,r,o as d,c as u,a as e,b as i,d as t,w as a,e as s}from"./app-77c416fe.js";const h={},p=s('

    Overview

    NotifyBC is a general purpose API Server to manage subscriptions and dispatch notifications. It aims to implement some common backend processes of a notification service without imposing any constraints on the UI frontend, nor impeding other server components' functionality. This is achieved by interacting with user browser and other server components through RESTful API and other standard protocols in a loosely coupled way.

    Features

    NotifyBC facilitates both anonymous and authentication-enabled secure webapps implementing notification feature. A NotifyBC server instance supports multiple notification services. A service is a topic of interest that user wants to receive updates. It is used as the partition of notification messages and user subscriptions. A user may subscribe to a service in multiple push delivery channels allowed. A user may subscribe to multiple services. In-app pull notification doesn't require subscription as it's not intrusive to user.

    notification

    • both in-app pull notifications (a.k.a. messages or alerts) and push notifications
    • multiple push notifications delivery channels
      • email
      • sms
    • unicast and broadcast message types
    • future-dated notifications
    • for in-app pull notifications
      • support read and deleted message states
      • message expiration
      • deleted messages are not deleted immediately for auditing and recovery purposes
    • for broadcast push notifications
      • allow both sync and async POST API calls. For async API call, an optional callback url is supported
      • can be auto-generated from RSS feeds
      • allow user to specify filter rules evaluated against data contained in the notification
      • allow sender to specify filter rules evaluated against data contained in the subscription
      • allow application developer to create custom filter functions used by the filter rules mentioned above

    subscription and un-subscription

    • verify the ownership of push notification subscription channel:
      • generates confirmation code based on a regex input
      • send confirmation request to unconfirmed subscription channel
      • verify confirmation code
    • generate random un-subscription code
    • send acknowledgement message after un-subscription for anonymous subscribers
    • bulk unsubscription
    • list-unsubscribe by email
    • track bounces and unsubscribe the recipient from all subscriptions when hard bounce count exceeds threshold
    • sms user can unsubscribe by replying a shortcode keyword with Swift sms provider

    mail merge

    Strings in notification or subscription message that are enclosed between curly braces { } are called tokens, also known as placeholders. Tokens are replaced based on the context of notification or subscription when dispatching the message. To avoid treating a string between curly braces as a token, escape the curly braces with backslash \\. For example \\{i_am_not_a_token\\} is not a token. It will be rendered as {i_am_not_a_token}.

    Tokens whose names are predetermined by NotifyBC are called static tokens; otherwise they are called dynamic tokens.

    static tokens

    NotifyBC recognizes following case-insensitive static tokens. Most of the names are self-explanatory.

    • {subscription_confirmation_url}
    • {subscription_confirmation_code}
    • {service_name}
    • {http_host} - http host in the form http(s): //<host_name>:<port>. The value is obtained from the http request that triggers the message
    • {rest_api_root} - REST API URL path prefix
    • {subscription_id}
    • anonymous unsubscription related tokens
      • {unsubscription_url}
      • {unsubscription_all_url} - url to unsubscribe all services the user has subscribed on this NotifyBC instance
      • {unsubscription_code}
      • {unsubscription_reversion_url}
      • {unsubscription_service_names} - includes {service_name} and additional services user has unsubscribed, prefixed with conditionally pluralized word service.

    dynamic tokens

    Dynamic tokens are replaced with correspondingly named sub-field of data field in the notification or subscription if exist. Qualify token name with notification:: or subscription:: to indicate the source of substitution. If token name is not qualified, then both notification and subscription are checked, with notification taking precedence. Nested and indexed sub-fields are supported.

    Examples

    • {notification::description} is replaced with field data.description of the notification if exist
    • {subscription::gender} is replaced with field data.gender of the subscription if exist
    • {addresses[0].city} is replaced with field data.addresses[0].city of the notification if exist; otherwise is replaced with field data.addresses[0].city of the subscription if exist
    • {nonexistingDataField} is unreplaced if neither notification nor subscription contains data.nonexistingDataField
    ',18),m=e("em",null,"{subscription::...}",-1),f=s('

    Notification by RSS feeds relies on dynamic token

    A notification created by RSS feeds relies on dynamic token to supply the context to message template. In this case the data field contains the RSS item.

    Architecture

    Request Types

    NotifyBC, designed to be a microservice, doesn't use full-blown ACL to secure API calls. Instead, it classifies incoming requests into admin and user types. The key difference is while both admin and user can subscribe to notifications, only admin can post notifications.

    Each type has two subtypes based on following criteria

    ',5),b=e("p",null,"super-admin, if the request meets both of the following two requirements",-1),g=e("p",null,"The request carries one of the following two attributes",-1),y=e("li",null,"the source ip is in the admin ip list",-1),w=e("em",null,"NotifyBC",-1),v=e("li",null,[e("p",null,"The request doesn't contain any of following case insensitive HTTP headers, with the first three being SiteMinder headers"),e("ul",null,[e("li",null,"sm_universalid"),e("li",null,"sm_user"),e("li",null,"smgov_userdisplayname"),e("li",null,"is_anonymous")])],-1),_=s('
  • admin, if the request is not super-admin and meets one of the following criteria

    • has a valid access token associated with an builtin admin user created and logged in using the administrator api, and the request doesn't contain any HTTP headers listed above
    • has a valid OIDC access token containing customizable admin profile attributes

    access token disambiguation

    Here the term access token has been used to refer two different things

    1. the token associated with a builtin admin user
    2. the token generated by OIDC provider.

    To reduce confusion, throughout the documentation the former is called access token and the latter is called OIDC access token.

  • authenticated user, if the request is neither super-admin nor admin, and meets one fo the following criteria

    • contains any of the 3 SiteMinder headers listed above, and comes from either trusted SiteMinder proxy or admin ip list
    • contains a valid OIDC access token
  • anonymous user, if the request doesn't meet any of the above criteria

  • ',3),k=s("

    The only extra privileges that a super-admin has over admin are that super-admin can perform CRUD operations on configuration, bounce and administrator entities through REST API. In the remaining docs, when no further distinction is necessary, an admin request refers to both super-admin and admin request; a user request refers to both authenticated and anonymous request.

    An admin request carries full authorization whereas user request has limited access. For example, a user request is not allowed to

    • send notification
    • bypass the delivery channel confirmation process when subscribing to a service
    • retrieve push notifications through API (can only receive notification from push notification channel such as email)
    • retrieve in-app notifications that is not targeted to the current user

    The result of an API call to the same end point may differ depending on the request type. For example, the call GET /notifications without a filter will return all notifications to all users for an admin request, but only non-deleted, non-expired in-app notifications for authenticated user request, and forbidden for anonymous user request. Sometimes it is desirable for a request from admin ip list, which would normally be admin request, to be voluntarily downgraded to user request in order to take advantage of predefined filters such as the ones described above. This can be achieved by adding one of the HTTP headers listed above to the request. This is also why admin request is not determined by ip or token alone.

    ",4),x=e("em",null,"NotifyBC",-1),q=["src"],C=s('

    Authentication Strategies

    API requests to NotifyBC can be either anonymous or authenticated. As alluded in Request Types above, NotifyBC supports following authentication strategies

    1. ip whitelisting
    2. client certificate
    3. access token associated with an builtin admin user
    4. OpenID Connect (OIDC)
    5. CA SiteMinder

    Authentication is performed in above order. Once a request passed an authentication strategy, the rest strategies are skipped. A request that failed all authentication strategies is anonymous.

    The mapping between authentication strategy and request type is

    AdminUser
    Super-adminadminauthenticatedanonymous
    ip whitelisting
    client certifcate
    access token
    OIDC
    SiteMinder

    Which authentication strategy to use?

    Because ip whitelist doesn't expire and client certificate usually has a relatively long expiration period (say one year), they are suitable for long-running unattended server processes such as server-side code of web apps, cron jobs, IOT sensors etc. The server processes have to be trusted because once authenticated, they have full privilege to NotifyBC. Usually the server processes and NotifyBC instance are in the same administrative domain, i.e. managed by the same admin group of an organization.

    By contrast, OIDC and SiteMinder use short-lived tokens or session cookies. Therefore they are only suitable for interactive user sessions.

    Access token associated with an builtin admin user should be avoided whenever possible.

    Here are some common scenarios and recommendations

    • For server-side code of web apps

      • use OIDC if the web app is OIDC enabled and user requests can be proxied to NotifyBC by web app; otherwise
      • use ip whitelisting if obtaining ip is feasible; otherwise
      • use client certificate (requires a little more config than ip whitelisting)
    • For front-end browser-based web apps such as SPAs

      • use OIDC
    • For server apps that send requests spontaneously such as IOT sensors, cron jobs

      • use ip whitelisting if obtaining ip is feasible; otherwise
      • client certificate
    • If NotifyBC is ued by a SiteMinder protected web apps and NotifyBC is also protected by SiteMinder

      • use SiteMinder

    Application Framework

    ',8),I=e("em",null,"NotifyBC",-1),S={href:"https://nestjs.com/",target:"_blank",rel:"noopener noreferrer"},T=e("em",null,"NotifyBC",-1),A={href:"https://docs.nestjs.com/",target:"_blank",rel:"noopener noreferrer"};function N(l,B){const n=r("RouterLink"),o=r("ExternalLinkIcon");return d(),u("div",null,[p,e("p",null,[i("As exception, in order to prevent spamming by unconfirmed subscribers, dynamic tokens in subscription "),t(n,{to:"/docs/config-subscription/#confirmation-request-message"},{default:a(()=>[i("confirmation request message")]),_:1}),i(" and "),t(n,{to:"/docs/config-subscription/#duplicated-subscription"},{default:a(()=>[i("duplicated subscription")]),_:1}),i(" message are not replaced with subscription data, for example "),m,i(" tokens are left unchanged.")]),f,e("ul",null,[e("li",null,[b,e("ol",null,[e("li",null,[g,e("ul",null,[y,e("li",null,[i("has a client certificate that is signed using "),w,i(" server certificate. See "),t(n,{to:"/docs/config/certificates.html#client-certificate-authentication"},{default:a(()=>[i("Client certificate authentication")]),_:1}),i(" on how to sign.")])])]),v])]),_]),k,e("p",null,[i("The way "),x,i(" interacts with other components is diagrammed below. "),e("img",{src:l.$withBase("/img/architecture.svg"),alt:"architecture diagram"},null,8,q)]),C,e("p",null,[I,i(" is created on "),e("a",S,[i("NestJS"),t(o)]),i(". Contributors to source code of "),T,i(" should be familiar with NestJS. "),e("a",A,[i("NestJS Docs"),t(o)]),i(" serves a good complement to this documentation.")])])}const O=c(h,[["render",N],["__file","index.html.vue"]]);export{O as default}; +import{_ as c,r,o as d,c as u,a as e,b as i,d as t,w as a,e as s}from"./app-be471a62.js";const h={},p=s('

    Overview

    NotifyBC is a general purpose API Server to manage subscriptions and dispatch notifications. It aims to implement some common backend processes of a notification service without imposing any constraints on the UI frontend, nor impeding other server components' functionality. This is achieved by interacting with user browser and other server components through RESTful API and other standard protocols in a loosely coupled way.

    Features

    NotifyBC facilitates both anonymous and authentication-enabled secure webapps implementing notification feature. A NotifyBC server instance supports multiple notification services. A service is a topic of interest that user wants to receive updates. It is used as the partition of notification messages and user subscriptions. A user may subscribe to a service in multiple push delivery channels allowed. A user may subscribe to multiple services. In-app pull notification doesn't require subscription as it's not intrusive to user.

    notification

    • both in-app pull notifications (a.k.a. messages or alerts) and push notifications
    • multiple push notifications delivery channels
      • email
      • sms
    • unicast and broadcast message types
    • future-dated notifications
    • for in-app pull notifications
      • support read and deleted message states
      • message expiration
      • deleted messages are not deleted immediately for auditing and recovery purposes
    • for broadcast push notifications
      • allow both sync and async POST API calls. For async API call, an optional callback url is supported
      • can be auto-generated from RSS feeds
      • allow user to specify filter rules evaluated against data contained in the notification
      • allow sender to specify filter rules evaluated against data contained in the subscription
      • allow application developer to create custom filter functions used by the filter rules mentioned above

    subscription and un-subscription

    • verify the ownership of push notification subscription channel:
      • generates confirmation code based on a regex input
      • send confirmation request to unconfirmed subscription channel
      • verify confirmation code
    • generate random un-subscription code
    • send acknowledgement message after un-subscription for anonymous subscribers
    • bulk unsubscription
    • list-unsubscribe by email
    • track bounces and unsubscribe the recipient from all subscriptions when hard bounce count exceeds threshold
    • sms user can unsubscribe by replying a shortcode keyword with Swift sms provider

    mail merge

    Strings in notification or subscription message that are enclosed between curly braces { } are called tokens, also known as placeholders. Tokens are replaced based on the context of notification or subscription when dispatching the message. To avoid treating a string between curly braces as a token, escape the curly braces with backslash \\. For example \\{i_am_not_a_token\\} is not a token. It will be rendered as {i_am_not_a_token}.

    Tokens whose names are predetermined by NotifyBC are called static tokens; otherwise they are called dynamic tokens.

    static tokens

    NotifyBC recognizes following case-insensitive static tokens. Most of the names are self-explanatory.

    • {subscription_confirmation_url}
    • {subscription_confirmation_code}
    • {service_name}
    • {http_host} - http host in the form http(s): //<host_name>:<port>. The value is obtained from the http request that triggers the message
    • {rest_api_root} - REST API URL path prefix
    • {subscription_id}
    • anonymous unsubscription related tokens
      • {unsubscription_url}
      • {unsubscription_all_url} - url to unsubscribe all services the user has subscribed on this NotifyBC instance
      • {unsubscription_code}
      • {unsubscription_reversion_url}
      • {unsubscription_service_names} - includes {service_name} and additional services user has unsubscribed, prefixed with conditionally pluralized word service.

    dynamic tokens

    Dynamic tokens are replaced with correspondingly named sub-field of data field in the notification or subscription if exist. Qualify token name with notification:: or subscription:: to indicate the source of substitution. If token name is not qualified, then both notification and subscription are checked, with notification taking precedence. Nested and indexed sub-fields are supported.

    Examples

    • {notification::description} is replaced with field data.description of the notification if exist
    • {subscription::gender} is replaced with field data.gender of the subscription if exist
    • {addresses[0].city} is replaced with field data.addresses[0].city of the notification if exist; otherwise is replaced with field data.addresses[0].city of the subscription if exist
    • {nonexistingDataField} is unreplaced if neither notification nor subscription contains data.nonexistingDataField
    ',18),m=e("em",null,"{subscription::...}",-1),f=s('

    Notification by RSS feeds relies on dynamic token

    A notification created by RSS feeds relies on dynamic token to supply the context to message template. In this case the data field contains the RSS item.

    Architecture

    Request Types

    NotifyBC, designed to be a microservice, doesn't use full-blown ACL to secure API calls. Instead, it classifies incoming requests into admin and user types. The key difference is while both admin and user can subscribe to notifications, only admin can post notifications.

    Each type has two subtypes based on following criteria

    ',5),b=e("p",null,"super-admin, if the request meets both of the following two requirements",-1),g=e("p",null,"The request carries one of the following two attributes",-1),y=e("li",null,"the source ip is in the admin ip list",-1),w=e("em",null,"NotifyBC",-1),v=e("li",null,[e("p",null,"The request doesn't contain any of following case insensitive HTTP headers, with the first three being SiteMinder headers"),e("ul",null,[e("li",null,"sm_universalid"),e("li",null,"sm_user"),e("li",null,"smgov_userdisplayname"),e("li",null,"is_anonymous")])],-1),_=s('
  • admin, if the request is not super-admin and meets one of the following criteria

    • has a valid access token associated with an builtin admin user created and logged in using the administrator api, and the request doesn't contain any HTTP headers listed above
    • has a valid OIDC access token containing customizable admin profile attributes

    access token disambiguation

    Here the term access token has been used to refer two different things

    1. the token associated with a builtin admin user
    2. the token generated by OIDC provider.

    To reduce confusion, throughout the documentation the former is called access token and the latter is called OIDC access token.

  • authenticated user, if the request is neither super-admin nor admin, and meets one fo the following criteria

    • contains any of the 3 SiteMinder headers listed above, and comes from either trusted SiteMinder proxy or admin ip list
    • contains a valid OIDC access token
  • anonymous user, if the request doesn't meet any of the above criteria

  • ',3),k=s("

    The only extra privileges that a super-admin has over admin are that super-admin can perform CRUD operations on configuration, bounce and administrator entities through REST API. In the remaining docs, when no further distinction is necessary, an admin request refers to both super-admin and admin request; a user request refers to both authenticated and anonymous request.

    An admin request carries full authorization whereas user request has limited access. For example, a user request is not allowed to

    • send notification
    • bypass the delivery channel confirmation process when subscribing to a service
    • retrieve push notifications through API (can only receive notification from push notification channel such as email)
    • retrieve in-app notifications that is not targeted to the current user

    The result of an API call to the same end point may differ depending on the request type. For example, the call GET /notifications without a filter will return all notifications to all users for an admin request, but only non-deleted, non-expired in-app notifications for authenticated user request, and forbidden for anonymous user request. Sometimes it is desirable for a request from admin ip list, which would normally be admin request, to be voluntarily downgraded to user request in order to take advantage of predefined filters such as the ones described above. This can be achieved by adding one of the HTTP headers listed above to the request. This is also why admin request is not determined by ip or token alone.

    ",4),x=e("em",null,"NotifyBC",-1),q=["src"],C=s('

    Authentication Strategies

    API requests to NotifyBC can be either anonymous or authenticated. As alluded in Request Types above, NotifyBC supports following authentication strategies

    1. ip whitelisting
    2. client certificate
    3. access token associated with an builtin admin user
    4. OpenID Connect (OIDC)
    5. CA SiteMinder

    Authentication is performed in above order. Once a request passed an authentication strategy, the rest strategies are skipped. A request that failed all authentication strategies is anonymous.

    The mapping between authentication strategy and request type is

    AdminUser
    Super-adminadminauthenticatedanonymous
    ip whitelisting
    client certifcate
    access token
    OIDC
    SiteMinder

    Which authentication strategy to use?

    Because ip whitelist doesn't expire and client certificate usually has a relatively long expiration period (say one year), they are suitable for long-running unattended server processes such as server-side code of web apps, cron jobs, IOT sensors etc. The server processes have to be trusted because once authenticated, they have full privilege to NotifyBC. Usually the server processes and NotifyBC instance are in the same administrative domain, i.e. managed by the same admin group of an organization.

    By contrast, OIDC and SiteMinder use short-lived tokens or session cookies. Therefore they are only suitable for interactive user sessions.

    Access token associated with an builtin admin user should be avoided whenever possible.

    Here are some common scenarios and recommendations

    • For server-side code of web apps

      • use OIDC if the web app is OIDC enabled and user requests can be proxied to NotifyBC by web app; otherwise
      • use ip whitelisting if obtaining ip is feasible; otherwise
      • use client certificate (requires a little more config than ip whitelisting)
    • For front-end browser-based web apps such as SPAs

      • use OIDC
    • For server apps that send requests spontaneously such as IOT sensors, cron jobs

      • use ip whitelisting if obtaining ip is feasible; otherwise
      • client certificate
    • If NotifyBC is ued by a SiteMinder protected web apps and NotifyBC is also protected by SiteMinder

      • use SiteMinder

    Application Framework

    ',8),I=e("em",null,"NotifyBC",-1),S={href:"https://nestjs.com/",target:"_blank",rel:"noopener noreferrer"},T=e("em",null,"NotifyBC",-1),A={href:"https://docs.nestjs.com/",target:"_blank",rel:"noopener noreferrer"};function N(l,B){const n=r("RouterLink"),o=r("ExternalLinkIcon");return d(),u("div",null,[p,e("p",null,[i("As exception, in order to prevent spamming by unconfirmed subscribers, dynamic tokens in subscription "),t(n,{to:"/docs/config-subscription/#confirmation-request-message"},{default:a(()=>[i("confirmation request message")]),_:1}),i(" and "),t(n,{to:"/docs/config-subscription/#duplicated-subscription"},{default:a(()=>[i("duplicated subscription")]),_:1}),i(" message are not replaced with subscription data, for example "),m,i(" tokens are left unchanged.")]),f,e("ul",null,[e("li",null,[b,e("ol",null,[e("li",null,[g,e("ul",null,[y,e("li",null,[i("has a client certificate that is signed using "),w,i(" server certificate. See "),t(n,{to:"/docs/config/certificates.html#client-certificate-authentication"},{default:a(()=>[i("Client certificate authentication")]),_:1}),i(" on how to sign.")])])]),v])]),_]),k,e("p",null,[i("The way "),x,i(" interacts with other components is diagrammed below. "),e("img",{src:l.$withBase("/img/architecture.svg"),alt:"architecture diagram"},null,8,q)]),C,e("p",null,[I,i(" is created on "),e("a",S,[i("NestJS"),t(o)]),i(". Contributors to source code of "),T,i(" should be familiar with NestJS. "),e("a",A,[i("NestJS Docs"),t(o)]),i(" serves a good complement to this documentation.")])])}const O=c(h,[["render",N],["__file","index.html.vue"]]);export{O as default}; diff --git a/assets/index.html-819c8b73.js b/assets/index.html-421ecf2b.js similarity index 99% rename from assets/index.html-819c8b73.js rename to assets/index.html-421ecf2b.js index dff61ed0a..64493471b 100644 --- a/assets/index.html-819c8b73.js +++ b/assets/index.html-421ecf2b.js @@ -1,4 +1,4 @@ -import{_ as l,r as o,o as p,c as r,a as n,b as s,d as e,w as c,e as a}from"./app-77c416fe.js";const u={},d=a('

    Benchmarks

    tl;dr

    A NotifyBC server node can deliver 1 million emails in as little as 1 hour to a SMTP server node. SMTP server node's disk I/O is the bottleneck in such case. Throughput can be improved through horizontal scaling.

    When NotifyBC is used to deliver broadcast push notifications to a large number of subscribers, probably the most important benchmark is throughput. The benchmark is especially critical if a latency cap is desired. To facilitate capacity planning, load testing on the email channel has been conducted. The test environment, procedure, results and performance tuning advices are provided hereafter.

    Environment

    Hardware

    Two computers, connected by 1Gbps LAN, are used to host

    • NotifyBC
      • Mac Mini Late 2012 model
      • Intel core i7-3615QM
      • 16GB RAM
      • 2TB HDD
    • SMTP and mail delivery
      • Lenovo ThinkCentre M Series 2015 model
      • Intel core i5-3470
      • 8GB RAM
      • 256GB SSD

    Software Stack

    The test was performed in August 2017. Unless otherwise specified, the versions of all other software were reasonably up-to-date at the time of testing.

    • NotifyBC

      • MacOS Sierra Version 10.12.6
      • Virtualbox VM with 8vCPU, 10GB RAM, created using miniShift v1.3.1+f4900b07
      • OpenShift 1.5.1+7b451fc with metrics
      • default NotifyBC OpenShift installation, which contains following relevant pods
        • 1 mongodb pod with 1 core, 1GiB RAM limit
        • a variable number of Node.js app pods each with 1 core, 1GiB RAM limit. The number varies by test runs as indicated in result.
    • SMTP and mail delivery

      • Windows 7 host
      • Virtualbox VM with 4 vCPU, 3.5GB RAM, running Windows Server 2012
      • added SMTP Server feature
      • in SMTP Server properties dialog box, uncheck all of following boxes in Messages tab
        • Limit message size to (KB)
        • Limit session size to (KB)
        • Limit number of messages per connection to
        • Limit number of recipients per message to

    Procedure

    ',11),h=n("em",null,"/src/config.local.js",-1),m=a(`
    var _ = require('lodash');
    +import{_ as l,r as o,o as p,c as r,a as n,b as s,d as e,w as c,e as a}from"./app-be471a62.js";const u={},d=a('

    Benchmarks

    tl;dr

    A NotifyBC server node can deliver 1 million emails in as little as 1 hour to a SMTP server node. SMTP server node's disk I/O is the bottleneck in such case. Throughput can be improved through horizontal scaling.

    When NotifyBC is used to deliver broadcast push notifications to a large number of subscribers, probably the most important benchmark is throughput. The benchmark is especially critical if a latency cap is desired. To facilitate capacity planning, load testing on the email channel has been conducted. The test environment, procedure, results and performance tuning advices are provided hereafter.

    Environment

    Hardware

    Two computers, connected by 1Gbps LAN, are used to host

    • NotifyBC
      • Mac Mini Late 2012 model
      • Intel core i7-3615QM
      • 16GB RAM
      • 2TB HDD
    • SMTP and mail delivery
      • Lenovo ThinkCentre M Series 2015 model
      • Intel core i5-3470
      • 8GB RAM
      • 256GB SSD

    Software Stack

    The test was performed in August 2017. Unless otherwise specified, the versions of all other software were reasonably up-to-date at the time of testing.

    • NotifyBC

      • MacOS Sierra Version 10.12.6
      • Virtualbox VM with 8vCPU, 10GB RAM, created using miniShift v1.3.1+f4900b07
      • OpenShift 1.5.1+7b451fc with metrics
      • default NotifyBC OpenShift installation, which contains following relevant pods
        • 1 mongodb pod with 1 core, 1GiB RAM limit
        • a variable number of Node.js app pods each with 1 core, 1GiB RAM limit. The number varies by test runs as indicated in result.
    • SMTP and mail delivery

      • Windows 7 host
      • Virtualbox VM with 4 vCPU, 3.5GB RAM, running Windows Server 2012
      • added SMTP Server feature
      • in SMTP Server properties dialog box, uncheck all of following boxes in Messages tab
        • Limit message size to (KB)
        • Limit session size to (KB)
        • Limit number of messages per connection to
        • Limit number of recipients per message to

    Procedure

    ',11),h=n("em",null,"/src/config.local.js",-1),m=a(`
    var _ = require('lodash');
     module.exports = {
       smtp: {
         host: '<smtp-vm-ip-or-hostname>',
    diff --git a/assets/index.html-8e2d950c.js b/assets/index.html-45a39212.js
    similarity index 99%
    rename from assets/index.html-8e2d950c.js
    rename to assets/index.html-45a39212.js
    index bff54eb43..aa327cad2 100644
    --- a/assets/index.html-8e2d950c.js
    +++ b/assets/index.html-45a39212.js
    @@ -1,4 +1,4 @@
    -import{_ as c,r as o,o as r,c as l,a as s,b as n,d as a,w as i,e}from"./app-77c416fe.js";const u={},d=e(`

    Subscription

    Configs in this section customize behavior of subscription and unsubscription workflow. They are all sub-properties of config object subscription. This object can be defined as service-agnostic static config as well as service-specific dynamic config, which overrides the static one on a service-by-service basis. Default static config is defined in file /src/config.ts. There is no default dynamic config.

    To customize static config, create the config object subscription in file /src/config.local.js

    module.exports = {
    +import{_ as c,r as o,o as r,c as l,a as s,b as n,d as a,w as i,e}from"./app-be471a62.js";const u={},d=e(`

    Subscription

    Configs in this section customize behavior of subscription and unsubscription workflow. They are all sub-properties of config object subscription. This object can be defined as service-agnostic static config as well as service-specific dynamic config, which overrides the static one on a service-by-service basis. Default static config is defined in file /src/config.ts. There is no default dynamic config.

    To customize static config, create the config object subscription in file /src/config.local.js

    module.exports = {
       "subscription": {
         ...
       }
    diff --git a/assets/index.html-46fb1d05.js b/assets/index.html-4a2d667d.js
    similarity index 97%
    rename from assets/index.html-46fb1d05.js
    rename to assets/index.html-4a2d667d.js
    index 00e78d4ed..4d9bca064 100644
    --- a/assets/index.html-46fb1d05.js
    +++ b/assets/index.html-4a2d667d.js
    @@ -1,4 +1,4 @@
    -import{_ as s,r as a,o as i,c as o,a as e,b as t,d as r,w as l,e as c}from"./app-77c416fe.js";const p={},u=c(`

    Internal HTTP Host

    By default, HTTP requests submitted by NotifyBC back to itself will be sent to httpHost if defined or the host of the incoming HTTP request that spawns such internal requests. But if config internalHttpHost, which has no default value, is defined, for example in file /src/config.local.js

    module.exports = {
    +import{_ as s,r as a,o as i,c as o,a as e,b as t,d as r,w as l,e as c}from"./app-be471a62.js";const p={},u=c(`

    Internal HTTP Host

    By default, HTTP requests submitted by NotifyBC back to itself will be sent to httpHost if defined or the host of the incoming HTTP request that spawns such internal requests. But if config internalHttpHost, which has no default value, is defined, for example in file /src/config.local.js

    module.exports = {
       internalHttpHost: 'http://notifybc:3000',
     };
     
    `,3),d=e("em",null,"internalHttpHost",-1),h=e("p",null,[t("All internal requests are supposed to be admin requests. The purpose of "),e("em",null,"internalHttpHost"),t(" is to facilitate identifying the internal server ip as admin ip.")],-1),m=e("div",{class:"custom-container tip"},[e("p",{class:"custom-container-title"},"Kubernetes Use Case"),e("p",null,[t("The Kubernetes deployment script sets "),e("i",null,"internalHttpHost"),t(" to "),e("em",null,"notify-bc-app"),t(" service url in config map. The source ip in such case would be in a private Kubernetes ip range. You should add this private ip range to "),e("a",{href:"#admin-ip-list"},"admin ip list"),t(". The private ip range varies from Kubernetes installation. In BCGov's OCP4 cluster, it starts with octet 10.")])],-1);function f(b,v){const n=a("RouterLink");return i(),o("div",null,[u,e("p",null,[t("then the HTTP request will be sent to the configured host. An internal request can be generated, for example, as a "),r(n,{to:"/docs/config-notification/#broadcast-push-notification-task-concurrency"},{default:l(()=>[t("sub-request of broadcast push notification")]),_:1}),t(". "),d,t(" shouldn't be accessible from internet.")]),h,m])}const g=s(p,[["render",f],["__file","index.html.vue"]]);export{g as default}; diff --git a/assets/index.html-8b2ab8f8.js b/assets/index.html-4b695468.js similarity index 99% rename from assets/index.html-8b2ab8f8.js rename to assets/index.html-4b695468.js index 55cb86938..ade762424 100644 --- a/assets/index.html-8b2ab8f8.js +++ b/assets/index.html-4b695468.js @@ -1,4 +1,4 @@ -import{_ as o,r as i,o as p,c,a as e,b as n,d as a,w as t,e as r}from"./app-77c416fe.js";const l={},d=e("h1",{id:"rsa-keys",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#rsa-keys","aria-hidden":"true"},"#"),n(" RSA Keys")],-1),u=e("em",null,"NotifyBC",-1),h=e("em",null,"cURL",-1),k=r(`
    curl -X GET 'http://localhost:3000/api/configurations?filter=%7B%22where%22%3A%20%7B%22name%22%3A%20%22rsa%22%7D%7D'
    +import{_ as o,r as i,o as p,c,a as e,b as n,d as a,w as t,e as r}from"./app-be471a62.js";const l={},d=e("h1",{id:"rsa-keys",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#rsa-keys","aria-hidden":"true"},"#"),n(" RSA Keys")],-1),u=e("em",null,"NotifyBC",-1),h=e("em",null,"cURL",-1),k=r(`
    curl -X GET 'http://localhost:3000/api/configurations?filter=%7B%22where%22%3A%20%7B%22name%22%3A%20%22rsa%22%7D%7D'
     

    or you can open API explorer, expand GET /configurations and set filter to

    {"where": {"name": "rsa"}}
     

    The response should be something like

    [
       {
    diff --git a/assets/index.html-091f58dc.js b/assets/index.html-566c81a6.js
    similarity index 93%
    rename from assets/index.html-091f58dc.js
    rename to assets/index.html-566c81a6.js
    index 7dc65b650..7f5e60e94 100644
    --- a/assets/index.html-091f58dc.js
    +++ b/assets/index.html-566c81a6.js
    @@ -1 +1 @@
    -import{_ as e,o as t,c as o,e as r}from"./app-77c416fe.js";const n={},s=r('

    Worker Process Count

    When NotifyBC runs on a host with multiple CPUs, by default it creates a cluster of worker processes of which the count matches CPU count. You can override the number with the environment variable NOTIFYBC_WORKER_PROCESS_COUNT.

    A note about worker process count on OpenShift

    It has been observed that on OpenShift Node.js returns incorrect CPU count. The template therefore sets NOTIFYBC_WORKER_PROCESS_COUNT to 1. After all, on OpenShift NotifyBC is expected to be horizontally scaled by pods rather by CPUs.

    ',3),c=[s];function a(i,h){return t(),o("div",null,c)}const d=e(n,[["render",a],["__file","index.html.vue"]]);export{d as default}; +import{_ as e,o as t,c as o,e as r}from"./app-be471a62.js";const n={},s=r('

    Worker Process Count

    When NotifyBC runs on a host with multiple CPUs, by default it creates a cluster of worker processes of which the count matches CPU count. You can override the number with the environment variable NOTIFYBC_WORKER_PROCESS_COUNT.

    A note about worker process count on OpenShift

    It has been observed that on OpenShift Node.js returns incorrect CPU count. The template therefore sets NOTIFYBC_WORKER_PROCESS_COUNT to 1. After all, on OpenShift NotifyBC is expected to be horizontally scaled by pods rather by CPUs.

    ',3),c=[s];function a(i,h){return t(),o("div",null,c)}const d=e(n,[["render",a],["__file","index.html.vue"]]);export{d as default}; diff --git a/assets/index.html-05c25248.js b/assets/index.html-599e9037.js similarity index 95% rename from assets/index.html-05c25248.js rename to assets/index.html-599e9037.js index 6ed99986d..3d12bd79c 100644 --- a/assets/index.html-05c25248.js +++ b/assets/index.html-599e9037.js @@ -1,4 +1,4 @@ -import{_ as e,o as t,c as s,e as n}from"./app-77c416fe.js";const a={},o=n(`

    HTTP Host

    httpHost config sets the fallback http host used by

    • mail merge token substitution
    • internal HTTP requests spawned by NotifyBC

    httpHost can be overridden by other configs or data. For example

    • internalHttpHost config
    • httpHost field in a notification

    There are contexts where there is no alternatives to httpHost. Therefore this config should be defined.

    Define the config, which has no default value, in /src/config.local.js

    module.exports = {
    +import{_ as e,o as t,c as s,e as n}from"./app-be471a62.js";const a={},o=n(`

    HTTP Host

    httpHost config sets the fallback http host used by

    • mail merge token substitution
    • internal HTTP requests spawned by NotifyBC

    httpHost can be overridden by other configs or data. For example

    • internalHttpHost config
    • httpHost field in a notification

    There are contexts where there is no alternatives to httpHost. Therefore this config should be defined.

    Define the config, which has no default value, in /src/config.local.js

    module.exports = {
       httpHost: 'http://foo.com',
     };
     
    `,8),i=[o];function l(p,c){return t(),s("div",null,i)}const d=e(a,[["render",l],["__file","index.html.vue"]]);export{d as default}; diff --git a/assets/index.html-ac8da399.js b/assets/index.html-59f066f2.js similarity index 94% rename from assets/index.html-ac8da399.js rename to assets/index.html-59f066f2.js index 9ca6c6331..b52472265 100644 --- a/assets/index.html-ac8da399.js +++ b/assets/index.html-59f066f2.js @@ -1 +1 @@ -import{_ as e,o as i,c as a,e as o}from"./app-77c416fe.js";const t={},n=o('

    API Overview

    NotifyBC's core function is implemented by two models - subscription and notification. Other models - configuration, administrator and bounces etc, are for administrative purposes. A model determines the underlying database schema and the API. The APIs displayed in the web console (by default http://localhost:3000) and API explorer are also grouped by models. Click on a model in API explorer, say notification, to explore the operations on that model. Model specific APIs are available here:

    ',3),r=[n];function l(s,c){return i(),a("div",null,r)}const h=e(t,[["render",l],["__file","index.html.vue"]]);export{h as default}; +import{_ as e,o as i,c as a,e as o}from"./app-be471a62.js";const t={},n=o('

    API Overview

    NotifyBC's core function is implemented by two models - subscription and notification. Other models - configuration, administrator and bounces etc, are for administrative purposes. A model determines the underlying database schema and the API. The APIs displayed in the web console (by default http://localhost:3000) and API explorer are also grouped by models. Click on a model in API explorer, say notification, to explore the operations on that model. Model specific APIs are available here:

    ',3),r=[n];function l(s,c){return i(),a("div",null,r)}const h=e(t,[["render",l],["__file","index.html.vue"]]);export{h as default}; diff --git a/assets/index.html-9d0a34f4.js b/assets/index.html-64244dc0.js similarity index 98% rename from assets/index.html-9d0a34f4.js rename to assets/index.html-64244dc0.js index c19c9e300..d697ba23a 100644 --- a/assets/index.html-9d0a34f4.js +++ b/assets/index.html-64244dc0.js @@ -1,3 +1,3 @@ -import{_ as r,r as o,o as l,c,a as e,b as t,d as n,w as d,e as a}from"./app-77c416fe.js";const u={},h=a('

    Developer Notes

    Setup development environment

    Install Visual Studio Code and following extensions:

    • Prettier
    • ESLint
    • Vetur
    • Code Spell Checker
    • Debugger for Chrome

    Multiple run configs have been created to facilitate debugging server, client, test and docs.

    Client certificate authentication doesn't work in client debugger

    Because Vue cli webpack dev server cannot proxy passthrough HTTPS connections, client certificate authentication doesn't work in client debugger. If testing client certificate authentication in web console is needed, run npm run build to generate prod client distribution and launch server debugger on https://localhost:3000

    Automated Testing

    ',7),p=e("em",null,"NotifyBC",-1),m={href:"https://jestjs.io/",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"npm run test:e2e",-1),f=e("em",null,"Test",-1),g=e("p",null,"Github Actions runs tests as part of the build. All test scripts should be able to run unattended, headless, quickly and depend only on local resources.",-1),_=e("h3",{id:"writing-test-specs",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#writing-test-specs","aria-hidden":"true"},"#"),t(" Writing Test Specs")],-1),v={href:"https://github.com/visionmedia/supertest",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/nodkz/mongodb-memory-server",target:"_blank",rel:"noopener noreferrer"},w=e("em",null,"sendMail",-1),x=e("em",null,"sendSMS",-1),y=a(`
    • start at a processing phase as early as possible. For example, to test a REST end point, start with the HTTP user request.
    • assert outcome of a processing phase as late and down below as possible - the HTTP response body/code, the database record created, for example.
    • avoid asserting middleware function input/output to facilitate code refactoring.
    • mock email/sms sending function (implemented by default). Inspect the input of the function, or at least assert the function has been called.

    Install Docs Website

    If you want to contribute to NotifyBC docs beyond simple fix ups, run

    cd docs && npm install && npm run dev
    +import{_ as r,r as o,o as l,c,a as e,b as t,d as n,w as d,e as a}from"./app-be471a62.js";const u={},h=a('

    Developer Notes

    Setup development environment

    Install Visual Studio Code and following extensions:

    • Prettier
    • ESLint
    • Vetur
    • Code Spell Checker
    • Debugger for Chrome

    Multiple run configs have been created to facilitate debugging server, client, test and docs.

    Client certificate authentication doesn't work in client debugger

    Because Vue cli webpack dev server cannot proxy passthrough HTTPS connections, client certificate authentication doesn't work in client debugger. If testing client certificate authentication in web console is needed, run npm run build to generate prod client distribution and launch server debugger on https://localhost:3000

    Automated Testing

    ',7),p=e("em",null,"NotifyBC",-1),m={href:"https://jestjs.io/",target:"_blank",rel:"noopener noreferrer"},b=e("code",null,"npm run test:e2e",-1),f=e("em",null,"Test",-1),g=e("p",null,"Github Actions runs tests as part of the build. All test scripts should be able to run unattended, headless, quickly and depend only on local resources.",-1),_=e("h3",{id:"writing-test-specs",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#writing-test-specs","aria-hidden":"true"},"#"),t(" Writing Test Specs")],-1),v={href:"https://github.com/visionmedia/supertest",target:"_blank",rel:"noopener noreferrer"},k={href:"https://github.com/nodkz/mongodb-memory-server",target:"_blank",rel:"noopener noreferrer"},w=e("em",null,"sendMail",-1),x=e("em",null,"sendSMS",-1),y=a(`
    • start at a processing phase as early as possible. For example, to test a REST end point, start with the HTTP user request.
    • assert outcome of a processing phase as late and down below as possible - the HTTP response body/code, the database record created, for example.
    • avoid asserting middleware function input/output to facilitate code refactoring.
    • mock email/sms sending function (implemented by default). Inspect the input of the function, or at least assert the function has been called.

    Install Docs Website

    If you want to contribute to NotifyBC docs beyond simple fix ups, run

    cd docs && npm install && npm run dev
     

    If everything goes well, the last line of the output will be

    > VuePress dev server listening at http://localhost:8080/NotifyBC/
     
    `,6),C={href:"http://localhost:8080/NotifyBC/",target:"_blank",rel:"noopener noreferrer"},T=e("h2",{id:"publish-version-checklist",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#publish-version-checklist","aria-hidden":"true"},"#"),t(" Publish Version Checklist")],-1),S=e("li",null,[t("update "),e("em",null,"version"),t(" in "),e("em",null,"package.json")],-1),N=e("li",null,[t("update "),e("em",null,"version"),t(),e("em",null,"appVersion"),t(" in "),e("em",null,"helm/Chart.yaml"),t(" (major/minor only)")],-1),V=e("li",null,"create a new Github release",-1);function B(I,P){const s=o("ExternalLinkIcon"),i=o("RouterLink");return l(),c("div",null,[h,e("p",null,[p,t(" uses "),e("a",m,[t("Jest"),n(s)]),t(" test framework bundled in NestJS. To launch test, run "),b,t(". A "),f,t(" launch config is provided to debug in VS Code.")]),g,_,e("p",null,[t("Thanks to "),e("a",v,[t("supertest"),n(s)]),t(" and "),e("a",k,[t("MongoDB In-Memory Server"),n(s)]),t(", test specs can be written to cover nearly end-to-end request processing workflow (only "),w,t(" and "),x,t(" need to be mocked). This allows test specs to anchor onto business requirements rather than program units such as functions or files, resulting in regression tests that are more resilient to code refactoring. Whenever possible, a test spec should be written to")]),y,e("p",null,[t("You can now browse to the local docs site "),e("a",C,[t("http://localhost:8080/NotifyBC"),n(s)])]),T,e("ol",null,[S,N,e("li",null,[t("update "),n(i,{to:"/docs/getting-started/what's-new.html"},{default:d(()=>[t("What's new")]),_:1}),t(" (major/minor only)")]),V])])}const E=r(u,[["render",B],["__file","index.html.vue"]]);export{E as default}; diff --git a/assets/index.html-5795a2ec.js b/assets/index.html-6906b24f.js similarity index 99% rename from assets/index.html-5795a2ec.js rename to assets/index.html-6906b24f.js index 924893acf..661aed2d7 100644 --- a/assets/index.html-5795a2ec.js +++ b/assets/index.html-6906b24f.js @@ -1,4 +1,4 @@ -import{_ as d,r as l,o as u,c,a as e,b as t,d as s,w as o,e as n}from"./app-77c416fe.js";const p={},m=e("h1",{id:"subscription",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#subscription","aria-hidden":"true"},"#"),t(" Subscription")],-1),b=e("p",null,[t("The subscription API encapsulates the backend workflow of user subscription and un-subscription of push notification service. Depending on whether a API call comes from user browser as a user request or from an authorized server as an admin request, "),e("em",null,"NotifyBC"),t(" applies different validation rules. For user requests, the notification channel entered by user is unconfirmed. A confirmation code will be associated with this request. The confirmation code can be created in one of two ways:")],-1),h=e("em",null,"NotifyBC",-1),f=e("em",null,"subscription.confirmationRequest..confirmationCodeRegex",-1),v=e("em",null,"NotifyBC",-1),g=e("em",null,"NotifyBC",-1),q=e("em",null,"NotifyBC",-1),y=e("em",null,"NotifyBC",-1),k=e("em",null,"NotifyBC",-1),_=e("p",null,[t("Equipped with the confirmation code and a message template, "),e("em",null,"NotifyBC"),t(" can now send out confirmation request to unconfirmed subscription channel. At a minimum this confirmation request should contain the confirmation code. When user receives the message, he/she echos the confirmation code back to a "),e("em",null,"NotifyBC"),t(" provided API to verify against saved record. If match, the state of the subscription request is changed to confirmed.")],-1),x=e("p",null,[t("For admin requests, "),e("em",null,"NotifyBC"),t(" can still perform the above confirmation process. But admin request has full CRUD privilege, including set the subscription state to confirmed, bypassing the confirmation process.")],-1),w=e("em",null,"NotifyBC",-1),C=["src"],j=e("p",null,[t("In the case user subscribing to notifications offered by different service providers in separate trust domains, the confirmation code is generated by a third-party server app trusted by all "),e("em",null,"NotifyBC"),t(" instances. Following sequence diagram shows the workflow. The diagram indicates "),e("em",null,"NotifyBC API Server 2"),t(" is chosen to send confirmation request.")],-1),I=["src"],N=n(`

    Model Schema

    The API operates on following subscription data model fields:

    NameAttributes

    serviceName

    name of the service. Avoid prefixing the name with underscore (_), or it may conflict with internal implementation.

    typestring
    requiredtrue

    channel

    name of the delivery channel. Valid values: email and sms. Notice inApp is invalid as in-app notification doesn't need subscription.

    typestring
    requiredtrue
    defaultemail

    userChannelId

    user's delivery channel id, for example, email address

    typestring
    requiredtrue

    id

    subscription id

    typestring, format depends on db
    requiredfalse
    auto-generatedtrue

    state

    state of subscription. Valid values: unconfirmed, confirmed, deleted

    typestring
    requiredfalse
    defaultunconfirmed

    userId

    user id. Auto-populated for authenticated user requests.

    typestring
    requiredfalse

    created

    date and time of creation

    typedate
    requiredfalse
    auto-generatedtrue

    updated

    date and time of last update

    typedate
    requiredfalse
    auto-generatedtrue

    confirmationRequest

    an object containing these child fields
    • confirmationCodeRegex
      • type: string
      • regular expression used to generate confirmation code
    • confirmationCodeEncrypted
      • type: string
      • encrypted confirmation code
    • sendRequest
      • type: boolean
      • whether to send confirmation request
    • from, subject, textBody, htmlBody
      • type: string
      • these are email template fields used for sending email confirmation request. If confirmationRequest.sendRequest is true and channel is email, then these fields should be supplied in order to send confirmation email.
    typeobject
    requiredtrue for user request with encrypted confirmation code; false otherwise

    broadcastPushNotificationFilter

    a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
    • simple
      province == 'BC'
    • calling jmespath's built-in functions
      contains(province,'B')
    • calling custom filter functions
      contains_ci(province,'b')
    • compound
      (contains(province,'BC') || contains_ci(province,'b')) && city == 'Victoria'
    All of above filters will match data object {"province": "BC", "city": "Victoria"}
    typestring
    requiredfalse

    data

    An object used by

    data object can only be populated by non-anonymous requests.

    typeobject
    requiredfalse

    unsubscriptionCode

    generated randomly according to RegEx config anonymousUnsubscription.code.regex during anonymous subscription if config anonymousUnsubscription.code.required is set to true

    typestring
    requiredfalse
    auto-generatedtrue

    unsubscribedAdditionalServices

    generated if parameter additionalServices is supplied in unsubscription request. Contains 2 sub-fields: ids and names, each being a list identifying the additional unsubscribed subscriptions.

    typeobject
    requiredfalse
    auto-generatedtrue

    Get Subscriptions

    GET /subscriptions
    +import{_ as d,r as l,o as u,c,a as e,b as t,d as s,w as o,e as n}from"./app-be471a62.js";const p={},m=e("h1",{id:"subscription",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#subscription","aria-hidden":"true"},"#"),t(" Subscription")],-1),b=e("p",null,[t("The subscription API encapsulates the backend workflow of user subscription and un-subscription of push notification service. Depending on whether a API call comes from user browser as a user request or from an authorized server as an admin request, "),e("em",null,"NotifyBC"),t(" applies different validation rules. For user requests, the notification channel entered by user is unconfirmed. A confirmation code will be associated with this request. The confirmation code can be created in one of two ways:")],-1),h=e("em",null,"NotifyBC",-1),f=e("em",null,"subscription.confirmationRequest..confirmationCodeRegex",-1),v=e("em",null,"NotifyBC",-1),g=e("em",null,"NotifyBC",-1),q=e("em",null,"NotifyBC",-1),y=e("em",null,"NotifyBC",-1),k=e("em",null,"NotifyBC",-1),_=e("p",null,[t("Equipped with the confirmation code and a message template, "),e("em",null,"NotifyBC"),t(" can now send out confirmation request to unconfirmed subscription channel. At a minimum this confirmation request should contain the confirmation code. When user receives the message, he/she echos the confirmation code back to a "),e("em",null,"NotifyBC"),t(" provided API to verify against saved record. If match, the state of the subscription request is changed to confirmed.")],-1),x=e("p",null,[t("For admin requests, "),e("em",null,"NotifyBC"),t(" can still perform the above confirmation process. But admin request has full CRUD privilege, including set the subscription state to confirmed, bypassing the confirmation process.")],-1),w=e("em",null,"NotifyBC",-1),C=["src"],j=e("p",null,[t("In the case user subscribing to notifications offered by different service providers in separate trust domains, the confirmation code is generated by a third-party server app trusted by all "),e("em",null,"NotifyBC"),t(" instances. Following sequence diagram shows the workflow. The diagram indicates "),e("em",null,"NotifyBC API Server 2"),t(" is chosen to send confirmation request.")],-1),I=["src"],N=n(`

    Model Schema

    The API operates on following subscription data model fields:

    NameAttributes

    serviceName

    name of the service. Avoid prefixing the name with underscore (_), or it may conflict with internal implementation.

    typestring
    requiredtrue

    channel

    name of the delivery channel. Valid values: email and sms. Notice inApp is invalid as in-app notification doesn't need subscription.

    typestring
    requiredtrue
    defaultemail

    userChannelId

    user's delivery channel id, for example, email address

    typestring
    requiredtrue

    id

    subscription id

    typestring, format depends on db
    requiredfalse
    auto-generatedtrue

    state

    state of subscription. Valid values: unconfirmed, confirmed, deleted

    typestring
    requiredfalse
    defaultunconfirmed

    userId

    user id. Auto-populated for authenticated user requests.

    typestring
    requiredfalse

    created

    date and time of creation

    typedate
    requiredfalse
    auto-generatedtrue

    updated

    date and time of last update

    typedate
    requiredfalse
    auto-generatedtrue

    confirmationRequest

    an object containing these child fields
    • confirmationCodeRegex
      • type: string
      • regular expression used to generate confirmation code
    • confirmationCodeEncrypted
      • type: string
      • encrypted confirmation code
    • sendRequest
      • type: boolean
      • whether to send confirmation request
    • from, subject, textBody, htmlBody
      • type: string
      • these are email template fields used for sending email confirmation request. If confirmationRequest.sendRequest is true and channel is email, then these fields should be supplied in order to send confirmation email.
    typeobject
    requiredtrue for user request with encrypted confirmation code; false otherwise

    broadcastPushNotificationFilter

    a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
    • simple
      province == 'BC'
    • calling jmespath's built-in functions
      contains(province,'B')
    • calling custom filter functions
      contains_ci(province,'b')
    • compound
      (contains(province,'BC') || contains_ci(province,'b')) && city == 'Victoria'
    All of above filters will match data object {"province": "BC", "city": "Victoria"}
    typestring
    requiredfalse

    data

    An object used by

    data object can only be populated by non-anonymous requests.

    typeobject
    requiredfalse

    unsubscriptionCode

    generated randomly according to RegEx config anonymousUnsubscription.code.regex during anonymous subscription if config anonymousUnsubscription.code.required is set to true

    typestring
    requiredfalse
    auto-generatedtrue

    unsubscribedAdditionalServices

    generated if parameter additionalServices is supplied in unsubscription request. Contains 2 sub-fields: ids and names, each being a list identifying the additional unsubscribed subscriptions.

    typeobject
    requiredfalse
    auto-generatedtrue

    Get Subscriptions

    GET /subscriptions
     
    `,5),T=e("li",null,[e("p",null,"permissions required, one of"),e("ul",null,[e("li",null,"super admin"),e("li",null,"admin"),e("li",null,"authenticated user")])],-1),A=e("p",null,"inputs",-1),B=n("

    a filter containing properties where, fields, order, skip, and limit

    • parameter name: filter
    • required: false
    • parameter type: query
    • data type: object

    The filter can be expressed as either

    ",3),R=e("li",null,"URL-encoded stringified JSON object (see example below); or",-1),S={href:"https://github.com/ljharb/qs",target:"_blank",rel:"noopener noreferrer"},E=e("code",null,'?filter[where][created][$gte]="2023-01-01"&filter[where][created][$lt]="2024-01-01"',-1),P=n(`

    Regardless, the filter will have to be parsed into a JSON object conforming to

    {
         "where": {...},
         "fields": ...,
    diff --git a/assets/index.html-1fa10348.js b/assets/index.html-698e3d15.js
    similarity index 96%
    rename from assets/index.html-1fa10348.js
    rename to assets/index.html-698e3d15.js
    index 30bcb4002..89dc09cbd 100644
    --- a/assets/index.html-1fa10348.js
    +++ b/assets/index.html-698e3d15.js
    @@ -1 +1 @@
    -import{_ as r,r as s,o as a,c as d,a as e,b as t,d as o,w as h}from"./app-77c416fe.js";const c={},_=e("h2",{id:"getting-help",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#getting-help","aria-hidden":"true"},"#"),t(" Getting Help")],-1),u=e("p",null,"Need help with NotifyBC? Try these resources.",-1),l={id:"documentation",tabindex:"-1"},f=e("a",{class:"header-anchor",href:"#documentation","aria-hidden":"true"},"#",-1),g=e("p",null,"Our guide to NotifyBC covering installation, writing, customization, deployment, and more.",-1),p={id:"view-source",tabindex:"-1"},m=e("a",{class:"header-anchor",href:"#view-source","aria-hidden":"true"},"#",-1),b={href:"https://github.com/bcgov/NotifyBC",target:"_blank",rel:"noopener noreferrer"},x=e("p",null,"Use the source, Luke.",-1),y={id:"google",tabindex:"-1"},w=e("a",{class:"header-anchor",href:"#google","aria-hidden":"true"},"#",-1),k={href:"https://www.google.com/?q=NotifyBC",target:"_blank",rel:"noopener noreferrer"},N=e("p",null,[t("Add "),e("strong",null,"NotifyBC"),t(" to almost any query, and you'll find just what you need.")],-1),v={id:"outstanding-issues-and-requests",tabindex:"-1"},B=e("a",{class:"header-anchor",href:"#outstanding-issues-and-requests","aria-hidden":"true"},"#",-1),C={href:"https://github.com/bcgov/NotifyBC/issues",target:"_blank",rel:"noopener noreferrer"},q=e("p",null,"Search through the issues on the main NotifyBC development. Think you've found a bug? File a new issue.",-1);function L(V,E){const i=s("RouterLink"),n=s("ExternalLinkIcon");return a(),d("div",null,[_,u,e("h3",l,[f,t(),o(i,{to:"/docs/"},{default:h(()=>[t("Documentation")]),_:1})]),g,e("h3",p,[m,t(),e("a",b,[t("View source"),o(n)])]),x,e("h3",y,[w,t(),e("a",k,[t("Google"),o(n)])]),N,e("h3",v,[B,t(),e("a",C,[t("Outstanding issues and requests"),o(n)])]),q])}const G=r(c,[["render",L],["__file","index.html.vue"]]);export{G as default};
    +import{_ as r,r as s,o as a,c as d,a as e,b as t,d as o,w as h}from"./app-be471a62.js";const c={},_=e("h2",{id:"getting-help",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#getting-help","aria-hidden":"true"},"#"),t(" Getting Help")],-1),u=e("p",null,"Need help with NotifyBC? Try these resources.",-1),l={id:"documentation",tabindex:"-1"},f=e("a",{class:"header-anchor",href:"#documentation","aria-hidden":"true"},"#",-1),g=e("p",null,"Our guide to NotifyBC covering installation, writing, customization, deployment, and more.",-1),p={id:"view-source",tabindex:"-1"},m=e("a",{class:"header-anchor",href:"#view-source","aria-hidden":"true"},"#",-1),b={href:"https://github.com/bcgov/NotifyBC",target:"_blank",rel:"noopener noreferrer"},x=e("p",null,"Use the source, Luke.",-1),y={id:"google",tabindex:"-1"},w=e("a",{class:"header-anchor",href:"#google","aria-hidden":"true"},"#",-1),k={href:"https://www.google.com/?q=NotifyBC",target:"_blank",rel:"noopener noreferrer"},N=e("p",null,[t("Add "),e("strong",null,"NotifyBC"),t(" to almost any query, and you'll find just what you need.")],-1),v={id:"outstanding-issues-and-requests",tabindex:"-1"},B=e("a",{class:"header-anchor",href:"#outstanding-issues-and-requests","aria-hidden":"true"},"#",-1),C={href:"https://github.com/bcgov/NotifyBC/issues",target:"_blank",rel:"noopener noreferrer"},q=e("p",null,"Search through the issues on the main NotifyBC development. Think you've found a bug? File a new issue.",-1);function L(V,E){const i=s("RouterLink"),n=s("ExternalLinkIcon");return a(),d("div",null,[_,u,e("h3",l,[f,t(),o(i,{to:"/docs/"},{default:h(()=>[t("Documentation")]),_:1})]),g,e("h3",p,[m,t(),e("a",b,[t("View source"),o(n)])]),x,e("h3",y,[w,t(),e("a",k,[t("Google"),o(n)])]),N,e("h3",v,[B,t(),e("a",C,[t("Outstanding issues and requests"),o(n)])]),q])}const G=r(c,[["render",L],["__file","index.html.vue"]]);export{G as default};
    diff --git a/assets/index.html-e98535e1.js b/assets/index.html-73d191ff.js
    similarity index 98%
    rename from assets/index.html-e98535e1.js
    rename to assets/index.html-73d191ff.js
    index ccd4fb42b..69cc4082d 100644
    --- a/assets/index.html-e98535e1.js
    +++ b/assets/index.html-73d191ff.js
    @@ -1,4 +1,4 @@
    -import{_ as t,r as o,o as p,c,a as n,b as s,d as i,e as a}from"./app-77c416fe.js";const l={},r=n("h1",{id:"oidc",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#oidc","aria-hidden":"true"},"#"),s(" OIDC")],-1),u=n("p",null,[n("em",null,"NotifyBC"),s(" currently can only authenticate RSA signed OIDC access token if the token is a JWT. OIDC providers such as Keycloak meet the requirement.")],-1),d=n("p",null,[s("To enable OIDC authentication strategy, add "),n("em",null,"oidc"),s(" configuration object to "),n("em",null,"/src/config.local.js"),s(". The object supports following properties")],-1),k=n("em",null,"discoveryUrl",-1),m={href:"https://openid.net/specs/openid-connect-discovery-1_0.html",target:"_blank",rel:"noopener noreferrer"},f=a("
  • clientId - OIDC client id
  • isAdmin - a predicate function to determine if authenticated user is NotifyBC administrator. The function takes the decoded OIDC access token JWT payload as input user object and should return either a boolean or a promise of boolean, i.e. the function can be both sync or async.
  • isAuthorizedUser - an optional predicate function to determine if authenticated user is an authorized NotifyBC user. If omitted, any authenticated user is authorized NotifyBC user. This function has same signature as isAdmin
  • ",3),h=a(`

    A example of complete OIDC configuration looks like

    module.exports = {
    +import{_ as t,r as o,o as p,c,a as n,b as s,d as i,e as a}from"./app-be471a62.js";const l={},r=n("h1",{id:"oidc",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#oidc","aria-hidden":"true"},"#"),s(" OIDC")],-1),u=n("p",null,[n("em",null,"NotifyBC"),s(" currently can only authenticate RSA signed OIDC access token if the token is a JWT. OIDC providers such as Keycloak meet the requirement.")],-1),d=n("p",null,[s("To enable OIDC authentication strategy, add "),n("em",null,"oidc"),s(" configuration object to "),n("em",null,"/src/config.local.js"),s(". The object supports following properties")],-1),k=n("em",null,"discoveryUrl",-1),m={href:"https://openid.net/specs/openid-connect-discovery-1_0.html",target:"_blank",rel:"noopener noreferrer"},f=a("
  • clientId - OIDC client id
  • isAdmin - a predicate function to determine if authenticated user is NotifyBC administrator. The function takes the decoded OIDC access token JWT payload as input user object and should return either a boolean or a promise of boolean, i.e. the function can be both sync or async.
  • isAuthorizedUser - an optional predicate function to determine if authenticated user is an authorized NotifyBC user. If omitted, any authenticated user is authorized NotifyBC user. This function has same signature as isAdmin
  • ",3),h=a(`

    A example of complete OIDC configuration looks like

    module.exports = {
       ...
       oidc: {
         discoveryUrl:
    diff --git a/assets/index.html-c12f943f.js b/assets/index.html-9cf75b13.js
    similarity index 63%
    rename from assets/index.html-c12f943f.js
    rename to assets/index.html-9cf75b13.js
    index 2f29833f3..f73a46a98 100644
    --- a/assets/index.html-c12f943f.js
    +++ b/assets/index.html-9cf75b13.js
    @@ -1 +1 @@
    -import{_ as e,o as _,c as t}from"./app-77c416fe.js";const n={};function c(o,r){return _(),t("div")}const l=e(n,[["render",c],["__file","index.html.vue"]]);export{l as default};
    +import{_ as e,o as _,c as t}from"./app-be471a62.js";const n={};function c(o,r){return _(),t("div")}const l=e(n,[["render",c],["__file","index.html.vue"]]);export{l as default};
    diff --git a/assets/index.html-70ee88ef.js b/assets/index.html-9fc267c0.js
    similarity index 97%
    rename from assets/index.html-70ee88ef.js
    rename to assets/index.html-9fc267c0.js
    index b8ba39f91..e345f4f6f 100644
    --- a/assets/index.html-70ee88ef.js
    +++ b/assets/index.html-9fc267c0.js
    @@ -1,4 +1,4 @@
    -import{_ as a,r as o,o as t,c as p,a as e,b as n,d as r,e as c}from"./app-77c416fe.js";const l={},i=c(`

    Database

    By default NotifyBC uses in-memory database backed up by folder /server/database/ for local and docker deployment and MongoDB for Kubernetes deployment. To use MongoDB for non-Kubernetes deployment, add file /src/datasources/db.datasource.(local|<env>).(json|js|ts) with MongoDB connection information such as following:

    module.exports = {
    +import{_ as a,r as o,o as t,c as p,a as e,b as n,d as r,e as c}from"./app-be471a62.js";const l={},i=c(`

    Database

    By default NotifyBC uses in-memory database backed up by folder /server/database/ for local and docker deployment and MongoDB for Kubernetes deployment. To use MongoDB for non-Kubernetes deployment, add file /src/datasources/db.datasource.(local|<env>).(json|js|ts) with MongoDB connection information such as following:

    module.exports = {
       uri: 'mongodb://127.0.0.1:27017/notifyBC?replicaSet=rs0',
       user: process.env.MONGODB_USER,
       pass: process.env.MONGODB_PASSWORD,
    diff --git a/assets/index.html-51b84ddd.js b/assets/index.html-a40f24b5.js
    similarity index 98%
    rename from assets/index.html-51b84ddd.js
    rename to assets/index.html-a40f24b5.js
    index e5d3941d7..d72728ff5 100644
    --- a/assets/index.html-51b84ddd.js
    +++ b/assets/index.html-a40f24b5.js
    @@ -1,4 +1,4 @@
    -import{_ as l,r as o,o as p,c,a as e,b as s,d as n,w as i,e as r}from"./app-77c416fe.js";const u={},d=e("h1",{id:"bulk-import",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#bulk-import","aria-hidden":"true"},"#"),s(" Bulk Import")],-1),m={href:"https://www.mongodb.com/docs/database-tools/mongoimport/",target:"_blank",rel:"noopener noreferrer"},h=e("em",null,"NotifyBC",-1),f=e("li",null,[s("Software installed "),e("ul",null,[e("li",null,"Node.js"),e("li",null,"Git")])],-1),b=e("em",null,"NotifyBC",-1),v={href:"https://github.com/bcgov/NotifyBC/tree/main/src/utils/bulk-import/sample-subscription.csv",target:"_blank",rel:"noopener noreferrer"},k=e("em",null,"confirmationRequest.sendRequest",-1),_=r(`

    To run the utility

    git clone https://github.com/bcgov/NotifyBC.git
    +import{_ as l,r as o,o as p,c,a as e,b as s,d as n,w as i,e as r}from"./app-be471a62.js";const u={},d=e("h1",{id:"bulk-import",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#bulk-import","aria-hidden":"true"},"#"),s(" Bulk Import")],-1),m={href:"https://www.mongodb.com/docs/database-tools/mongoimport/",target:"_blank",rel:"noopener noreferrer"},h=e("em",null,"NotifyBC",-1),f=e("li",null,[s("Software installed "),e("ul",null,[e("li",null,"Node.js"),e("li",null,"Git")])],-1),b=e("em",null,"NotifyBC",-1),v={href:"https://github.com/bcgov/NotifyBC/tree/main/src/utils/bulk-import/sample-subscription.csv",target:"_blank",rel:"noopener noreferrer"},k=e("em",null,"confirmationRequest.sendRequest",-1),_=r(`

    To run the utility

    git clone https://github.com/bcgov/NotifyBC.git
     cd NotifyBC
     npm i && npm run build
     node dist/utils/bulk-import/subscription.js -a <api-url-prefix> -c <concurrency> <csv-file-path>
    diff --git a/assets/index.html-81176e6b.js b/assets/index.html-a85cf3ae.js
    similarity index 99%
    rename from assets/index.html-81176e6b.js
    rename to assets/index.html-a85cf3ae.js
    index 5bbe3e197..5161cf7e6 100644
    --- a/assets/index.html-81176e6b.js
    +++ b/assets/index.html-a85cf3ae.js
    @@ -1 +1 @@
    -import{_ as a,r as i,o as l,c as h,a as e,b as t,d as o,w as r,e as d}from"./app-77c416fe.js";const c={},u=e("h1",{id:"what-s-new",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-s-new","aria-hidden":"true"},"#"),t(" What's New")],-1),_=e("em",null,"NotifyBC",-1),p={href:"https://semver.org/",target:"_blank",rel:"noopener noreferrer"},f=e("h2",{id:"v5",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v5","aria-hidden":"true"},"#"),t(" v5")],-1),m=e("h3",{id:"v5-1-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v5-1-0","aria-hidden":"true"},"#"),t(" v5.1.0")],-1),b={href:"https://github.com/bcgov/NotifyBC/issues/85",target:"_blank",rel:"noopener noreferrer"},v=e("li",null,"Changed package manager from yarn to npm",-1),g=e("h3",{id:"v5-0-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v5-0-0","aria-hidden":"true"},"#"),t(" v5.0.0")],-1),k=e("ul",null,[e("li",null,[t("Runs on "),e("em",null,"NestJS")]),e("li",null,"Bitnami MongoDB Helm chart is updated from version 10.7.1 to 14.3.2, with corresponding MongoDB from 4.4 to 7.0.4"),e("li",null,"Bitnami Redis Helm chart is updated from version 14.7.2 to 16.13.2, with corresponding Redis from 6.2.4 to 6.2.7")],-1),y={class:"custom-container tip"},B=e("p",{class:"custom-container-title"},"Why v5?",-1),N=e("em",null,"NotifyBC",-1),w={href:"https://loopback.io/",target:"_blank",rel:"noopener noreferrer"},x=e("em",null,"Loopback",-1),C=e("em",null,"Loopback",-1),S=d("
    1. features such as GraphQL have been in experimental state for years
    2. recent commits are mostly chores rather than enhancements
    3. core developers have ceased to contribute

    To pave the way for future growth, switching platform becomes necessary. NestJS was chosen because

    1. both NestJS and Loopback are server-side Node.js frameworks
    2. NestJS has the closest feature set as Loopback. To a large extent NestJS is a superset of Loopback
    3. NestJS incorporates more technologies
    ",3),L=e("h2",{id:"v4",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v4","aria-hidden":"true"},"#"),t(" v4")],-1),I=e("h3",{id:"v4-1-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v4-1-0","aria-hidden":"true"},"#"),t(" v4.1.0")],-1),R={href:"https://github.com/bcgov/NotifyBC/issues/50",target:"_blank",rel:"noopener noreferrer"},J=e("li",null,"applied sms throttle to all sms messages rather than just broadcast push notification.",-1),j=e("li",null,"docs updates",-1),A=e("h3",{id:"v4-0-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v4-0-0","aria-hidden":"true"},"#"),t(" v4.0.0")],-1),V={href:"https://github.com/bcgov/NotifyBC/issues/48",target:"_blank",rel:"noopener noreferrer"},D=e("li",null,"Re-ordered config file precedence",-1),E=e("li",null,"Re-organized Email and SMS configs",-1),H=e("li",null,"docs updates",-1),M=e("h2",{id:"v3",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v3","aria-hidden":"true"},"#"),t(" v3")],-1),T=e("h3",{id:"v3-1-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v3-1-0","aria-hidden":"true"},"#"),t(" v3.1.0")],-1),G={href:"https://github.com/bcgov/NotifyBC/issues/45",target:"_blank",rel:"noopener noreferrer"},U=e("li",null,"docs updates",-1),W=e("h3",{id:"v3-0-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v3-0-0","aria-hidden":"true"},"#"),t(" v3.0.0")],-1),O={href:"https://github.com/bcgov/NotifyBC/issues/36",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/bcgov/NotifyBC/issues/37",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/bcgov/NotifyBC/issues/38",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/bcgov/NotifyBC/issues/39",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/bcgov/NotifyBC/issues/40",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/bcgov/NotifyBC/issues/41",target:"_blank",rel:"noopener noreferrer"},K={href:"https://github.com/bcgov/NotifyBC/issues/42",target:"_blank",rel:"noopener noreferrer"},X=e("li",null,"docs updates",-1),Y=e("h2",{id:"v2",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2","aria-hidden":"true"},"#"),t(" v2")],-1),Z=e("h3",{id:"v2-9-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-9-0","aria-hidden":"true"},"#"),t(" v2.9.0")],-1),$={href:"https://github.com/bcgov/NotifyBC/issues/34",target:"_blank",rel:"noopener noreferrer"},ee=e("li",null,"docs updates",-1),te=e("h3",{id:"v2-8-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-8-0","aria-hidden":"true"},"#"),t(" v2.8.0")],-1),oe={href:"https://github.com/bcgov/NotifyBC/issues/28",target:"_blank",rel:"noopener noreferrer"},se={href:"https://github.com/bcgov/NotifyBC/issues/32",target:"_blank",rel:"noopener noreferrer"},ne=e("li",null,"docs updates",-1),re=e("h3",{id:"v2-7-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-7-0","aria-hidden":"true"},"#"),t(" v2.7.0")],-1),ie={href:"https://github.com/bcgov/NotifyBC/issues/26",target:"_blank",rel:"noopener noreferrer"},ae=e("li",null,"docs updates",-1),le=e("h3",{id:"v2-6-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-6-0","aria-hidden":"true"},"#"),t(" v2.6.0")],-1),he=e("ul",null,[e("li",null,"Helm chart updates"),e("li",null,"docs updates")],-1),de=e("h3",{id:"v2-5-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-5-0","aria-hidden":"true"},"#"),t(" v2.5.0")],-1),ce={href:"https://github.com/bcgov/NotifyBC/tree/main/helm",target:"_blank",rel:"noopener noreferrer"},ue=e("li",null,"docs updates",-1),_e=e("h3",{id:"v2-4-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-4-0","aria-hidden":"true"},"#"),t(" v2.4.0")],-1),pe={href:"https://github.com/bcgov/NotifyBC/issues/16",target:"_blank",rel:"noopener noreferrer"},fe=e("li",null,"misc web console adjustments",-1),me=e("li",null,"docs updates",-1),be=e("h3",{id:"v2-3-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-3-0","aria-hidden":"true"},"#"),t(" v2.3.0")],-1),ve={href:"https://github.com/bcgov/NotifyBC/issues/15",target:"_blank",rel:"noopener noreferrer"},ge=e("li",null,"misc web console adjustments",-1),ke=e("li",null,"docs updates",-1),ye=e("h3",{id:"v2-2-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-2-0","aria-hidden":"true"},"#"),t(" v2.2.0")],-1),Be={href:"https://github.com/bcgov/NotifyBC/issues/14",target:"_blank",rel:"noopener noreferrer"},Ne=e("li",null,"misc web console adjustments",-1),we=e("li",null,"docs updates",-1),xe=e("h3",{id:"v2-1-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-1-0","aria-hidden":"true"},"#"),t(" v2.1.0")],-1),Ce={href:"https://github.com/bcgov/NotifyBC/issues/13",target:"_blank",rel:"noopener noreferrer"},Se=e("li",null,"misc web console adjustments",-1),Le=e("li",null,"docs updates",-1),Ie=e("h3",{id:"v2-0-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-0-0","aria-hidden":"true"},"#"),t(" v2.0.0")],-1),Re=e("li",null,"Runs on LoopBack v4",-1),Je=e("li",null,"All code is converted to TypeScript",-1),je={href:"https://swagger.io/specification/",target:"_blank",rel:"noopener noreferrer"},Ae=e("li",null,"Docs is converted from Jekyll to VuePress",-1),Ve={class:"custom-container tip"},De=e("p",{class:"custom-container-title"},"Why v2?",-1),Ee=e("em",null,"NotifyBC",-1),He={href:"https://loopback.io/",target:"_blank",rel:"noopener noreferrer"},Me=e("em",null,"NotifyBC",-1);function Te(Ge,Ue){const s=i("ExternalLinkIcon"),n=i("RouterLink");return l(),h("div",null,[u,e("p",null,[_,t(" uses "),e("a",p,[t("semantic versioning"),o(s)]),t(".")]),f,m,e("ul",null,[e("li",null,[t("Issue "),e("a",b,[t("#85"),o(s)]),t(": added health check")]),v]),g,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v4-to-v5"},{default:r(()=>[t("Upgrade Guide")]),_:1}),t(" for more information.")]),k,e("div",y,[B,e("p",null,[N,t(" was built on "),e("a",w,[t("LoopBack"),o(s)]),t(" since the beginning. While "),x,t(" is an awesome framework at the time, it is evident by 2022 "),C,t(" is no longer actively maintained")]),S]),L,I,e("ul",null,[e("li",null,[t("Issue "),e("a",R,[t("#50"),o(s)]),t(": Email message throttle")]),J,j]),A,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v3-to-v4"},{default:r(()=>[t("v3 to v4 upgrade guide")]),_:1}),t(" for more information.")]),e("ul",null,[e("li",null,[t("Issue "),e("a",V,[t("#48"),o(s)]),t(": SMS message throttle")]),D,E,H]),M,T,e("ul",null,[e("li",null,[t("Issue "),e("a",G,[t("#45"),o(s)]),t(": Reliability - Log skipped dispatches for broadcast push notifications")]),U]),W,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v2-to-v3"},{default:r(()=>[t("v2 to v3 upgrade guide")]),_:1}),t(" for more information.")]),e("ul",null,[e("li",null,[t("Reliability improvements - issues "),e("a",O,[t("#36"),o(s)]),t(","),e("a",z,[t("#37"),o(s)]),t(","),e("a",P,[t("#38"),o(s)]),t(","),e("a",Q,[t("#39"),o(s)]),t(","),e("a",q,[t("#40"),o(s)]),t(","),e("a",F,[t("#41"),o(s)]),t(","),e("a",K,[t("#42"),o(s)])]),X]),Y,Z,e("ul",null,[e("li",null,[t("Issue "),e("a",$,[t("#34"),o(s)]),t(": Helm - add k8s cronJob to backup MongoDB")]),ee]),te,e("ul",null,[e("li",null,[t("Issue "),e("a",oe,[t("#28"),o(s)]),t(": Allow subscription data be used by mail merge dynamic tokens")]),e("li",null,[t("Issue "),e("a",se,[t("#32"),o(s)]),t(": Allow escape mail merge delimiter")]),ne]),re,e("ul",null,[e("li",null,[t("Issue "),e("a",ie,[t("#26"),o(s)]),t(": Allow filter specified in a notification")]),ae]),le,he,de,e("ul",null,[e("li",null,[t("added "),e("a",ce,[t("helm chart"),o(s)]),t(". See "),o(n,{to:"/docs/miscellaneous/upgrade.html#openshift-template-to-helm"},{default:r(()=>[t("OpenShift template to Helm upgrade guide")]),_:1})]),ue]),_e,e("ul",null,[e("li",null,[t("Issue "),e("a",pe,[t("#16"),o(s)]),t(": Support client certificate authentication")]),fe,me]),be,e("ul",null,[e("li",null,[t("Issue "),e("a",ve,[t("#15"),o(s)]),t(": Support OIDC authentication for both admin and non-admin user")]),ge,ke]),ye,e("ul",null,[e("li",null,[t("Issue "),e("a",Be,[t("#14"),o(s)]),t(": Support Administrator login, changing password, obtain access token in web console")]),Ne,we]),xe,e("ul",null,[e("li",null,[t("Issue "),e("a",Ce,[t("#13"),o(s)]),t(": Upgraded Vuetify from v0.16.9 to v2.4.3")]),Se,Le]),Ie,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v1-to-v2"},{default:r(()=>[t("Upgrade Guide")]),_:1}),t(" for more information.")]),e("ul",null,[Re,Je,e("li",null,[t("Upgraded "),e("a",je,[t("OAS"),o(s)]),t(" from v2 to v3")]),Ae]),e("div",Ve,[De,e("p",null,[Ee,t(" has been built on Node.js "),e("a",He,[t("LoopBack"),o(s)]),t(" framework since 2016. LoopBack v4, which was released in 2019, is backward incompatible. To keep software stack up-to-date, unless rewriting from scratch, it is necessary to port "),Me,t(" to LoopBack v4. Great care has been taken to minimize upgrade effort.")])])])}const Oe=a(c,[["render",Te],["__file","index.html.vue"]]);export{Oe as default}; +import{_ as a,r as i,o as l,c as h,a as e,b as t,d as o,w as r,e as d}from"./app-be471a62.js";const c={},u=e("h1",{id:"what-s-new",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#what-s-new","aria-hidden":"true"},"#"),t(" What's New")],-1),_=e("em",null,"NotifyBC",-1),p={href:"https://semver.org/",target:"_blank",rel:"noopener noreferrer"},f=e("h2",{id:"v5",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v5","aria-hidden":"true"},"#"),t(" v5")],-1),m=e("h3",{id:"v5-1-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v5-1-0","aria-hidden":"true"},"#"),t(" v5.1.0")],-1),b={href:"https://github.com/bcgov/NotifyBC/issues/85",target:"_blank",rel:"noopener noreferrer"},v=e("li",null,"Changed package manager from yarn to npm",-1),g=e("h3",{id:"v5-0-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v5-0-0","aria-hidden":"true"},"#"),t(" v5.0.0")],-1),k=e("ul",null,[e("li",null,[t("Runs on "),e("em",null,"NestJS")]),e("li",null,"Bitnami MongoDB Helm chart is updated from version 10.7.1 to 14.3.2, with corresponding MongoDB from 4.4 to 7.0.4"),e("li",null,"Bitnami Redis Helm chart is updated from version 14.7.2 to 16.13.2, with corresponding Redis from 6.2.4 to 6.2.7")],-1),y={class:"custom-container tip"},B=e("p",{class:"custom-container-title"},"Why v5?",-1),N=e("em",null,"NotifyBC",-1),w={href:"https://loopback.io/",target:"_blank",rel:"noopener noreferrer"},x=e("em",null,"Loopback",-1),C=e("em",null,"Loopback",-1),S=d("
    1. features such as GraphQL have been in experimental state for years
    2. recent commits are mostly chores rather than enhancements
    3. core developers have ceased to contribute

    To pave the way for future growth, switching platform becomes necessary. NestJS was chosen because

    1. both NestJS and Loopback are server-side Node.js frameworks
    2. NestJS has the closest feature set as Loopback. To a large extent NestJS is a superset of Loopback
    3. NestJS incorporates more technologies
    ",3),L=e("h2",{id:"v4",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v4","aria-hidden":"true"},"#"),t(" v4")],-1),I=e("h3",{id:"v4-1-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v4-1-0","aria-hidden":"true"},"#"),t(" v4.1.0")],-1),R={href:"https://github.com/bcgov/NotifyBC/issues/50",target:"_blank",rel:"noopener noreferrer"},J=e("li",null,"applied sms throttle to all sms messages rather than just broadcast push notification.",-1),j=e("li",null,"docs updates",-1),A=e("h3",{id:"v4-0-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v4-0-0","aria-hidden":"true"},"#"),t(" v4.0.0")],-1),V={href:"https://github.com/bcgov/NotifyBC/issues/48",target:"_blank",rel:"noopener noreferrer"},D=e("li",null,"Re-ordered config file precedence",-1),E=e("li",null,"Re-organized Email and SMS configs",-1),H=e("li",null,"docs updates",-1),M=e("h2",{id:"v3",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v3","aria-hidden":"true"},"#"),t(" v3")],-1),T=e("h3",{id:"v3-1-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v3-1-0","aria-hidden":"true"},"#"),t(" v3.1.0")],-1),G={href:"https://github.com/bcgov/NotifyBC/issues/45",target:"_blank",rel:"noopener noreferrer"},U=e("li",null,"docs updates",-1),W=e("h3",{id:"v3-0-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v3-0-0","aria-hidden":"true"},"#"),t(" v3.0.0")],-1),O={href:"https://github.com/bcgov/NotifyBC/issues/36",target:"_blank",rel:"noopener noreferrer"},z={href:"https://github.com/bcgov/NotifyBC/issues/37",target:"_blank",rel:"noopener noreferrer"},P={href:"https://github.com/bcgov/NotifyBC/issues/38",target:"_blank",rel:"noopener noreferrer"},Q={href:"https://github.com/bcgov/NotifyBC/issues/39",target:"_blank",rel:"noopener noreferrer"},q={href:"https://github.com/bcgov/NotifyBC/issues/40",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/bcgov/NotifyBC/issues/41",target:"_blank",rel:"noopener noreferrer"},K={href:"https://github.com/bcgov/NotifyBC/issues/42",target:"_blank",rel:"noopener noreferrer"},X=e("li",null,"docs updates",-1),Y=e("h2",{id:"v2",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2","aria-hidden":"true"},"#"),t(" v2")],-1),Z=e("h3",{id:"v2-9-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-9-0","aria-hidden":"true"},"#"),t(" v2.9.0")],-1),$={href:"https://github.com/bcgov/NotifyBC/issues/34",target:"_blank",rel:"noopener noreferrer"},ee=e("li",null,"docs updates",-1),te=e("h3",{id:"v2-8-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-8-0","aria-hidden":"true"},"#"),t(" v2.8.0")],-1),oe={href:"https://github.com/bcgov/NotifyBC/issues/28",target:"_blank",rel:"noopener noreferrer"},se={href:"https://github.com/bcgov/NotifyBC/issues/32",target:"_blank",rel:"noopener noreferrer"},ne=e("li",null,"docs updates",-1),re=e("h3",{id:"v2-7-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-7-0","aria-hidden":"true"},"#"),t(" v2.7.0")],-1),ie={href:"https://github.com/bcgov/NotifyBC/issues/26",target:"_blank",rel:"noopener noreferrer"},ae=e("li",null,"docs updates",-1),le=e("h3",{id:"v2-6-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-6-0","aria-hidden":"true"},"#"),t(" v2.6.0")],-1),he=e("ul",null,[e("li",null,"Helm chart updates"),e("li",null,"docs updates")],-1),de=e("h3",{id:"v2-5-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-5-0","aria-hidden":"true"},"#"),t(" v2.5.0")],-1),ce={href:"https://github.com/bcgov/NotifyBC/tree/main/helm",target:"_blank",rel:"noopener noreferrer"},ue=e("li",null,"docs updates",-1),_e=e("h3",{id:"v2-4-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-4-0","aria-hidden":"true"},"#"),t(" v2.4.0")],-1),pe={href:"https://github.com/bcgov/NotifyBC/issues/16",target:"_blank",rel:"noopener noreferrer"},fe=e("li",null,"misc web console adjustments",-1),me=e("li",null,"docs updates",-1),be=e("h3",{id:"v2-3-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-3-0","aria-hidden":"true"},"#"),t(" v2.3.0")],-1),ve={href:"https://github.com/bcgov/NotifyBC/issues/15",target:"_blank",rel:"noopener noreferrer"},ge=e("li",null,"misc web console adjustments",-1),ke=e("li",null,"docs updates",-1),ye=e("h3",{id:"v2-2-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-2-0","aria-hidden":"true"},"#"),t(" v2.2.0")],-1),Be={href:"https://github.com/bcgov/NotifyBC/issues/14",target:"_blank",rel:"noopener noreferrer"},Ne=e("li",null,"misc web console adjustments",-1),we=e("li",null,"docs updates",-1),xe=e("h3",{id:"v2-1-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-1-0","aria-hidden":"true"},"#"),t(" v2.1.0")],-1),Ce={href:"https://github.com/bcgov/NotifyBC/issues/13",target:"_blank",rel:"noopener noreferrer"},Se=e("li",null,"misc web console adjustments",-1),Le=e("li",null,"docs updates",-1),Ie=e("h3",{id:"v2-0-0",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2-0-0","aria-hidden":"true"},"#"),t(" v2.0.0")],-1),Re=e("li",null,"Runs on LoopBack v4",-1),Je=e("li",null,"All code is converted to TypeScript",-1),je={href:"https://swagger.io/specification/",target:"_blank",rel:"noopener noreferrer"},Ae=e("li",null,"Docs is converted from Jekyll to VuePress",-1),Ve={class:"custom-container tip"},De=e("p",{class:"custom-container-title"},"Why v2?",-1),Ee=e("em",null,"NotifyBC",-1),He={href:"https://loopback.io/",target:"_blank",rel:"noopener noreferrer"},Me=e("em",null,"NotifyBC",-1);function Te(Ge,Ue){const s=i("ExternalLinkIcon"),n=i("RouterLink");return l(),h("div",null,[u,e("p",null,[_,t(" uses "),e("a",p,[t("semantic versioning"),o(s)]),t(".")]),f,m,e("ul",null,[e("li",null,[t("Issue "),e("a",b,[t("#85"),o(s)]),t(": added health check")]),v]),g,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v4-to-v5"},{default:r(()=>[t("Upgrade Guide")]),_:1}),t(" for more information.")]),k,e("div",y,[B,e("p",null,[N,t(" was built on "),e("a",w,[t("LoopBack"),o(s)]),t(" since the beginning. While "),x,t(" is an awesome framework at the time, it is evident by 2022 "),C,t(" is no longer actively maintained")]),S]),L,I,e("ul",null,[e("li",null,[t("Issue "),e("a",R,[t("#50"),o(s)]),t(": Email message throttle")]),J,j]),A,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v3-to-v4"},{default:r(()=>[t("v3 to v4 upgrade guide")]),_:1}),t(" for more information.")]),e("ul",null,[e("li",null,[t("Issue "),e("a",V,[t("#48"),o(s)]),t(": SMS message throttle")]),D,E,H]),M,T,e("ul",null,[e("li",null,[t("Issue "),e("a",G,[t("#45"),o(s)]),t(": Reliability - Log skipped dispatches for broadcast push notifications")]),U]),W,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v2-to-v3"},{default:r(()=>[t("v2 to v3 upgrade guide")]),_:1}),t(" for more information.")]),e("ul",null,[e("li",null,[t("Reliability improvements - issues "),e("a",O,[t("#36"),o(s)]),t(","),e("a",z,[t("#37"),o(s)]),t(","),e("a",P,[t("#38"),o(s)]),t(","),e("a",Q,[t("#39"),o(s)]),t(","),e("a",q,[t("#40"),o(s)]),t(","),e("a",F,[t("#41"),o(s)]),t(","),e("a",K,[t("#42"),o(s)])]),X]),Y,Z,e("ul",null,[e("li",null,[t("Issue "),e("a",$,[t("#34"),o(s)]),t(": Helm - add k8s cronJob to backup MongoDB")]),ee]),te,e("ul",null,[e("li",null,[t("Issue "),e("a",oe,[t("#28"),o(s)]),t(": Allow subscription data be used by mail merge dynamic tokens")]),e("li",null,[t("Issue "),e("a",se,[t("#32"),o(s)]),t(": Allow escape mail merge delimiter")]),ne]),re,e("ul",null,[e("li",null,[t("Issue "),e("a",ie,[t("#26"),o(s)]),t(": Allow filter specified in a notification")]),ae]),le,he,de,e("ul",null,[e("li",null,[t("added "),e("a",ce,[t("helm chart"),o(s)]),t(". See "),o(n,{to:"/docs/miscellaneous/upgrade.html#openshift-template-to-helm"},{default:r(()=>[t("OpenShift template to Helm upgrade guide")]),_:1})]),ue]),_e,e("ul",null,[e("li",null,[t("Issue "),e("a",pe,[t("#16"),o(s)]),t(": Support client certificate authentication")]),fe,me]),be,e("ul",null,[e("li",null,[t("Issue "),e("a",ve,[t("#15"),o(s)]),t(": Support OIDC authentication for both admin and non-admin user")]),ge,ke]),ye,e("ul",null,[e("li",null,[t("Issue "),e("a",Be,[t("#14"),o(s)]),t(": Support Administrator login, changing password, obtain access token in web console")]),Ne,we]),xe,e("ul",null,[e("li",null,[t("Issue "),e("a",Ce,[t("#13"),o(s)]),t(": Upgraded Vuetify from v0.16.9 to v2.4.3")]),Se,Le]),Ie,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v1-to-v2"},{default:r(()=>[t("Upgrade Guide")]),_:1}),t(" for more information.")]),e("ul",null,[Re,Je,e("li",null,[t("Upgraded "),e("a",je,[t("OAS"),o(s)]),t(" from v2 to v3")]),Ae]),e("div",Ve,[De,e("p",null,[Ee,t(" has been built on Node.js "),e("a",He,[t("LoopBack"),o(s)]),t(" framework since 2016. LoopBack v4, which was released in 2019, is backward incompatible. To keep software stack up-to-date, unless rewriting from scratch, it is necessary to port "),Me,t(" to LoopBack v4. Great care has been taken to minimize upgrade effort.")])])])}const Oe=a(c,[["render",Te],["__file","index.html.vue"]]);export{Oe as default}; diff --git a/assets/index.html-04c7721d.js b/assets/index.html-aaab02f3.js similarity index 99% rename from assets/index.html-04c7721d.js rename to assets/index.html-aaab02f3.js index 684330193..33ba76874 100644 --- a/assets/index.html-04c7721d.js +++ b/assets/index.html-aaab02f3.js @@ -1,4 +1,4 @@ -import{_ as l,r as s,o as r,c as d,a as t,b as e,d as o,w as a,e as i}from"./app-77c416fe.js";const u={},h=t("h1",{id:"web-console",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#web-console","aria-hidden":"true"},"#"),e(" Web Console")],-1),p=t("a",{href:"../installation"},"installing",-1),f=t("em",null,"NotifyBC",-1),m=t("em",null,"NotifyBC",-1),b={href:"http://localhost:3000",target:"_blank",rel:"noopener noreferrer"},_=t("p",null,"What you see in web console and what you get from API calls depend on how your requests are authenticated.",-1),y=t("h2",{id:"ip-whitelisting-authentication",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#ip-whitelisting-authentication","aria-hidden":"true"},"#"),e(" Ip whitelisting authentication")],-1),g=t("span",{class:"material-icons"},"verified_user",-1),w=t("p",null,"To see the result of non super-admin requests, you can choose one of the following methods",-1),v=t("ul",null,[t("li",null,"customize admin ip list to omit localhost (127.0.0.1)"),t("li",null,"access web console from another ip not in the admin ip list")],-1),I=t("h2",{id:"client-certificate-authentication",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#client-certificate-authentication","aria-hidden":"true"},"#"),e(" Client certificate authentication")],-1),x=t("em",null,"NotifyBC",-1),A=t("span",{class:"material-icons"},"verified",-1),k=i('

    Anonymous

    If you access web console from a client that is not in the admin ip list, you are by default anonymous user. Anonymous authentication status is indicated by the LOGINlogin button on top right corner of web console. Click the button to login.

    Access token authentication

    ',3),P=t("em",null,"Access Token",-1),C=t("em",null,"Access Token",-1),O=t("span",{class:"material-icons"},"login",-1),S=i('

    Tokens are not shared between API Explorer and web console

    Despite API Explorer appears to be part of web console, it is a separate application. At this point neither the access token nor the OIDC access token are shared between the two applications. You have to use API Explorer's Authorize button to authenticate even if you have logged into web console.

    OIDC authentication

    If you have configured OIDC, then the login button will direct you to OIDC provider's login page. Once login successfully, you will be redirected back to NoitfyBC web console. OIDC authentication status is indicated by the LOGOUTlogout button.

    ',3),q=i(`

    SiteMinder authentication

    To get results of a SiteMinder authenticated user, do one of the following

    • access the API via a SiteMinder proxy if you have configured SiteMinder properly
    • use a tool such as curl that allows to specify custom headers, and supply SiteMinder header SM_USER:
    curl -X GET --header "Accept: application/json" \\
    +import{_ as l,r as s,o as r,c as d,a as t,b as e,d as o,w as a,e as i}from"./app-be471a62.js";const u={},h=t("h1",{id:"web-console",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#web-console","aria-hidden":"true"},"#"),e(" Web Console")],-1),p=t("a",{href:"../installation"},"installing",-1),f=t("em",null,"NotifyBC",-1),m=t("em",null,"NotifyBC",-1),b={href:"http://localhost:3000",target:"_blank",rel:"noopener noreferrer"},_=t("p",null,"What you see in web console and what you get from API calls depend on how your requests are authenticated.",-1),y=t("h2",{id:"ip-whitelisting-authentication",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#ip-whitelisting-authentication","aria-hidden":"true"},"#"),e(" Ip whitelisting authentication")],-1),g=t("span",{class:"material-icons"},"verified_user",-1),w=t("p",null,"To see the result of non super-admin requests, you can choose one of the following methods",-1),v=t("ul",null,[t("li",null,"customize admin ip list to omit localhost (127.0.0.1)"),t("li",null,"access web console from another ip not in the admin ip list")],-1),I=t("h2",{id:"client-certificate-authentication",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#client-certificate-authentication","aria-hidden":"true"},"#"),e(" Client certificate authentication")],-1),x=t("em",null,"NotifyBC",-1),A=t("span",{class:"material-icons"},"verified",-1),k=i('

    Anonymous

    If you access web console from a client that is not in the admin ip list, you are by default anonymous user. Anonymous authentication status is indicated by the LOGINlogin button on top right corner of web console. Click the button to login.

    Access token authentication

    ',3),P=t("em",null,"Access Token",-1),C=t("em",null,"Access Token",-1),O=t("span",{class:"material-icons"},"login",-1),S=i('

    Tokens are not shared between API Explorer and web console

    Despite API Explorer appears to be part of web console, it is a separate application. At this point neither the access token nor the OIDC access token are shared between the two applications. You have to use API Explorer's Authorize button to authenticate even if you have logged into web console.

    OIDC authentication

    If you have configured OIDC, then the login button will direct you to OIDC provider's login page. Once login successfully, you will be redirected back to NoitfyBC web console. OIDC authentication status is indicated by the LOGOUTlogout button.

    ',3),q=i(`

    SiteMinder authentication

    To get results of a SiteMinder authenticated user, do one of the following

    • access the API via a SiteMinder proxy if you have configured SiteMinder properly
    • use a tool such as curl that allows to specify custom headers, and supply SiteMinder header SM_USER:
    curl -X GET --header "Accept: application/json" \\
         --header "SM_USER: foo" \\
         "http://localhost:3000/api/notifications"
     
    `,4);function N(T,E){const c=s("ExternalLinkIcon"),n=s("RouterLink");return r(),d("div",null,[h,t("p",null,[e("After "),p,e(),f,e(", you can start exploring "),m,e(" resources by opening web console, a curated GUI, at "),t("a",b,[e("http://localhost:3000"),o(c)]),e(". You can further explore full-blown APIs by clicking the API explorer Swagger UI embedded in web console.")]),t("p",null,[e("Consult the "),o(n,{to:"/docs/api-overview/"},{default:a(()=>[e("API docs")]),_:1}),e(" for valid inputs and expected outcome while you are exploring the APIs. Once you are familiar with the APIs, you can start writing code to call the APIs from either user browser or from a server application.")]),_,y,t("p",null,[e("The API calls you made with API explorer as well as API calls made by web console from localhost are by default authenticated as "),o(n,{to:"/docs/overview/#architecture"},{default:a(()=>[e("super-admin requests")]),_:1}),e(" because localhost is in "),o(n,{to:"/docs/config-adminIpList/"},{default:a(()=>[e("admin ip list")]),_:1}),e(" by default. Ip whitelisting authentication status is indicated by the "),g,e(" icon on top right corner of web console.")]),w,v,I,t("p",null,[e("If your ip is not in the admin ip list but you have setup a client certificate issued by "),x,e(" server in browser, the API calls you made with API explorer as well as API calls made by web console are also authenticated as "),o(n,{to:"/docs/overview/#architecture"},{default:a(()=>[e("super-admin requests")]),_:1}),e(". Client certificate authentication status is indicated by the "),A,e(" icon on top right corner of web console.")]),k,t("p",null,[e("If you have not configured "),o(n,{to:"/docs/config/oidc.html"},{default:a(()=>[e("OIDC")]),_:1}),e(", the login button opens a login form. After successful login, the login button is replaced with the "),P,e(" text field on top right corner of web console. You can edit the text field. If the new access token you entered is invalid, you are essentially logging yourself out. In such case "),C,e(" text field is replaced by the LOGIN"),O,e(" button.")]),t("p",null,[e("The procedure to create an admin login account is documented in "),o(n,{to:"/docs/api/administrator.html"},{default:a(()=>[e("Administrator API")]),_:1})]),S,t("p",null,[e("If you passed "),o(n,{to:"/docs/config/oidc.html"},{default:a(()=>[e("isAdmin")]),_:1}),e(" validation, you are admin; otherwise you are authenticated user.")]),q])}const B=l(u,[["render",N],["__file","index.html.vue"]]);export{B as default}; diff --git a/assets/index.html-321d8b52.js b/assets/index.html-aad2a358.js similarity index 98% rename from assets/index.html-321d8b52.js rename to assets/index.html-aad2a358.js index 36059a6c7..a6c8161bb 100644 --- a/assets/index.html-321d8b52.js +++ b/assets/index.html-aad2a358.js @@ -1,4 +1,4 @@ -import{_ as n,o as s,c as a,e as t}from"./app-77c416fe.js";const o={},e=t(`

    Health Check

    Health status of NotifyBC can be obtained by querying /health API end point. For example

    $ curl -s http://localhost:3000/api/health | jq
    +import{_ as n,o as s,c as a,e as t}from"./app-be471a62.js";const o={},e=t(`

    Health Check

    Health status of NotifyBC can be obtained by querying /health API end point. For example

    $ curl -s http://localhost:3000/api/health | jq
     {
       "status": "ok",
       "info": {
    diff --git a/assets/index.html-95e23621.js b/assets/index.html-b9f3ea8d.js
    similarity index 99%
    rename from assets/index.html-95e23621.js
    rename to assets/index.html-b9f3ea8d.js
    index 8ceaaae9f..9488e961c 100644
    --- a/assets/index.html-95e23621.js
    +++ b/assets/index.html-b9f3ea8d.js
    @@ -1,4 +1,4 @@
    -import{_ as c,r as t,o as l,c as p,a as e,d as s,w as i,b as a,e as r}from"./app-77c416fe.js";const m={},d=r(`

    TLS Certificates

    NotifyBC supports HTTPS TLS to achieve end-to-end encryption. In addition, both server and client can be authenticated using certificates.

    To enable HTTPS for server authentication only, you need to create two files

    • server/certs/key.pem - a PEM encoded private key
    • server/certs/cert.pem - a PEM encoded X.509 certificate chain

    Use ConfigMaps on Kubernetes

    Create key.pem and cert.pem as items in ConfigMap notify-bc, then mount the items under /home/node/app/server/certs similar to how config.local.js and middleware.local.js are implemented.

    For self-signed certificate, run

    openssl req -x509 -newkey rsa:4096 -keyout server/certs/key.pem -out server/certs/cert.pem -nodes -days 365 -subj "/CN=NotifyBC"
    +import{_ as c,r as t,o as l,c as p,a as e,d as s,w as i,b as a,e as r}from"./app-be471a62.js";const m={},d=r(`

    TLS Certificates

    NotifyBC supports HTTPS TLS to achieve end-to-end encryption. In addition, both server and client can be authenticated using certificates.

    To enable HTTPS for server authentication only, you need to create two files

    • server/certs/key.pem - a PEM encoded private key
    • server/certs/cert.pem - a PEM encoded X.509 certificate chain

    Use ConfigMaps on Kubernetes

    Create key.pem and cert.pem as items in ConfigMap notify-bc, then mount the items under /home/node/app/server/certs similar to how config.local.js and middleware.local.js are implemented.

    For self-signed certificate, run

    openssl req -x509 -newkey rsa:4096 -keyout server/certs/key.pem -out server/certs/cert.pem -nodes -days 365 -subj "/CN=NotifyBC"
     

    to generate both files in one shot.

    Caution about self-signed cert

    Self-signed cert is intended to be used in non-production environments only to authenticate server. In such environments to allow NotifyBC connecting to itself, environment variable NODE_TLS_REJECT_UNAUTHORIZED must be set to 0.

    To create a CSR from the private key generated above, run

    openssl req -new -key server/certs/key.pem -out server/certs/csr.pem
     

    Then bring your CSR to your CA to sign. Replace server/certs/cert.pem with the cert signed by CA. If your CA also supplied intermediate certificate in PEM encoded format, say in a file called intermediate.pem, append all of the content of intermediate.pem to file server/certs/cert.pem.

    Make a copy of self-signed server/certs/cert.pem

    If you want to enable client certificate authentication documented below, make sure to copy self-signed server/certs/cert.pem to server/certs/ca.pem before replacing the file with the cert signed by CA. You need the self-signed server/certs/cert.pem to sign client CSR.

    In case you created server/certs/key.pem and server/certs/cert.pem but don't want to enable HTTPS, create following config in src/config.local.js

    module.exports = {
       tls: {
    diff --git a/assets/index.html-13cd24c1.js b/assets/index.html-bf54ea9c.js
    similarity index 96%
    rename from assets/index.html-13cd24c1.js
    rename to assets/index.html-bf54ea9c.js
    index 498191856..2a32cb57a 100644
    --- a/assets/index.html-13cd24c1.js
    +++ b/assets/index.html-bf54ea9c.js
    @@ -1 +1 @@
    -import{_ as n,r as l,o as s,c as a,a as e,b as r,d as o}from"./app-77c416fe.js";const i={},_=e("h1",{id:"acknowledgments",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#acknowledgments","aria-hidden":"true"},"#"),r(" Acknowledgments")],-1),c=e("p",null,[e("em",null,"NotifyBC"),r(" is built on a myriad of open source software. At runtime it also depends on a few services. Credit goes to their contributors. Notably")],-1),h={href:"https://nodejs.org/",target:"_blank",rel:"noopener noreferrer"},d={href:"https://nestjs.com/",target:"_blank",rel:"noopener noreferrer"},f={href:"https://www.mongodb.com/",target:"_blank",rel:"noopener noreferrer"},p={href:"https://nodemailer.com/",target:"_blank",rel:"noopener noreferrer"},u={href:"https://jmespath.org/",target:"_blank",rel:"noopener noreferrer"},m={href:"https://vuejs.org/",target:"_blank",rel:"noopener noreferrer"},g={href:"https://vuetifyjs.com/",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/json-editor/json-editor",target:"_blank",rel:"noopener noreferrer"},w={href:"https://vuepress.vuejs.org/",target:"_blank",rel:"noopener noreferrer"},k={href:"https://www.twilio.com/",target:"_blank",rel:"noopener noreferrer"},j={href:"https://www.swiftsmsgateway.com/",target:"_blank",rel:"noopener noreferrer"},x={href:"https://bitnami.com/",target:"_blank",rel:"noopener noreferrer"};function N(v,y){const t=l("ExternalLinkIcon");return s(),a("div",null,[_,c,e("ul",null,[e("li",null,[e("a",h,[r("Node.js"),o(t)])]),e("li",null,[e("a",d,[r("NestJS"),o(t)])]),e("li",null,[e("a",f,[r("MongoDB"),o(t)])]),e("li",null,[e("a",p,[r("NodeMailer"),o(t)])]),e("li",null,[e("a",u,[r("JMESPath"),o(t)])]),e("li",null,[e("a",m,[r("Vue"),o(t)])]),e("li",null,[e("a",g,[r("Vuetify"),o(t)])]),e("li",null,[e("a",b,[r("JSON Editor"),o(t)])]),e("li",null,[e("a",w,[r("VuePress"),o(t)])]),e("li",null,[e("a",k,[r("Twilio"),o(t)])]),e("li",null,[e("a",j,[r("Swift SMS Gateway"),o(t)])]),e("li",null,[e("a",x,[r("Bitnami"),o(t)])])])])}const S=n(i,[["render",N],["__file","index.html.vue"]]);export{S as default};
    +import{_ as n,r as l,o as s,c as a,a as e,b as r,d as o}from"./app-be471a62.js";const i={},_=e("h1",{id:"acknowledgments",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#acknowledgments","aria-hidden":"true"},"#"),r(" Acknowledgments")],-1),c=e("p",null,[e("em",null,"NotifyBC"),r(" is built on a myriad of open source software. At runtime it also depends on a few services. Credit goes to their contributors. Notably")],-1),h={href:"https://nodejs.org/",target:"_blank",rel:"noopener noreferrer"},d={href:"https://nestjs.com/",target:"_blank",rel:"noopener noreferrer"},f={href:"https://www.mongodb.com/",target:"_blank",rel:"noopener noreferrer"},p={href:"https://nodemailer.com/",target:"_blank",rel:"noopener noreferrer"},u={href:"https://jmespath.org/",target:"_blank",rel:"noopener noreferrer"},m={href:"https://vuejs.org/",target:"_blank",rel:"noopener noreferrer"},g={href:"https://vuetifyjs.com/",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/json-editor/json-editor",target:"_blank",rel:"noopener noreferrer"},w={href:"https://vuepress.vuejs.org/",target:"_blank",rel:"noopener noreferrer"},k={href:"https://www.twilio.com/",target:"_blank",rel:"noopener noreferrer"},j={href:"https://www.swiftsmsgateway.com/",target:"_blank",rel:"noopener noreferrer"},x={href:"https://bitnami.com/",target:"_blank",rel:"noopener noreferrer"};function N(v,y){const t=l("ExternalLinkIcon");return s(),a("div",null,[_,c,e("ul",null,[e("li",null,[e("a",h,[r("Node.js"),o(t)])]),e("li",null,[e("a",d,[r("NestJS"),o(t)])]),e("li",null,[e("a",f,[r("MongoDB"),o(t)])]),e("li",null,[e("a",p,[r("NodeMailer"),o(t)])]),e("li",null,[e("a",u,[r("JMESPath"),o(t)])]),e("li",null,[e("a",m,[r("Vue"),o(t)])]),e("li",null,[e("a",g,[r("Vuetify"),o(t)])]),e("li",null,[e("a",b,[r("JSON Editor"),o(t)])]),e("li",null,[e("a",w,[r("VuePress"),o(t)])]),e("li",null,[e("a",k,[r("Twilio"),o(t)])]),e("li",null,[e("a",j,[r("Swift SMS Gateway"),o(t)])]),e("li",null,[e("a",x,[r("Bitnami"),o(t)])])])])}const S=n(i,[["render",N],["__file","index.html.vue"]]);export{S as default};
    diff --git a/assets/index.html-00458894.js b/assets/index.html-c60b591d.js
    similarity index 99%
    rename from assets/index.html-00458894.js
    rename to assets/index.html-c60b591d.js
    index 93a84dad7..706b50410 100644
    --- a/assets/index.html-00458894.js
    +++ b/assets/index.html-c60b591d.js
    @@ -1,4 +1,4 @@
    -import{_ as p,r as i,o as l,c as r,a as s,b as n,d as e,w as o,e as c}from"./app-77c416fe.js";const d={},u=s("h1",{id:"cron-jobs",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#cron-jobs","aria-hidden":"true"},"#"),n(" Cron Jobs")],-1),m=s("p",null,[s("em",null,"NotifyBC"),n(" runs several cron jobs described below. These jobs are controlled by sub-properties defined in config object "),s("em",null,"cron"),n(". To change config, create the object and properties in file "),s("em",null,"/src/config.local.js"),n(".")],-1),v=s("a",{name:"timeSpec"},null,-1),f=s("em",null,"timeSpec",-1),k={href:"https://www.freebsd.org/cgi/man.cgi?crontab(5)",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/kelektiv/node-cron#cron-ranges",target:"_blank",rel:"noopener noreferrer"},h=c(`

    Purge Data

    This cron job purges old notifications, subscriptions and notification bounces. The default frequency of cron job and retention policy are defined by cron.purgeData config object in file /src/config.ts

    module.exports = {
    +import{_ as p,r as i,o as l,c as r,a as s,b as n,d as e,w as o,e as c}from"./app-be471a62.js";const d={},u=s("h1",{id:"cron-jobs",tabindex:"-1"},[s("a",{class:"header-anchor",href:"#cron-jobs","aria-hidden":"true"},"#"),n(" Cron Jobs")],-1),m=s("p",null,[s("em",null,"NotifyBC"),n(" runs several cron jobs described below. These jobs are controlled by sub-properties defined in config object "),s("em",null,"cron"),n(". To change config, create the object and properties in file "),s("em",null,"/src/config.local.js"),n(".")],-1),v=s("a",{name:"timeSpec"},null,-1),f=s("em",null,"timeSpec",-1),k={href:"https://www.freebsd.org/cgi/man.cgi?crontab(5)",target:"_blank",rel:"noopener noreferrer"},b={href:"https://github.com/kelektiv/node-cron#cron-ranges",target:"_blank",rel:"noopener noreferrer"},h=c(`

    Purge Data

    This cron job purges old notifications, subscriptions and notification bounces. The default frequency of cron job and retention policy are defined by cron.purgeData config object in file /src/config.ts

    module.exports = {
       cron: {
         purgeData: {
           // daily at 1am
    diff --git a/assets/index.html-655164a6.js b/assets/index.html-c8dd00de.js
    similarity index 99%
    rename from assets/index.html-655164a6.js
    rename to assets/index.html-c8dd00de.js
    index 325828714..64d6809d0 100644
    --- a/assets/index.html-655164a6.js
    +++ b/assets/index.html-c8dd00de.js
    @@ -1,4 +1,4 @@
    -import{_ as r,r as l,o as p,c,a as e,b as a,d as n,w as t,e as o}from"./app-77c416fe.js";const d={},u=o('

    Upgrade Guide

    Major version can only be upgraded incrementally from immediate previous major version, i.e. from N to N+1.

    v1 to v2

    Upgrading NotifyBC from v1 to v2 involves two steps

    1. Update your client code if needed
    2. Upgrade NotifyBC server

    Update your client code

    NotifyBC v2 introduced backward incompatible API changes documented in the rest of this section. If your client code will be impacted by the changes, update your code to address the incompatibility first.

    Query parameter array syntax

    In v1 array can be specified in query parameter using two formats

    1. by enclosing array elements in square brackets such as &additionalServices=["s1","s2] in one query parameter
    2. by repeating the query parameters, for example &additionalServices=s1&additionalServices=s2

    In v2 only the latter format is supported.

    Date-Time fields

    In v1 date-time fields can be specified in date-only string such as 2020-01-01. In v2 the field must be specified in ISO 8601 extended format such as 2020-01-01T00:00:00Z.

    Return status codes

    HTTP response code of success calls to following APIs are changed from 200 to 204

    ',15),m=e("h4",{id:"administrator-api",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#administrator-api","aria-hidden":"true"},"#"),a(" Administrator API")],-1),v=e("em",null,"Administrator",-1),h=e("em",null,"UserCredential",-1),b=o(`

    Upgrade NotifyBC server

    The procedure to upgrade from v1 to v2 depends on how v1 was installed.

    Source-code Installation

    1. Stop NotifyBC
    2. Backup app root and database!
    3. Make sure current branch is tracking correct remote branch
      git remote set-url origin https://github.com/bcgov/NotifyBC.git
      +import{_ as r,r as l,o as p,c,a as e,b as a,d as n,w as t,e as o}from"./app-be471a62.js";const d={},u=o('

      Upgrade Guide

      Major version can only be upgraded incrementally from immediate previous major version, i.e. from N to N+1.

      v1 to v2

      Upgrading NotifyBC from v1 to v2 involves two steps

      1. Update your client code if needed
      2. Upgrade NotifyBC server

      Update your client code

      NotifyBC v2 introduced backward incompatible API changes documented in the rest of this section. If your client code will be impacted by the changes, update your code to address the incompatibility first.

      Query parameter array syntax

      In v1 array can be specified in query parameter using two formats

      1. by enclosing array elements in square brackets such as &additionalServices=["s1","s2] in one query parameter
      2. by repeating the query parameters, for example &additionalServices=s1&additionalServices=s2

      In v2 only the latter format is supported.

      Date-Time fields

      In v1 date-time fields can be specified in date-only string such as 2020-01-01. In v2 the field must be specified in ISO 8601 extended format such as 2020-01-01T00:00:00Z.

      Return status codes

      HTTP response code of success calls to following APIs are changed from 200 to 204

      ',15),m=e("h4",{id:"administrator-api",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#administrator-api","aria-hidden":"true"},"#"),a(" Administrator API")],-1),v=e("em",null,"Administrator",-1),h=e("em",null,"UserCredential",-1),b=o(`

      Upgrade NotifyBC server

      The procedure to upgrade from v1 to v2 depends on how v1 was installed.

      Source-code Installation

      1. Stop NotifyBC
      2. Backup app root and database!
      3. Make sure current branch is tracking correct remote branch
        git remote set-url origin https://github.com/bcgov/NotifyBC.git
         git branch -u origin/main
         
      4. Make a note of any extra packages added to package.json
      5. Run git pull && git checkout tags/v2.x.x from app root, replace v2.x.x with a v2 release, preferably latest, found in GitHub such as v2.9.0.
      6. Make sure version property in package.json is 2.x.x
      7. Add back extra packages noted in step 4
      8. Move server/config.(local|dev|production).(js|json) to src/ if exists
      9. Move server/datasources.(local|dev|production).(js|json) to src/datasources/db.datasource.(local|dev|production).(js|json) if exists. Notice the file name has changed.
      10. Move server/middleware.*.(js|json) to src/ if exists. Reorganize top level properties to all or apiOnly, where all applies to all requests including web console and apiOnly applies to API requests only. For example, given
      module.exports = {
         initial: {
      diff --git a/assets/index.html-cd7a5a49.js b/assets/index.html-d1f8fc1e.js
      similarity index 96%
      rename from assets/index.html-cd7a5a49.js
      rename to assets/index.html-d1f8fc1e.js
      index a82b76a74..933c60d37 100644
      --- a/assets/index.html-cd7a5a49.js
      +++ b/assets/index.html-d1f8fc1e.js
      @@ -1,4 +1,4 @@
      -import{_ as e,o as a,c as n,e as o}from"./app-77c416fe.js";const s={},t=o(`

      Memory Dump

      To troubleshoot memory related issues, Super-admin can get a memory dump of NotifyBC by querying /memory API end point. For example

      $ curl -s http://localhost:3000/api/memory
      +import{_ as e,o as a,c as n,e as o}from"./app-be471a62.js";const s={},t=o(`

      Memory Dump

      To troubleshoot memory related issues, Super-admin can get a memory dump of NotifyBC by querying /memory API end point. For example

      $ curl -s http://localhost:3000/api/memory
       Heap.20240513.114015.22037.0.001.heapsnapshot
       

      The output is the file name of the memory dump. The dump file can be loaded by, for example, Chrome DevTools.

      fileName query parameter can be used to specify the file path and name

      $ curl -s http://localhost:3000/api/memory?fileName=/tmp/my.heapsnapshot
       /tmp/my.heapsnapshot
      diff --git a/assets/index.html-5fb6acba.js b/assets/index.html-d4f673ca.js
      similarity index 99%
      rename from assets/index.html-5fb6acba.js
      rename to assets/index.html-d4f673ca.js
      index 14027a9c7..e5dee304f 100644
      --- a/assets/index.html-5fb6acba.js
      +++ b/assets/index.html-d4f673ca.js
      @@ -1,4 +1,4 @@
      -import{_ as s,r as l,o as r,c as o,a as e,b as t,d as n,e as i}from"./app-77c416fe.js";const d={},p=i(`

      Configuration

      The configuration API, accessible by only super-admin requests, is used to define dynamic configurations. Dynamic configuration is needed in situations like

      • RSA key pair generated automatically at boot time if not present
      • service-specific subscription confirmation request message template

      Model Schema

      The API operates on following configuration data model fields:

      NameAttributes

      id

      config id

      typestring, format depends on db
      auto-generatedtrue

      name

      config name

      typestring
      requiredtrue

      value

      config value.
      typeobject
      requiredtrue

      serviceName

      name of the service the config applicable to

      typestring
      requiredfalse

      Get Configurations

      GET /configurations
      +import{_ as s,r as l,o as r,c as o,a as e,b as t,d as n,e as i}from"./app-be471a62.js";const d={},p=i(`

      Configuration

      The configuration API, accessible by only super-admin requests, is used to define dynamic configurations. Dynamic configuration is needed in situations like

      • RSA key pair generated automatically at boot time if not present
      • service-specific subscription confirmation request message template

      Model Schema

      The API operates on following configuration data model fields:

      NameAttributes

      id

      config id

      typestring, format depends on db
      auto-generatedtrue

      name

      config name

      typestring
      requiredtrue

      value

      config value.
      typeobject
      requiredtrue

      serviceName

      name of the service the config applicable to

      typestring
      requiredfalse

      Get Configurations

      GET /configurations
       
      `,8),u=e("li",null,[e("p",null,"permissions required, one of"),e("ul",null,[e("li",null,"super admin"),e("li",null,"admin")])],-1),c=e("p",null,"inputs",-1),m=i("

      a filter containing properties where, fields, order, skip, and limit

      • parameter name: filter
      • required: false
      • parameter type: query
      • data type: object

      The filter can be expressed as either

      ",3),h=e("li",null,"URL-encoded stringified JSON object (see example below); or",-1),f={href:"https://github.com/ljharb/qs",target:"_blank",rel:"noopener noreferrer"},g=e("code",null,'?filter[where][created][$gte]="2023-01-01"&filter[where][created][$lt]="2024-01-01"',-1),b=i(`

      Regardless, the filter will have to be parsed into a JSON object conforming to

      {
           "where": {...},
           "fields": ...,
      diff --git a/assets/index.html-6b8cefcc.js b/assets/index.html-db6fe449.js
      similarity index 96%
      rename from assets/index.html-6b8cefcc.js
      rename to assets/index.html-db6fe449.js
      index ef75195fd..79ed0a85c 100644
      --- a/assets/index.html-6b8cefcc.js
      +++ b/assets/index.html-db6fe449.js
      @@ -1 +1 @@
      -import{_ as o,r as i,o as s,c as r,a as t,b as e,u as c,d as l,e as h,f as d}from"./app-77c416fe.js";const u=h('

      Welcome

      This site aims to be a comprehensive guide to NotifyBC. We’ll cover topics such as getting your instance up and running, interacting with browser or other server components, deployment, and give you some advice on participating in the future development of NotifyBC itself.

      Helpful Hints

      Throughout this guide there are a number of small-but-handy pieces of information that can make using NotifyBC easier, more interesting, and less hazardous. Here’s what to look out for.

      General information

      These are tips and tricks that will help you become a NotifyBC wizard!

      Important information

      These are tidbits you might want to keep in mind.

      Warnings

      Be aware of these messages if you wish to avoid disaster.

      ',7),p=["href"],m={__name:"index.html",setup(f){const a=d();return(g,_)=>{const n=i("ExternalLinkIcon");return s(),r("div",null,[u,t("p",null,[e("If you come across anything along the way that we haven’t covered, or if you know of a tip you think others would find handy, please "),t("a",{target:"_blank",rel:"noopener noreferrer",href:c(a).repo+"/issues/new"},[e("file an issue"),l(n)],8,p),e(" and we’ll see about including it in this guide.")])])}}},w=o(m,[["__file","index.html.vue"]]);export{w as default}; +import{_ as o,r as i,o as s,c as r,a as t,b as e,u as c,d as l,e as h,f as d}from"./app-be471a62.js";const u=h('

      Welcome

      This site aims to be a comprehensive guide to NotifyBC. We’ll cover topics such as getting your instance up and running, interacting with browser or other server components, deployment, and give you some advice on participating in the future development of NotifyBC itself.

      Helpful Hints

      Throughout this guide there are a number of small-but-handy pieces of information that can make using NotifyBC easier, more interesting, and less hazardous. Here’s what to look out for.

      General information

      These are tips and tricks that will help you become a NotifyBC wizard!

      Important information

      These are tidbits you might want to keep in mind.

      Warnings

      Be aware of these messages if you wish to avoid disaster.

      ',7),p=["href"],m={__name:"index.html",setup(f){const a=d();return(g,_)=>{const n=i("ExternalLinkIcon");return s(),r("div",null,[u,t("p",null,[e("If you come across anything along the way that we haven’t covered, or if you know of a tip you think others would find handy, please "),t("a",{target:"_blank",rel:"noopener noreferrer",href:c(a).repo+"/issues/new"},[e("file an issue"),l(n)],8,p),e(" and we’ll see about including it in this guide.")])])}}},w=o(m,[["__file","index.html.vue"]]);export{w as default}; diff --git a/assets/index.html-cf1801d6.js b/assets/index.html-e0ff510f.js similarity index 91% rename from assets/index.html-cf1801d6.js rename to assets/index.html-e0ff510f.js index 7482a01b5..d719b1f7e 100644 --- a/assets/index.html-cf1801d6.js +++ b/assets/index.html-e0ff510f.js @@ -1 +1 @@ -import{_ as t,o,c as s,a as e,b as n}from"./app-77c416fe.js";const a={},l=e("h1",{id:"node-roles",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#node-roles","aria-hidden":"true"},"#"),n(" Node Roles")],-1),d=e("p",null,[n("In a multi-node deployment, some tasks should only be run by one node. That node is designated as "),e("em",null,"master"),n(". The distinction is made using environment variable "),e("em",null,"NOTIFYBC_NODE_ROLE"),n(". Setting to anything other than "),e("em",null,"slave"),n(", including not set, will be regarded as "),e("em",null,"master"),n(".")],-1),i=[l,d];function r(c,_){return o(),s("div",null,i)}const m=t(a,[["render",r],["__file","index.html.vue"]]);export{m as default}; +import{_ as t,o,c as s,a as e,b as n}from"./app-be471a62.js";const a={},l=e("h1",{id:"node-roles",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#node-roles","aria-hidden":"true"},"#"),n(" Node Roles")],-1),d=e("p",null,[n("In a multi-node deployment, some tasks should only be run by one node. That node is designated as "),e("em",null,"master"),n(". The distinction is made using environment variable "),e("em",null,"NOTIFYBC_NODE_ROLE"),n(". Setting to anything other than "),e("em",null,"slave"),n(", including not set, will be regarded as "),e("em",null,"master"),n(".")],-1),i=[l,d];function r(c,_){return o(),s("div",null,i)}const m=t(a,[["render",r],["__file","index.html.vue"]]);export{m as default}; diff --git a/assets/index.html-38194d5f.js b/assets/index.html-e9ca5832.js similarity index 99% rename from assets/index.html-38194d5f.js rename to assets/index.html-e9ca5832.js index 5168683a1..c68c7668e 100644 --- a/assets/index.html-38194d5f.js +++ b/assets/index.html-e9ca5832.js @@ -1,4 +1,4 @@ -import{_ as r,r as n,o as l,c as d,a as e,b as t,d as a,w as p,e as s}from"./app-77c416fe.js";const c={},u=s(`

      Notification

      The notification API encapsulates the backend workflow of staging and dispatching a message to targeted user after receiving the message from event source.

      Depending on whether an API call comes from user browser as a user request or from an authorized server application as an admin request, NotifyBC applies different permissions. Admin request allows full CRUD operations. An authenticated user request, on the other hand, are only allowed to get a list of in-app pull notifications targeted to the current user and changing the state of the notifications. An unauthenticated user request can not access any API.

      When a notification is created by the event source server application, the message is saved to database prior to responding to API caller. In addition, for push notification, the message is delivered immediately, i.e. the API call is synchronous. For in-app pull notification, the message, which by default is in state new, can be retrieved later on by browser user request. A user request can only get the list of in-app messages targeted to the current user. A user request can then change the message state to read or deleted depending on user action. A deleted message cannot be retrieved subsequently by user requests, but the state can be updated given the correct id.

      Deleted message is still kept in database.

      NotifyBC provides API for deleting a notification. For the purpose of auditing and recovery, this API only marks the state field as deleted rather than deleting the record from database.

      undo in-app notification deletion within a session

      Because "deleted" message is still kept in database, you can implement undo feature for in-app notification as long as the message id is retained prior to deletion within the current session. To undo, call update API to set desired state.

      In-app pull notification also supports message expiration by setting a date in field validTill. An expired message cannot be retrieved by user requests.

      A message, regardless of push or pull, can be unicast or broadcast. A unicast message is intended for an individual user whereas a broadcast message is intended for all confirmed subscribers of a service. A unicast message must have field userChannelId populated. The value of userChannelId is channel dependent. In the case of email for example, this would be user's email address. A broadcast message must set isBroadcast to true and leave userChannelId empty.

      Why field isBroadcast?

      Unicast and broadcast message can be distinguished by whether field userChannelId is empty or not alone. So why the extra field isBroadcast? This is in order to prevent inadvertent marking a unicast message broadcast by omitting userChannelId or populating it with empty value. The precaution is necessary because in-app notifications may contain personalized and confidential information.

      NotifyBC ensures the state of an in-app broadcast message is isolated by user, so that for example, a message read by one user is still new to another user. To achieve this, NotifyBC maintains two internal fields of array type - readBy and deletedBy. When a user request updates the state field of an in-app broadcast message to read or deleted, instead of altering the state field, NotifyBC appends the current user to readBy or deletedBy list. When user request retrieving in-app messages, the state field of the broadcast message in HTTP response is updated based on whether the user exists in field deletedBy and readBy. If existing in both fields, deletedBy takes precedence (the message therefore is not returned). The record in database, meanwhile, is unchanged. Neither field deletedBy nor readBy is visible to user request.

      Model Schema

      The API operates on following notification data model fields:

      NameAttributes

      id

      notification id

      typestring, format depends on db
      auto-generatedtrue

      serviceName

      name of the service

      typestring
      requiredtrue

      channel

      name of the delivery channel. Valid values: inApp, email, sms.

      typestring
      requiredtrue
      defaultinApp

      userChannelId

      user's delivery channel id, for example, email address. For unicast inApp notification, this is authenticated user id. When sending unicast push notification, either userChannelId or userId is required.

      typestring
      requiredfalse

      userId

      authenticated user id. When sending unicast push notification, either userChannelId or userId is required.

      typestring
      requiredfalse

      state

      state of notification. Valid values: new, read (inApp only), deleted (inApp only), sent (push only) or error. For inApp broadcast notification, if the user has read or deleted the message, the value of this field retrieved by admin request will still be new. The state for the user is tracked in fields readBy and deletedBy in such case. For user request, the value contains correct state.

      typestring
      requiredtrue
      defaultnew

      created

      date and time of creation

      typedate
      auto-generatedtrue

      updated

      date and time of last update

      typedate
      auto-generatedtrue

      isBroadcast

      whether it's a broadcast message. A broadcast message should omit userChannelId and userId, in addition to setting isBroadcast to true

      typeboolean
      requiredfalse
      defaultfalse

      skipSubscriptionConfirmationCheck

      When sending unicast push notification, whether or not to verify if the recipient has a confirmed subscription. This field allows subscription information be kept elsewhere and NotifyBC be used as a unicast push notification gateway only.

      typeboolean
      requiredfalse
      defaultfalse

      validTill

      expiration date-time of the message. Applicable to inApp notification only.

      typedate
      requiredfalse

      invalidBefore

      date-time in the future after which the notification can be dispatched.

      typedate
      requiredfalse

      message

      an object whose child fields are channel dependent:
      • for inApp, NotifyBC doesn't have any restriction as long as web application can handle the message. subject and body are common examples.
      • for email: from, subject, textBody, htmlBody
        • type: string
        • these are email template fields.
      • for sms: textBody
        • type: string
        • sms message template.
      Mail merge is performed on email and sms message templates.
      typeobject
      requiredtrue

      httpHost

      This field is used to replace token {http_host} in push notification message template during mail merge and overrides config httpHost.

      typestring
      requiredfalse
      default<http protocol, host and port of current request> for push notification

      asyncBroadcastPushNotification

      this field determines if the API call to create an immediate (i.e. not future-dated) broadcast push notification is asynchronous or not. If omitted, the API call is synchronous, i.e. the API call blocks until notifications to all subscribers have been dispatched. If set, valid values and corresponding behaviors are
      • true - async without callback
      • false - sync
      • a string containing callback url - async with a POST call to the supplied callback url upon completion
      When posting to a service with large number of subscribers, it is highly recommended to set the API call to asynchronous, i.e. setting the value to true or supplying a callback.
      typestring or boolean
      requiredfalse
      defaultfalse

      data

      the event that triggers the notification, for example, a RSS feed item when the notification is generated automatically by RSS cron job. Field data serves two purposes
      • to replace dynamic tokens in message template fields
      • to match against filter defined in subscription field broadcastPushNotificationFilter, if supplied, for broadcast push notifications to determine if the notification should be delivered to the subscriber
      typeobject
      requiredfalse

      broadcastPushNotificationSubscriptionFilter

      a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
      • simple
        province == 'BC'
      • calling jmespath's built-in functions
        contains(province,'B')
      • calling custom filter functions
        contains_ci(province,'b')
      • compound
        (contains(province,'BC') || contains_ci(province,'b')) && city == 'Victoria'
      All of above filters will match data object {"province": "BC", "city": "Victoria"}
      typestring
      requiredfalse

      readBy

      this is an internal field to track the list of users who have read an inApp broadcast message. It's not visible to a user request.

      typearray
      requiredfalse
      auto-generatedtrue

      deletedBy

      this is an internal field to track the list of users who have marked an inApp broadcast message as deleted. It's not visible to a user request.

      typearray
      requiredfalse
      auto-generatedtrue

      dispatch

      this is an internal field to track the broadcast push notification dispatch outcome. It consists of up to four arrays

      • failed - a list of objects containing subscription IDs and error of failed dispatching
      • successful - a list of strings containing subscription IDs of successful dispatching
      • skipped - a list of strings containing subscription IDs of skipped dispatching
      • candidates - a list of strings containing IDs of confirmed subscriptions to the service. Dispatching to a subscription is subject to filtering.
      typeobject
      requiredfalse
      auto-generatedtrue

      Get Notifications

      GET /notifications
      +import{_ as r,r as n,o as l,c as d,a as e,b as t,d as a,w as p,e as s}from"./app-be471a62.js";const c={},u=s(`

      Notification

      The notification API encapsulates the backend workflow of staging and dispatching a message to targeted user after receiving the message from event source.

      Depending on whether an API call comes from user browser as a user request or from an authorized server application as an admin request, NotifyBC applies different permissions. Admin request allows full CRUD operations. An authenticated user request, on the other hand, are only allowed to get a list of in-app pull notifications targeted to the current user and changing the state of the notifications. An unauthenticated user request can not access any API.

      When a notification is created by the event source server application, the message is saved to database prior to responding to API caller. In addition, for push notification, the message is delivered immediately, i.e. the API call is synchronous. For in-app pull notification, the message, which by default is in state new, can be retrieved later on by browser user request. A user request can only get the list of in-app messages targeted to the current user. A user request can then change the message state to read or deleted depending on user action. A deleted message cannot be retrieved subsequently by user requests, but the state can be updated given the correct id.

      Deleted message is still kept in database.

      NotifyBC provides API for deleting a notification. For the purpose of auditing and recovery, this API only marks the state field as deleted rather than deleting the record from database.

      undo in-app notification deletion within a session

      Because "deleted" message is still kept in database, you can implement undo feature for in-app notification as long as the message id is retained prior to deletion within the current session. To undo, call update API to set desired state.

      In-app pull notification also supports message expiration by setting a date in field validTill. An expired message cannot be retrieved by user requests.

      A message, regardless of push or pull, can be unicast or broadcast. A unicast message is intended for an individual user whereas a broadcast message is intended for all confirmed subscribers of a service. A unicast message must have field userChannelId populated. The value of userChannelId is channel dependent. In the case of email for example, this would be user's email address. A broadcast message must set isBroadcast to true and leave userChannelId empty.

      Why field isBroadcast?

      Unicast and broadcast message can be distinguished by whether field userChannelId is empty or not alone. So why the extra field isBroadcast? This is in order to prevent inadvertent marking a unicast message broadcast by omitting userChannelId or populating it with empty value. The precaution is necessary because in-app notifications may contain personalized and confidential information.

      NotifyBC ensures the state of an in-app broadcast message is isolated by user, so that for example, a message read by one user is still new to another user. To achieve this, NotifyBC maintains two internal fields of array type - readBy and deletedBy. When a user request updates the state field of an in-app broadcast message to read or deleted, instead of altering the state field, NotifyBC appends the current user to readBy or deletedBy list. When user request retrieving in-app messages, the state field of the broadcast message in HTTP response is updated based on whether the user exists in field deletedBy and readBy. If existing in both fields, deletedBy takes precedence (the message therefore is not returned). The record in database, meanwhile, is unchanged. Neither field deletedBy nor readBy is visible to user request.

      Model Schema

      The API operates on following notification data model fields:

      NameAttributes

      id

      notification id

      typestring, format depends on db
      auto-generatedtrue

      serviceName

      name of the service

      typestring
      requiredtrue

      channel

      name of the delivery channel. Valid values: inApp, email, sms.

      typestring
      requiredtrue
      defaultinApp

      userChannelId

      user's delivery channel id, for example, email address. For unicast inApp notification, this is authenticated user id. When sending unicast push notification, either userChannelId or userId is required.

      typestring
      requiredfalse

      userId

      authenticated user id. When sending unicast push notification, either userChannelId or userId is required.

      typestring
      requiredfalse

      state

      state of notification. Valid values: new, read (inApp only), deleted (inApp only), sent (push only) or error. For inApp broadcast notification, if the user has read or deleted the message, the value of this field retrieved by admin request will still be new. The state for the user is tracked in fields readBy and deletedBy in such case. For user request, the value contains correct state.

      typestring
      requiredtrue
      defaultnew

      created

      date and time of creation

      typedate
      auto-generatedtrue

      updated

      date and time of last update

      typedate
      auto-generatedtrue

      isBroadcast

      whether it's a broadcast message. A broadcast message should omit userChannelId and userId, in addition to setting isBroadcast to true

      typeboolean
      requiredfalse
      defaultfalse

      skipSubscriptionConfirmationCheck

      When sending unicast push notification, whether or not to verify if the recipient has a confirmed subscription. This field allows subscription information be kept elsewhere and NotifyBC be used as a unicast push notification gateway only.

      typeboolean
      requiredfalse
      defaultfalse

      validTill

      expiration date-time of the message. Applicable to inApp notification only.

      typedate
      requiredfalse

      invalidBefore

      date-time in the future after which the notification can be dispatched.

      typedate
      requiredfalse

      message

      an object whose child fields are channel dependent:
      • for inApp, NotifyBC doesn't have any restriction as long as web application can handle the message. subject and body are common examples.
      • for email: from, subject, textBody, htmlBody
        • type: string
        • these are email template fields.
      • for sms: textBody
        • type: string
        • sms message template.
      Mail merge is performed on email and sms message templates.
      typeobject
      requiredtrue

      httpHost

      This field is used to replace token {http_host} in push notification message template during mail merge and overrides config httpHost.

      typestring
      requiredfalse
      default<http protocol, host and port of current request> for push notification

      asyncBroadcastPushNotification

      this field determines if the API call to create an immediate (i.e. not future-dated) broadcast push notification is asynchronous or not. If omitted, the API call is synchronous, i.e. the API call blocks until notifications to all subscribers have been dispatched. If set, valid values and corresponding behaviors are
      • true - async without callback
      • false - sync
      • a string containing callback url - async with a POST call to the supplied callback url upon completion
      When posting to a service with large number of subscribers, it is highly recommended to set the API call to asynchronous, i.e. setting the value to true or supplying a callback.
      typestring or boolean
      requiredfalse
      defaultfalse

      data

      the event that triggers the notification, for example, a RSS feed item when the notification is generated automatically by RSS cron job. Field data serves two purposes
      • to replace dynamic tokens in message template fields
      • to match against filter defined in subscription field broadcastPushNotificationFilter, if supplied, for broadcast push notifications to determine if the notification should be delivered to the subscriber
      typeobject
      requiredfalse

      broadcastPushNotificationSubscriptionFilter

      a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
      • simple
        province == 'BC'
      • calling jmespath's built-in functions
        contains(province,'B')
      • calling custom filter functions
        contains_ci(province,'b')
      • compound
        (contains(province,'BC') || contains_ci(province,'b')) && city == 'Victoria'
      All of above filters will match data object {"province": "BC", "city": "Victoria"}
      typestring
      requiredfalse

      readBy

      this is an internal field to track the list of users who have read an inApp broadcast message. It's not visible to a user request.

      typearray
      requiredfalse
      auto-generatedtrue

      deletedBy

      this is an internal field to track the list of users who have marked an inApp broadcast message as deleted. It's not visible to a user request.

      typearray
      requiredfalse
      auto-generatedtrue

      dispatch

      this is an internal field to track the broadcast push notification dispatch outcome. It consists of up to four arrays

      • failed - a list of objects containing subscription IDs and error of failed dispatching
      • successful - a list of strings containing subscription IDs of successful dispatching
      • skipped - a list of strings containing subscription IDs of skipped dispatching
      • candidates - a list of strings containing IDs of confirmed subscriptions to the service. Dispatching to a subscription is subject to filtering.
      typeobject
      requiredfalse
      auto-generatedtrue

      Get Notifications

      GET /notifications
       
      `,15),m=e("li",null,[e("p",null,"permissions required, one of"),e("ul",null,[e("li",null,"super admin"),e("li",null,"admin"),e("li",null,"authenticated user")])],-1),h=e("p",null,"inputs",-1),f=s("

      a filter containing properties where, fields, order, skip, and limit

      • parameter name: filter
      • required: false
      • parameter type: query
      • data type: object

      The filter can be expressed as either

      ",3),b=e("li",null,"URL-encoded stringified JSON object (see example below); or",-1),g={href:"https://github.com/ljharb/qs",target:"_blank",rel:"noopener noreferrer"},v=e("code",null,'?filter[where][created][$gte]="2023-01-01"&filter[where][created][$lt]="2024-01-01"',-1),y=s(`

      Regardless, the filter will have to be parsed into a JSON object conforming to

      {
           "where": {...},
           "fields": ...,
      diff --git a/assets/index.html-54e79f35.js b/assets/index.html-ea71ab91.js
      similarity index 98%
      rename from assets/index.html-54e79f35.js
      rename to assets/index.html-ea71ab91.js
      index 570c43e5d..4c43b5995 100644
      --- a/assets/index.html-54e79f35.js
      +++ b/assets/index.html-ea71ab91.js
      @@ -1 +1 @@
      -import{_ as s,r as a,o as c,c as l,a as e,b as i,d as t,w as o,e as r}from"./app-77c416fe.js";const d={},f=e("h1",{id:"configuration-overview",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#configuration-overview","aria-hidden":"true"},"#"),i(" Configuration Overview")],-1),u={class:"custom-container tip"},m=e("p",{class:"custom-container-title"},"Helm Chart Configurations",-1),g=e("em",null,"NoitfyBC",-1),h=e("em",null,"NotifyBC",-1),p=r('

      There are two types of configurations - static and dynamic. Static configurations are defined in files or environment variables, requiring restarting NotifyBC to take effect, whereas dynamic configurations are defined in databases and updates take effect immediately.

      Static Configurations

      Most static configurations are specified in file /src/config.ts. If you need to change, instead of updating /src/config.ts file, create local file /src/config.local.js or environment specific file /src/config.<env>.js, which is only included when environment variable NODE_ENV equals <env>. Besides js, ts and json file extensions are also supported. The rest of the documentation assumes the file extension is js. Content in these files are deeply merged in following ascending precedence

      • default file /src/config.ts
      • environment specific file /src/config.<env>.js
      • local file /src/config.local.js

      Run build script whenever changing file in /src

      Every time a file under /src, including config files, is updated, run npm run build before restarting NotifyBC to take effect.

      Following configs should be customized per installation

      ',6),_=e("p",null,"In addition, if installing from source code",-1),v=e("p",null,"Customizing other configs only if needed.",-1),y=e("h2",{id:"dynamic-configurations",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#dynamic-configurations","aria-hidden":"true"},"#"),i(" Dynamic Configurations")],-1),b=e("div",{class:"custom-container tip"},[e("p",{class:"custom-container-title"},"Why Dynamic Configs?"),e("p",null,"Dynamic configs are needed in cases such as"),e("ul",null,[e("li",null,"to allow define service-specific configs such as message templates"),e("li",null,"in a multi-node deployment, configs can be generated by one node (typically master) and shared with other nodes")])],-1);function w(x,C){const n=a("RouterLink");return c(),l("div",null,[f,e("div",u,[m,e("p",null,[i("The document pages in this section cover "),g,i(" app level configurations only. If your "),h,i(" is deployed to Kubernetes using Helm, you can also "),t(n,{to:"/docs/getting-started/installation.html#customizations"},{default:o(()=>[i("customize")]),_:1}),i(" infrastructure level configurations.")])]),p,e("ul",null,[e("li",null,[t(n,{to:"/docs/config/adminIpList.html"},{default:o(()=>[i("Admin IP List")]),_:1})]),e("li",null,[t(n,{to:"/docs/config/reverseProxyIpLists.html"},{default:o(()=>[i("Reverse Proxy IP Lists")]),_:1})]),e("li",null,[t(n,{to:"/docs/config/httpHost.html"},{default:o(()=>[i("HTTP Host")]),_:1})]),e("li",null,[t(n,{to:"/docs/config/email.html#smtp"},{default:o(()=>[i("SMTP")]),_:1})])]),_,e("ul",null,[e("li",null,[t(n,{to:"/docs/config/database.html"},{default:o(()=>[i("Database")]),_:1})]),e("li",null,[t(n,{to:"/docs/config/internalHttpHost.html"},{default:o(()=>[i("Internal HTTP Host")]),_:1})])]),v,y,e("p",null,[i("Dynamic configs are managed using REST "),t(n,{to:"/docs/api-config/"},{default:o(()=>[i("configuration api")]),_:1}),i(".")]),b])}const T=s(d,[["render",w],["__file","index.html.vue"]]);export{T as default}; +import{_ as s,r as a,o as c,c as l,a as e,b as i,d as t,w as o,e as r}from"./app-be471a62.js";const d={},f=e("h1",{id:"configuration-overview",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#configuration-overview","aria-hidden":"true"},"#"),i(" Configuration Overview")],-1),u={class:"custom-container tip"},m=e("p",{class:"custom-container-title"},"Helm Chart Configurations",-1),g=e("em",null,"NoitfyBC",-1),h=e("em",null,"NotifyBC",-1),p=r('

      There are two types of configurations - static and dynamic. Static configurations are defined in files or environment variables, requiring restarting NotifyBC to take effect, whereas dynamic configurations are defined in databases and updates take effect immediately.

      Static Configurations

      Most static configurations are specified in file /src/config.ts. If you need to change, instead of updating /src/config.ts file, create local file /src/config.local.js or environment specific file /src/config.<env>.js, which is only included when environment variable NODE_ENV equals <env>. Besides js, ts and json file extensions are also supported. The rest of the documentation assumes the file extension is js. Content in these files are deeply merged in following ascending precedence

      • default file /src/config.ts
      • environment specific file /src/config.<env>.js
      • local file /src/config.local.js

      Run build script whenever changing file in /src

      Every time a file under /src, including config files, is updated, run npm run build before restarting NotifyBC to take effect.

      Following configs should be customized per installation

      ',6),_=e("p",null,"In addition, if installing from source code",-1),v=e("p",null,"Customizing other configs only if needed.",-1),y=e("h2",{id:"dynamic-configurations",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#dynamic-configurations","aria-hidden":"true"},"#"),i(" Dynamic Configurations")],-1),b=e("div",{class:"custom-container tip"},[e("p",{class:"custom-container-title"},"Why Dynamic Configs?"),e("p",null,"Dynamic configs are needed in cases such as"),e("ul",null,[e("li",null,"to allow define service-specific configs such as message templates"),e("li",null,"in a multi-node deployment, configs can be generated by one node (typically master) and shared with other nodes")])],-1);function w(x,C){const n=a("RouterLink");return c(),l("div",null,[f,e("div",u,[m,e("p",null,[i("The document pages in this section cover "),g,i(" app level configurations only. If your "),h,i(" is deployed to Kubernetes using Helm, you can also "),t(n,{to:"/docs/getting-started/installation.html#customizations"},{default:o(()=>[i("customize")]),_:1}),i(" infrastructure level configurations.")])]),p,e("ul",null,[e("li",null,[t(n,{to:"/docs/config/adminIpList.html"},{default:o(()=>[i("Admin IP List")]),_:1})]),e("li",null,[t(n,{to:"/docs/config/reverseProxyIpLists.html"},{default:o(()=>[i("Reverse Proxy IP Lists")]),_:1})]),e("li",null,[t(n,{to:"/docs/config/httpHost.html"},{default:o(()=>[i("HTTP Host")]),_:1})]),e("li",null,[t(n,{to:"/docs/config/email.html#smtp"},{default:o(()=>[i("SMTP")]),_:1})])]),_,e("ul",null,[e("li",null,[t(n,{to:"/docs/config/database.html"},{default:o(()=>[i("Database")]),_:1})]),e("li",null,[t(n,{to:"/docs/config/internalHttpHost.html"},{default:o(()=>[i("Internal HTTP Host")]),_:1})])]),v,y,e("p",null,[i("Dynamic configs are managed using REST "),t(n,{to:"/docs/api-config/"},{default:o(()=>[i("configuration api")]),_:1}),i(".")]),b])}const T=s(d,[["render",w],["__file","index.html.vue"]]);export{T as default}; diff --git a/assets/index.html-90a99e66.js b/assets/index.html-f201ed52.js similarity index 99% rename from assets/index.html-90a99e66.js rename to assets/index.html-f201ed52.js index 2d4d2a82c..ba99825db 100644 --- a/assets/index.html-90a99e66.js +++ b/assets/index.html-f201ed52.js @@ -1,4 +1,4 @@ -import{_ as c,r as p,o as r,c as l,a as n,b as s,d as e,w as i,e as t}from"./app-77c416fe.js";const u={},d=t('

      Notification

      Configs in this section customize the handling of notification request or generating notifications from RSS feeds. They are all sub-properties of config object notification. Service-agnostic configs are static and service-dependent configs are dynamic.

      RSS Feeds

      NotifyBC can generate broadcast push notifications automatically by polling RSS feeds periodically and detect changes by comparing with an internally maintained history list. The polling frequency, RSS url, RSS item change detection criteria, and message template can be defined in dynamic configs.

      Only first page is retrieved for paginated RSS feeds

      If a RSS feed is paginated, NotifyBC only retrieves the first page rather than auto-fetch subsequent pages. Hence paginated RSS feeds should be sorted descendingly by last modified timestamp. Refresh interval should be adjusted small enough such that all new or updated items are contained in first page.

      ',5),m=n("em",null,"myService",-1),h=n("em",null,"http://my-serivce/rss",-1),b=t(`
      {
      +import{_ as c,r as p,o as r,c as l,a as n,b as s,d as e,w as i,e as t}from"./app-be471a62.js";const u={},d=t('

      Notification

      Configs in this section customize the handling of notification request or generating notifications from RSS feeds. They are all sub-properties of config object notification. Service-agnostic configs are static and service-dependent configs are dynamic.

      RSS Feeds

      NotifyBC can generate broadcast push notifications automatically by polling RSS feeds periodically and detect changes by comparing with an internally maintained history list. The polling frequency, RSS url, RSS item change detection criteria, and message template can be defined in dynamic configs.

      Only first page is retrieved for paginated RSS feeds

      If a RSS feed is paginated, NotifyBC only retrieves the first page rather than auto-fetch subsequent pages. Hence paginated RSS feeds should be sorted descendingly by last modified timestamp. Refresh interval should be adjusted small enough such that all new or updated items are contained in first page.

      ',5),m=n("em",null,"myService",-1),h=n("em",null,"http://my-serivce/rss",-1),b=t(`
      {
         "name": "notification",
         "serviceName": "myService",
         "value": {
      diff --git a/assets/index.html-a3a34a72.js b/assets/index.html-f2c18423.js
      similarity index 99%
      rename from assets/index.html-a3a34a72.js
      rename to assets/index.html-f2c18423.js
      index ed7561a2e..878ad51a6 100644
      --- a/assets/index.html-a3a34a72.js
      +++ b/assets/index.html-f2c18423.js
      @@ -1,4 +1,4 @@
      -import{_ as l,r,o,c as d,a as e,b as s,d as a,e as t}from"./app-77c416fe.js";const p={},u=t(`

      Administrator

      The administrator API provides knowledge factor authentication to identify admin request by access token (aka API token in other literatures) associated with a registered administrator maintained in NotifyBC database. Because knowledge factor authentication is vulnerable to brute-force attack, administrator API based access token authentication is less favorable than admin ip list, client certificate, or OIDC authentication.

      Avoid Administrator API

      Administrator API was created to circumvent an OpenShift limitation - the source ip of a request initiated from an OpenShift pod cannot be exclusively allocated to the pod's project, rather it has to be shared by all OpenShift projects. Therefore it's difficult to impose granular access control based on source ip.

      With the introduction client certificate in v2.4.0, most use cases, if not all, that need Administrator API including the OpenShift use case mentioned above can be addressed by client certificate. Therefore only use Administrator API sparingly as last resort.

      To enable access token authentication,

      1. a super-admin signs up an administrator

        For example,

        curl -X POST "http://localhost:3000/api/administrators" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\\"username\\":\\"Foo\\",\\"email\\":\\"user@example.com\\",\\"password\\":\\"secret\\"}"
        +import{_ as l,r,o,c as d,a as e,b as s,d as a,e as t}from"./app-be471a62.js";const p={},u=t(`

        Administrator

        The administrator API provides knowledge factor authentication to identify admin request by access token (aka API token in other literatures) associated with a registered administrator maintained in NotifyBC database. Because knowledge factor authentication is vulnerable to brute-force attack, administrator API based access token authentication is less favorable than admin ip list, client certificate, or OIDC authentication.

        Avoid Administrator API

        Administrator API was created to circumvent an OpenShift limitation - the source ip of a request initiated from an OpenShift pod cannot be exclusively allocated to the pod's project, rather it has to be shared by all OpenShift projects. Therefore it's difficult to impose granular access control based on source ip.

        With the introduction client certificate in v2.4.0, most use cases, if not all, that need Administrator API including the OpenShift use case mentioned above can be addressed by client certificate. Therefore only use Administrator API sparingly as last resort.

        To enable access token authentication,

        1. a super-admin signs up an administrator

          For example,

          curl -X POST "http://localhost:3000/api/administrators" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\\"username\\":\\"Foo\\",\\"email\\":\\"user@example.com\\",\\"password\\":\\"secret\\"}"
           

          The step can also be completed in web console using add button in Administrators panel.

        2. Either super-admin or the user login to generate an access token

          For example,

          curl -X POST "http://localhost:3000/api/administrators/login" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\\"email\\":\\"user@example.com\\",\\"password\\":\\"secret\\",\\"tokenName\\":\\"myApp\\"}"
           

          The step can also be completed in web console GUI by an anonymous user using login button at top right corner. Access token generated by GUI is valid for 12hrs.

        3. Apply access token to either Authorization header or access_token query parameter to make authenticated requests. For example, to get a list of notifications

          ACCESS_TOKEN=6Nb2ti5QEXIoDBS5FQGWIz4poRFiBCMMYJbYXSGHWuulOuy0GTEuGx2VCEVvbpBK
           
          diff --git a/assets/jmespathFilter.html-a67ec5c7.js b/assets/jmespathFilter.html-f6652c45.js
          similarity index 94%
          rename from assets/jmespathFilter.html-a67ec5c7.js
          rename to assets/jmespathFilter.html-f6652c45.js
          index 561dd3cba..73155628c 100644
          --- a/assets/jmespathFilter.html-a67ec5c7.js
          +++ b/assets/jmespathFilter.html-f6652c45.js
          @@ -1,4 +1,4 @@
          -import{_ as t,o as n,c as e,a as i}from"./app-77c416fe.js";const o={},a=i("pre",null,[i("code",null,`  
          a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter +import{_ as t,o as n,c as e,a as i}from"./app-be471a62.js";const o={},a=i("pre",null,[i("code",null,`
          a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
          • simple
            province == 'BC' diff --git a/assets/throttle.html-b33db38d.js b/assets/throttle.html-2d038ede.js similarity index 94% rename from assets/throttle.html-b33db38d.js rename to assets/throttle.html-2d038ede.js index d9e8ea3c0..3e71c8626 100644 --- a/assets/throttle.html-b33db38d.js +++ b/assets/throttle.html-2d038ede.js @@ -1 +1 @@ -import{_ as r,r as i,o as s,c as l,a as t,b as e,d as n}from"./app-77c416fe.js";const a={},c={href:"https://github.com/SGrondin/bottleneck",target:"_blank",rel:"noopener noreferrer"},d={href:"https://github.com/luin/ioredis",target:"_blank",rel:"noopener noreferrer"},_=t("em",null,"NotifyBC",-1),h=t("em",null,"jobExpiration",-1),f=t("em",null,"expiration",-1),u={href:"https://github.com/bcgov/NotifyBC/blob/main/src/config.ts",target:"_blank",rel:"noopener noreferrer"},m=t("p",null,[e("When "),t("em",null,"NotifyBC"),e(" is deployed to Kubernetes using Helm, by default throttle, if enabled, uses Redis Sentinel therefore rate limit applies to whole cluster.")],-1);function p(b,g){const o=i("ExternalLinkIcon");return s(),l("div",null,[t("p",null,[e("Throttle is implemented using "),t("a",c,[e("Bottleneck"),n(o)]),e(" and "),t("a",d,[e("ioredis"),n(o)]),e(". See their documentations for more configurations. The only deviation made by "),_,e(" is using "),h,e(" to denote Bottleneck "),f,e(" job option with a default value of 2min as defined in "),t("a",u,[e("config.ts"),n(o)]),e(".")]),m])}const x=r(a,[["render",p],["__file","throttle.html.vue"]]);export{x as default}; +import{_ as r,r as i,o as s,c as l,a as t,b as e,d as n}from"./app-be471a62.js";const a={},c={href:"https://github.com/SGrondin/bottleneck",target:"_blank",rel:"noopener noreferrer"},d={href:"https://github.com/luin/ioredis",target:"_blank",rel:"noopener noreferrer"},_=t("em",null,"NotifyBC",-1),h=t("em",null,"jobExpiration",-1),f=t("em",null,"expiration",-1),u={href:"https://github.com/bcgov/NotifyBC/blob/main/src/config.ts",target:"_blank",rel:"noopener noreferrer"},m=t("p",null,[e("When "),t("em",null,"NotifyBC"),e(" is deployed to Kubernetes using Helm, by default throttle, if enabled, uses Redis Sentinel therefore rate limit applies to whole cluster.")],-1);function p(b,g){const o=i("ExternalLinkIcon");return s(),l("div",null,[t("p",null,[e("Throttle is implemented using "),t("a",c,[e("Bottleneck"),n(o)]),e(" and "),t("a",d,[e("ioredis"),n(o)]),e(". See their documentations for more configurations. The only deviation made by "),_,e(" is using "),h,e(" to denote Bottleneck "),f,e(" job option with a default value of 2min as defined in "),t("a",u,[e("config.ts"),n(o)]),e(".")]),m])}const x=r(a,[["render",p],["__file","throttle.html.vue"]]);export{x as default}; diff --git a/assets/whereQueryParam.html-e8cad04b.js b/assets/whereQueryParam.html-6115caa0.js similarity index 85% rename from assets/whereQueryParam.html-e8cad04b.js rename to assets/whereQueryParam.html-6115caa0.js index 054b1d3f1..db021a37a 100644 --- a/assets/whereQueryParam.html-e8cad04b.js +++ b/assets/whereQueryParam.html-6115caa0.js @@ -1,4 +1,4 @@ -import{_ as n,r as o,o as a,c,a as e,b as r,d as s}from"./app-77c416fe.js";const l={},m=e("em",null,"where",-1),d={href:"https://www.mongodb.com/docs/manual/tutorial/query-documents/",target:"_blank",rel:"noopener noreferrer"},u=e("pre",null,[e("code",null,`- parameter name: where +import{_ as n,r as o,o as a,c,a as e,b as r,d as s}from"./app-be471a62.js";const l={},m=e("em",null,"where",-1),d={href:"https://www.mongodb.com/docs/manual/tutorial/query-documents/",target:"_blank",rel:"noopener noreferrer"},u=e("pre",null,[e("code",null,`- parameter name: where - required: false - parameter type: query - data type: object diff --git a/assets/whereQueryParamCode.html-feffff85.js b/assets/whereQueryParamCode.html-ec3065e2.js similarity index 80% rename from assets/whereQueryParamCode.html-feffff85.js rename to assets/whereQueryParamCode.html-ec3065e2.js index 2c7953f2b..835db8cf8 100644 --- a/assets/whereQueryParamCode.html-feffff85.js +++ b/assets/whereQueryParamCode.html-ec3065e2.js @@ -1 +1 @@ -import{_ as e,o as t,c as r,a as o}from"./app-77c416fe.js";const a={},c=o("p",null,"?where=%7B%22created%22%3A%7B%22%24gte%22%3A%222023-01-01%22%2C%22%24lt%22%3A%222024-01-01%22%7D%7D",-1),_=[c];function s(n,l){return t(),r("div",null,_)}const h=e(a,[["render",s],["__file","whereQueryParamCode.html.vue"]]);export{h as default}; +import{_ as e,o as t,c as r,a as o}from"./app-be471a62.js";const a={},c=o("p",null,"?where=%7B%22created%22%3A%7B%22%24gte%22%3A%222023-01-01%22%2C%22%24lt%22%3A%222024-01-01%22%7D%7D",-1),_=[c];function s(n,l){return t(),r("div",null,_)}const h=e(a,[["render",s],["__file","whereQueryParamCode.html.vue"]]);export{h as default}; diff --git a/assets/whereQueryParamExample.html-810bff15.js b/assets/whereQueryParamExample.html-8a4f7134.js similarity index 86% rename from assets/whereQueryParamExample.html-810bff15.js rename to assets/whereQueryParamExample.html-8a4f7134.js index f9a59e130..d046f329a 100644 --- a/assets/whereQueryParamExample.html-810bff15.js +++ b/assets/whereQueryParamExample.html-8a4f7134.js @@ -1,4 +1,4 @@ -import{_ as n,o as r,c as a,a as e,b as t}from"./app-77c416fe.js";const o={},c=e("p",null,[t("the value of the "),e("em",null,"where"),t(" query parameter is URL-encoded stringified JSON object")],-1),l=e("pre",null,[e("code",null,`\`\`\`json +import{_ as n,o as r,c as a,a as e,b as t}from"./app-be471a62.js";const o={},c=e("p",null,[t("the value of the "),e("em",null,"where"),t(" query parameter is URL-encoded stringified JSON object")],-1),l=e("pre",null,[e("code",null,`\`\`\`json { "created": { "$gte": "2023-01-01", diff --git a/docs/acknowledgments/index.html b/docs/acknowledgments/index.html index e89dd1394..965e1680c 100644 --- a/docs/acknowledgments/index.html +++ b/docs/acknowledgments/index.html @@ -24,10 +24,10 @@ Acknowledgments | NotifyBC - + - + diff --git a/docs/api-administrator/index.html b/docs/api-administrator/index.html index 5890e1e27..fc131650c 100644 --- a/docs/api-administrator/index.html +++ b/docs/api-administrator/index.html @@ -24,7 +24,7 @@ Administrator | NotifyBC - +

            Administrator

            The administrator API provides knowledge factor authentication to identify admin request by access token (aka API token in other literatures) associated with a registered administrator maintained in NotifyBC database. Because knowledge factor authentication is vulnerable to brute-force attack, administrator API based access token authentication is less favorable than admin ip list, client certificate, or OIDC authentication.

            Avoid Administrator API

            Administrator API was created to circumvent an OpenShift limitation - the source ip of a request initiated from an OpenShift pod cannot be exclusively allocated to the pod's project, rather it has to be shared by all OpenShift projects. Therefore it's difficult to impose granular access control based on source ip.

            With the introduction client certificate in v2.4.0, most use cases, if not all, that need Administrator API including the OpenShift use case mentioned above can be addressed by client certificate. Therefore only use Administrator API sparingly as last resort.

            To enable access token authentication,

            1. a super-admin signs up an administrator

              For example,

              curl -X POST "http://localhost:3000/api/administrators" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\"username\":\"Foo\",\"email\":\"user@example.com\",\"password\":\"secret\"}"
              @@ -137,6 +137,6 @@
                 }
               }
               
        - + diff --git a/docs/api-bounce/index.html b/docs/api-bounce/index.html index 7ff5c5171..3ddc9c6ca 100644 --- a/docs/api-bounce/index.html +++ b/docs/api-bounce/index.html @@ -24,10 +24,10 @@ Bounce | NotifyBC - +

        Bounce

        Bounce handling involves recording bounce messages into bounce records, which are implemented using this bounce API and model. Administrator can view bounce records in web console or through API explorer. Bounce record is for internal use and should be read-only under normal circumstances.

        Model Schema

        The API operates on following data model fields:

        NameAttributes

        channel

        name of the delivery channel. Valid values: email, sms.

        typestring
        requiredtrue

        userChannelId

        user's delivery channel id, for example, email address.
        typestring
        requiredtrue

        hardBounceCount

        number of hard bounces recorded so far

        typeinteger
        requiredtrue

        state

        bounce record state. Valid values: active, deleted.

        typestring
        requiredtrue

        bounceMessages

        array of recorded bounce messages. Each element is an object containing the date bounce message was received and the message itself.

        typearray
        requiredfalse

        latestNotificationStarted

        latest notification started date.

        typedate
        requiredfalse

        latestNotificationEnded

        latest notification ended date.

        typedate
        requiredfalse

        created

        date and time bounce record was created

        typedate
        auto-generatedtrue

        updated

        date and time of bounce record was last updated

        typedate
        auto-generatedtrue

        id

        config id

        typestring, format depends on db
        auto-generatedtrue
        - + diff --git a/docs/api-config/index.html b/docs/api-config/index.html index e28000394..5cd2c4cbe 100644 --- a/docs/api-config/index.html +++ b/docs/api-config/index.html @@ -24,7 +24,7 @@ Configuration | NotifyBC - +

        Configuration

        The configuration API, accessible by only super-admin requests, is used to define dynamic configurations. Dynamic configuration is needed in situations like

        • RSA key pair generated automatically at boot time if not present
        • service-specific subscription confirmation request message template

        Model Schema

        The API operates on following configuration data model fields:

        NameAttributes

        id

        config id

        typestring, format depends on db
        auto-generatedtrue

        name

        config name

        typestring
        requiredtrue

        value

        config value.
        typeobject
        requiredtrue

        serviceName

        name of the service the config applicable to

        typestring
        requiredfalse

        Get Configurations

        GET /configurations
        @@ -61,6 +61,6 @@
         
      2. Delete a Configuration

        DELETE /configurations/{id}
         
        • permissions required, one of

          • super admin
          • admin
        • inputs

          • configuration id
            • parameter name: id
            • required: true
            • parameter type: path
            • data type: string
        • outcome

          For admin request, delete the config item requested; forbidden for user request

        Replace a Configuration

        PUT /configurations/{id}
         

        This API is intended to be only used by admin web console to modify a configuration.

        • permissions required, one of

          • super admin
          • admin
        • inputs

          • configuration id
            • parameter name: id
            • required: true
            • parameter type: path
            • data type: string
          • configuration data
            • parameter name: data
            • required: true
            • parameter type: body
            • data type: object
        • outcome

          For admin requests, replace configuration identified by id with parameter data and save to database.

      - + diff --git a/docs/api-notification/index.html b/docs/api-notification/index.html index f9867a456..63062581b 100644 --- a/docs/api-notification/index.html +++ b/docs/api-notification/index.html @@ -24,7 +24,7 @@ Notification | NotifyBC - +

      Notification

      The notification API encapsulates the backend workflow of staging and dispatching a message to targeted user after receiving the message from event source.

      Depending on whether an API call comes from user browser as a user request or from an authorized server application as an admin request, NotifyBC applies different permissions. Admin request allows full CRUD operations. An authenticated user request, on the other hand, are only allowed to get a list of in-app pull notifications targeted to the current user and changing the state of the notifications. An unauthenticated user request can not access any API.

      When a notification is created by the event source server application, the message is saved to database prior to responding to API caller. In addition, for push notification, the message is delivered immediately, i.e. the API call is synchronous. For in-app pull notification, the message, which by default is in state new, can be retrieved later on by browser user request. A user request can only get the list of in-app messages targeted to the current user. A user request can then change the message state to read or deleted depending on user action. A deleted message cannot be retrieved subsequently by user requests, but the state can be updated given the correct id.

      Deleted message is still kept in database.

      NotifyBC provides API for deleting a notification. For the purpose of auditing and recovery, this API only marks the state field as deleted rather than deleting the record from database.

      undo in-app notification deletion within a session

      Because "deleted" message is still kept in database, you can implement undo feature for in-app notification as long as the message id is retained prior to deletion within the current session. To undo, call update API to set desired state.

      In-app pull notification also supports message expiration by setting a date in field validTill. An expired message cannot be retrieved by user requests.

      A message, regardless of push or pull, can be unicast or broadcast. A unicast message is intended for an individual user whereas a broadcast message is intended for all confirmed subscribers of a service. A unicast message must have field userChannelId populated. The value of userChannelId is channel dependent. In the case of email for example, this would be user's email address. A broadcast message must set isBroadcast to true and leave userChannelId empty.

      Why field isBroadcast?

      Unicast and broadcast message can be distinguished by whether field userChannelId is empty or not alone. So why the extra field isBroadcast? This is in order to prevent inadvertent marking a unicast message broadcast by omitting userChannelId or populating it with empty value. The precaution is necessary because in-app notifications may contain personalized and confidential information.

      NotifyBC ensures the state of an in-app broadcast message is isolated by user, so that for example, a message read by one user is still new to another user. To achieve this, NotifyBC maintains two internal fields of array type - readBy and deletedBy. When a user request updates the state field of an in-app broadcast message to read or deleted, instead of altering the state field, NotifyBC appends the current user to readBy or deletedBy list. When user request retrieving in-app messages, the state field of the broadcast message in HTTP response is updated based on whether the user exists in field deletedBy and readBy. If existing in both fields, deletedBy takes precedence (the message therefore is not returned). The record in database, meanwhile, is unchanged. Neither field deletedBy nor readBy is visible to user request.

      Model Schema

      The API operates on following notification data model fields:

      NameAttributes

      id

      notification id

      typestring, format depends on db
      auto-generatedtrue

      serviceName

      name of the service

      typestring
      requiredtrue

      channel

      name of the delivery channel. Valid values: inApp, email, sms.

      typestring
      requiredtrue
      defaultinApp

      userChannelId

      user's delivery channel id, for example, email address. For unicast inApp notification, this is authenticated user id. When sending unicast push notification, either userChannelId or userId is required.

      typestring
      requiredfalse

      userId

      authenticated user id. When sending unicast push notification, either userChannelId or userId is required.

      typestring
      requiredfalse

      state

      state of notification. Valid values: new, read (inApp only), deleted (inApp only), sent (push only) or error. For inApp broadcast notification, if the user has read or deleted the message, the value of this field retrieved by admin request will still be new. The state for the user is tracked in fields readBy and deletedBy in such case. For user request, the value contains correct state.

      typestring
      requiredtrue
      defaultnew

      created

      date and time of creation

      typedate
      auto-generatedtrue

      updated

      date and time of last update

      typedate
      auto-generatedtrue

      isBroadcast

      whether it's a broadcast message. A broadcast message should omit userChannelId and userId, in addition to setting isBroadcast to true

      typeboolean
      requiredfalse
      defaultfalse

      skipSubscriptionConfirmationCheck

      When sending unicast push notification, whether or not to verify if the recipient has a confirmed subscription. This field allows subscription information be kept elsewhere and NotifyBC be used as a unicast push notification gateway only.

      typeboolean
      requiredfalse
      defaultfalse

      validTill

      expiration date-time of the message. Applicable to inApp notification only.

      typedate
      requiredfalse

      invalidBefore

      date-time in the future after which the notification can be dispatched.

      typedate
      requiredfalse

      message

      an object whose child fields are channel dependent:
      • for inApp, NotifyBC doesn't have any restriction as long as web application can handle the message. subject and body are common examples.
      • for email: from, subject, textBody, htmlBody
        • type: string
        • these are email template fields.
      • for sms: textBody
        • type: string
        • sms message template.
      Mail merge is performed on email and sms message templates.
      typeobject
      requiredtrue

      httpHost

      This field is used to replace token {http_host} in push notification message template during mail merge and overrides config httpHost.

      typestring
      requiredfalse
      default<http protocol, host and port of current request> for push notification

      asyncBroadcastPushNotification

      this field determines if the API call to create an immediate (i.e. not future-dated) broadcast push notification is asynchronous or not. If omitted, the API call is synchronous, i.e. the API call blocks until notifications to all subscribers have been dispatched. If set, valid values and corresponding behaviors are
      • true - async without callback
      • false - sync
      • a string containing callback url - async with a POST call to the supplied callback url upon completion
      When posting to a service with large number of subscribers, it is highly recommended to set the API call to asynchronous, i.e. setting the value to true or supplying a callback.
      typestring or boolean
      requiredfalse
      defaultfalse

      data

      the event that triggers the notification, for example, a RSS feed item when the notification is generated automatically by RSS cron job. Field data serves two purposes
      • to replace dynamic tokens in message template fields
      • to match against filter defined in subscription field broadcastPushNotificationFilter, if supplied, for broadcast push notifications to determine if the notification should be delivered to the subscriber
      typeobject
      requiredfalse

      broadcastPushNotificationSubscriptionFilter

      a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
      • simple
        province == 'BC'
      • calling jmespath's built-in functions
        contains(province,'B')
      • calling custom filter functions
        contains_ci(province,'b')
      • compound
        (contains(province,'BC') || contains_ci(province,'b')) && city == 'Victoria'
      All of above filters will match data object {"province": "BC", "city": "Victoria"}
      typestring
      requiredfalse

      readBy

      this is an internal field to track the list of users who have read an inApp broadcast message. It's not visible to a user request.

      typearray
      requiredfalse
      auto-generatedtrue

      deletedBy

      this is an internal field to track the list of users who have marked an inApp broadcast message as deleted. It's not visible to a user request.

      typearray
      requiredfalse
      auto-generatedtrue

      dispatch

      this is an internal field to track the broadcast push notification dispatch outcome. It consists of up to four arrays

      • failed - a list of objects containing subscription IDs and error of failed dispatching
      • successful - a list of strings containing subscription IDs of successful dispatching
      • skipped - a list of strings containing subscription IDs of skipped dispatching
      • candidates - a list of strings containing IDs of confirmed subscriptions to the service. Dispatching to a subscription is subject to filtering.
      typeobject
      requiredfalse
      auto-generatedtrue

      Get Notifications

      GET /notifications
      @@ -87,6 +87,6 @@
       

      This API is mainly used for updating an inApp notification.

      • permissions required, one of

        • super admin
        • admin
        • authenticated user
      • inputs

        • notification id
          • parameter name: id
          • required: true
          • parameter type: path
          • data type: string
        • an object containing fields to be updated.
          • parameter name: data
          • required: true
          • parameter type: body
          • data type: object
      • outcome

        • for user requests, NotifyBC performs following actions in sequence
          1. for unicast notification, if the notification is not targeted to current user, error is returned
          2. all fields except for state are discarded from the input
          3. for broadcast notification, current user id in appended to array readBy or deletedBy, depending on whether state is read or deleted, unless the user id is already in the array. The state field itself is then discarded
          4. the notification identified by id is merged with the updates and saved to database
          5. HTTP response code 204 is returned, unless there is error.
        • admin requests are allowed to update any field

      Delete a Notification

      This API is mainly used for marking an inApp notification deleted. It has the same effect as updating a notification with state set to deleted.

      DELETE /notifications/{id}
       
      • permissions required, one of
        • super admin
        • admin
        • authenticated user
      • inputs
        • notification id
          • parameter name: id
          • required: true
          • parameter type: path
          • data type: string
      • outcome: same as the outcome of Update a Notification with state set to deleted.

      Replace a Notification

      PUT /notifications/{id}
       

      This API is intended to be only used by admin web console to modify a notification in new state. Notifications in such state are typically future-dated or of channel in-app.

      • permissions required, one of

        • super admin
        • admin
      • inputs

        • notification id
          • parameter name: id
          • required: true
          • parameter type: path
          • data type: string
        • notification data
          • parameter name: data
          • required: true
          • parameter type: body
          • data type: object
      • outcome

        NotifyBC process the request same way as Create/Send Notifications except that notification data is saved with id supplied in the parameter, replacing existing one.

      - + diff --git a/docs/api-overview/index.html b/docs/api-overview/index.html index c265e758f..b077c8a5c 100644 --- a/docs/api-overview/index.html +++ b/docs/api-overview/index.html @@ -24,10 +24,10 @@ API Overview | NotifyBC - +

      API Overview

      NotifyBC's core function is implemented by two models - subscription and notification. Other models - configuration, administrator and bounces etc, are for administrative purposes. A model determines the underlying database schema and the API. The APIs displayed in the web console (by default http://localhost:3000) and API explorer are also grouped by models. Click on a model in API explorer, say notification, to explore the operations on that model. Model specific APIs are available here:

      - + diff --git a/docs/api-subscription/index.html b/docs/api-subscription/index.html index 62db3c311..79f7c4865 100644 --- a/docs/api-subscription/index.html +++ b/docs/api-subscription/index.html @@ -24,7 +24,7 @@ Subscription | NotifyBC - +

      Subscription

      The subscription API encapsulates the backend workflow of user subscription and un-subscription of push notification service. Depending on whether a API call comes from user browser as a user request or from an authorized server as an admin request, NotifyBC applies different validation rules. For user requests, the notification channel entered by user is unconfirmed. A confirmation code will be associated with this request. The confirmation code can be created in one of two ways:

      • by NotifyBC based on channel dependent subscription.confirmationRequest.<channel>.confirmationCodeRegex config.
      • by a trusted third party. This trusted third party encrypts the confirmation code using the public RSA key of the NotifyBC instance (see more about RSA Key Config) and pass the encrypted confirmation code to NotifyBC via user browser in the same subscription request. NotifyBC then decrypts to obtain the confirmation code. This method allows user subscribe to multiple notification services provided by NotifyBC instances in different trust domains (i.e. service providers) and only have to confirm the subscription channel once during one browser session. In such case only one NotifyBC instance should be chosen to deliver confirmation request to user.

      Equipped with the confirmation code and a message template, NotifyBC can now send out confirmation request to unconfirmed subscription channel. At a minimum this confirmation request should contain the confirmation code. When user receives the message, he/she echos the confirmation code back to a NotifyBC provided API to verify against saved record. If match, the state of the subscription request is changed to confirmed.

      For admin requests, NotifyBC can still perform the above confirmation process. But admin request has full CRUD privilege, including set the subscription state to confirmed, bypassing the confirmation process.

      The workflow of user subscribing to notification services offered by a single service provider is illustrated by sequence diagram below. In this case, the confirmation code is generated by NotifyBC. single service provider subscription

      In the case user subscribing to notifications offered by different service providers in separate trust domains, the confirmation code is generated by a third-party server app trusted by all NotifyBC instances. Following sequence diagram shows the workflow. The diagram indicates NotifyBC API Server 2 is chosen to send confirmation request.

      multi service provider subscription

      Model Schema

      The API operates on following subscription data model fields:

      NameAttributes

      serviceName

      name of the service. Avoid prefixing the name with underscore (_), or it may conflict with internal implementation.

      typestring
      requiredtrue

      channel

      name of the delivery channel. Valid values: email and sms. Notice inApp is invalid as in-app notification doesn't need subscription.

      typestring
      requiredtrue
      defaultemail

      userChannelId

      user's delivery channel id, for example, email address

      typestring
      requiredtrue

      id

      subscription id

      typestring, format depends on db
      requiredfalse
      auto-generatedtrue

      state

      state of subscription. Valid values: unconfirmed, confirmed, deleted

      typestring
      requiredfalse
      defaultunconfirmed

      userId

      user id. Auto-populated for authenticated user requests.

      typestring
      requiredfalse

      created

      date and time of creation

      typedate
      requiredfalse
      auto-generatedtrue

      updated

      date and time of last update

      typedate
      requiredfalse
      auto-generatedtrue

      confirmationRequest

      an object containing these child fields
      • confirmationCodeRegex
        • type: string
        • regular expression used to generate confirmation code
      • confirmationCodeEncrypted
        • type: string
        • encrypted confirmation code
      • sendRequest
        • type: boolean
        • whether to send confirmation request
      • from, subject, textBody, htmlBody
        • type: string
        • these are email template fields used for sending email confirmation request. If confirmationRequest.sendRequest is true and channel is email, then these fields should be supplied in order to send confirmation email.
      typeobject
      requiredtrue for user request with encrypted confirmation code; false otherwise

      broadcastPushNotificationFilter

      a string conforming to jmespath filter expressions syntax after the question mark (?). The filter is matched against the data field of the subscription. Examples of filter
      • simple
        province == 'BC'
      • calling jmespath's built-in functions
        contains(province,'B')
      • calling custom filter functions
        contains_ci(province,'b')
      • compound
        (contains(province,'BC') || contains_ci(province,'b')) && city == 'Victoria'
      All of above filters will match data object {"province": "BC", "city": "Victoria"}
      typestring
      requiredfalse

      data

      An object used by

      data object can only be populated by non-anonymous requests.

      typeobject
      requiredfalse

      unsubscriptionCode

      generated randomly according to RegEx config anonymousUnsubscription.code.regex during anonymous subscription if config anonymousUnsubscription.code.required is set to true

      typestring
      requiredfalse
      auto-generatedtrue

      unsubscribedAdditionalServices

      generated if parameter additionalServices is supplied in unsubscription request. Contains 2 sub-fields: ids and names, each being a list identifying the additional unsubscribed subscriptions.

      typeobject
      requiredfalse
      auto-generatedtrue

      Get Subscriptions

      GET /subscriptions
      @@ -110,6 +110,6 @@
       

      This API allows an anonymous subscriber to undo an unsubscription.

      • inputs

        • subscription id
          • parameter name: id
          • required: true
          • parameter type: path
          • data type: string
        • unsubscription code
          • parameter name: unsubscriptionCode
          • required: false
          • parameter type: query
          • data type: string
      • outcome

        NotifyBC performs following actions in sequence

        1. the subscription identified by id is retrieved
        2. for user request,
        • if request is anonymous, and server is configured to require unsubscription code, the input unsubscription code is matched against the unsubscriptionCode field. Request is rejected if not match
        • if request is authenticated, request is rejected
        • if the subscription state is not deleted, request is rejected
        1. the field state is set to confirmed for the subscription identified by id as well as additional subscriptions identified in field unsubscribedAdditionalServices, if populated
        2. field unsubscribedAdditionalServices is removed if populated
        3. returns either the message or redirect as configured in anonymousUndoUnsubscription
      • example

        To allow an anonymous subscriber to undo unsubscription, provide link token {unsubscription_reversion_url} in unsubscription acknowledgement notification, which is by default set. When sending notification, mail merge is performed on this token resolving to the API url and parameters.

      Get all services with confirmed subscribers

      GET /subscriptions/services
       

      This API is designed to facilitate implementing autocomplete for admin web console.

      • permissions required, one of
        • super admin
        • admin
      • inputs - none
      • outcome
        • for admin requests, returns an array of unique service names with confirmed subscribers
        • forbidden for non-admin requests

      Replace a Subscription

      PUT /subscriptions/{id}
       

      This API is intended to be only used by admin web console to modify a subscription without triggering any confirmation or acknowledgement notification.

      • permissions required, one of
        • super admin
        • admin
      • permissions required, one of
        • super admin
        • admin
        • authenticated user
      • inputs
        • subscription id
          • parameter name: id
          • required: true
          • parameter type: path
          • data type: string
        • subscription data
          • parameter name: data
          • required: true
          • parameter type: body
          • data type: object
      • outcome
        • for admin requests, replace subscription identified by id with parameter data and save to database. No notification is sent.
        • forbidden for non-admin requests
      - + diff --git a/docs/benchmarks/index.html b/docs/benchmarks/index.html index f5012170f..833ca08c5 100644 --- a/docs/benchmarks/index.html +++ b/docs/benchmarks/index.html @@ -24,7 +24,7 @@ Benchmarks | NotifyBC - +

      Benchmarks

      tl;dr

      A NotifyBC server node can deliver 1 million emails in as little as 1 hour to a SMTP server node. SMTP server node's disk I/O is the bottleneck in such case. Throughput can be improved through horizontal scaling.

      When NotifyBC is used to deliver broadcast push notifications to a large number of subscribers, probably the most important benchmark is throughput. The benchmark is especially critical if a latency cap is desired. To facilitate capacity planning, load testing on the email channel has been conducted. The test environment, procedure, results and performance tuning advices are provided hereafter.

      Environment

      Hardware

      Two computers, connected by 1Gbps LAN, are used to host

      • NotifyBC
        • Mac Mini Late 2012 model
        • Intel core i7-3615QM
        • 16GB RAM
        • 2TB HDD
      • SMTP and mail delivery
        • Lenovo ThinkCentre M Series 2015 model
        • Intel core i5-3470
        • 8GB RAM
        • 256GB SSD

      Software Stack

      The test was performed in August 2017. Unless otherwise specified, the versions of all other software were reasonably up-to-date at the time of testing.

      • NotifyBC

        • MacOS Sierra Version 10.12.6
        • Virtualbox VM with 8vCPU, 10GB RAM, created using miniShift v1.3.1+f4900b07
        • OpenShift 1.5.1+7b451fc with metrics
        • default NotifyBC OpenShift installation, which contains following relevant pods
          • 1 mongodb pod with 1 core, 1GiB RAM limit
          • a variable number of Node.js app pods each with 1 core, 1GiB RAM limit. The number varies by test runs as indicated in result.
      • SMTP and mail delivery

        • Windows 7 host
        • Virtualbox VM with 4 vCPU, 3.5GB RAM, running Windows Server 2012
        • added SMTP Server feature
        • in SMTP Server properties dialog box, uncheck all of following boxes in Messages tab
          • Limit message size to (KB)
          • Limit session size to (KB)
          • Limit number of messages per connection to
          • Limit number of recipients per message to

      Procedure

      1. update or create file /src/config.local.js through configMap. Add sections for SMTP server and a custom filter function

        var _ = require('lodash');
        @@ -74,6 +74,6 @@
         -h, --help                                         display this help
         

        The generated subscriptions contain a filter, hence all load testing results below included time spent on filtering.

      2. launch load testing using script curl-ntf.shopen in new window, which takes following optional parameters

        dist/utils/load-testing/curl-ntf.sh <apiUrlPrefix> <serviceName> <senderEmail>
         

        The script will print start time and the time taken to dispatch the notification.

      Results

      email counttime taken (min)throughput (#/min)app pod countnotes on bottleneck
      1,000,00071.513,9861app pod cpu capped
      100,0005.817,2412smtp vm disk queue length hits 1 frequently
      1,000,0005717,5442smtp vm disk queue length hits 1 frequently
      1,000,00057.817,3013smtp vm disk queue length hits 1 frequently

      Test runs using other software or configurations described below have also been conducted. Because throughput is significantly lower, results are not shown

      • using Linux sendmail SMTP. The throughput of a 4-vCPU Linux VM is about the same as a 1-vCPU Windows SMTP server. Bottleneck in such case is the CPU of SMTP server.
      • Reducing NotifyBC app pod's resource limit to 100 millicore CPU and 512MiB RAM. Even when scaled up pod count to 15, throughput is still about 1/3 of a 1-core pod.

      Here is a sample email saved onto the mail drop folder of SMTP server.

      Comparison to Other Benchmarks

      According to Baseline Performance for SMTPopen in new window published on Microsoft Technet in 2005, Windows SMTP server has a max throughput of 142 emails/s. However this NotifyBC load test yields a max throughput of 292 emails/s. The discrepancy may be attributed to following factors

      1. Email size in Microsoft's load test is 50k, as opposed to 1k used in this test
      2. SSD storage is used in this test. It is unlikely the test conducted in 2005 used SSD.

      Advices

      • Avoid using default direct mode in production. Instead use SMTP server. Direct mode doesn't support connection pooling, resulting in port depletion quickly.
      • Enable SMTP poolingopen in new window.
      • Set smtp config maxConnections to a number big enough as long as SMTP server can handle. Test found for Windows SMTP server 50 is a suitable number, beyond which performance gain is insignificant.
      • Set smtp config maxMessages to maximum possible number allowed by your SMTP server, or Infinity if SMTP server imposes no such constraint
      • Avoid setting CPU resource limit too low for NotifyBC app pods.
      • If you have control over the SMTP server,
        • use SSD for its storage
        • create a load balanced cluster if possible, since SMTP server is more likely to be the bottleneck.
      - + diff --git a/docs/bulk-import/index.html b/docs/bulk-import/index.html index 74561102a..738e81c41 100644 --- a/docs/bulk-import/index.html +++ b/docs/bulk-import/index.html @@ -24,7 +24,7 @@ Bulk Import | NotifyBC - +

      Bulk Import

      To migrate subscriptions from other notification systems, you can use mongoimportopen in new window. NotifyBC also provides a utility script to bulk import subscription data from a .csv file. To use the utility, you need

      • Software installed
        • Node.js
        • Git
      • Admin Access to a NotifyBC instance by adding your client ip to the Admin IP List
      • a csv file with header row matching subscription model schema. A sample csv file is providedopen in new window. Compound fields (of object type) should be dot-flattened as shown in the sample for field confirmationRequest.sendRequest

      To run the utility

      git clone https://github.com/bcgov/NotifyBC.git
      @@ -39,6 +39,6 @@
           }
         }
       
      - + diff --git a/docs/conduct/index.html b/docs/conduct/index.html index 2119272eb..e679e47d4 100644 --- a/docs/conduct/index.html +++ b/docs/conduct/index.html @@ -24,10 +24,10 @@ Code of Conduct | NotifyBC - +

      Code of Conduct

      As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

      We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.

      Examples of unacceptable behavior by participants include:

      • The use of sexualized language or imagery
      • Personal attacks
      • Trolling or insulting/derogatory comments
      • Public or private harassment
      • Publishing other's private information, such as physical or electronic addresses, without explicit permission
      • Other unethical or unprofessional conduct

      Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

      By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.

      This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.

      Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting a project maintainer. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident.

      This Code of Conduct is adapted from the Contributor Covenantopen in new window, version 1.3.0, available at http://contributor-covenant.org/version/1/3/0/open in new window

      - + diff --git a/docs/config-adminIpList/index.html b/docs/config-adminIpList/index.html index cf4c30952..8282c5cf8 100644 --- a/docs/config-adminIpList/index.html +++ b/docs/config-adminIpList/index.html @@ -24,7 +24,7 @@ Admin IP List | NotifyBC - +

      Admin IP List

      By design, NotifyBC classifies incoming requests into four types. For a request to be classified as super-admin, the request's source ip must be in admin ip list. By default, the list contains localhost only as defined by adminIps in /src/config.ts

      module.exports = {
      @@ -34,6 +34,6 @@
         adminIps: ['127.0.0.1', '192.168.0.0/24'],
       };
       

      It should be noted that NotifyBC may generate http requests sending to itself. These http requests are expected to be admin requests. If you have created an app cluster such as in Kubernetes, you should add the cluster ip range to adminIps. In Kubernetes, this ip range is a private ip range. For example, in BCGov's OpenShift cluster OCP4, the ip range starts with octet 10.

      - + diff --git a/docs/config-certificates/index.html b/docs/config-certificates/index.html index b2412b652..9d92da88f 100644 --- a/docs/config-certificates/index.html +++ b/docs/config-certificates/index.html @@ -24,7 +24,7 @@ TLS Certificates | NotifyBC - +

      TLS Certificates

      NotifyBC supports HTTPS TLS to achieve end-to-end encryption. In addition, both server and client can be authenticated using certificates.

      To enable HTTPS for server authentication only, you need to create two files

      • server/certs/key.pem - a PEM encoded private key
      • server/certs/cert.pem - a PEM encoded X.509 certificate chain

      Use ConfigMaps on Kubernetes

      Create key.pem and cert.pem as items in ConfigMap notify-bc, then mount the items under /home/node/app/server/certs similar to how config.local.js and middleware.local.js are implemented.

      For self-signed certificate, run

      openssl req -x509 -newkey rsa:4096 -keyout server/certs/key.pem -out server/certs/cert.pem -nodes -days 365 -subj "/CN=NotifyBC"
      @@ -43,6 +43,6 @@
         },
       };
       

      TLS termination has to be passthrough

      For client certification authentication to work, TLS termination of all reverse proxies has to be set to passthrough rather than offload and reload. This means, for example, when NotifyBC is hosted on OpenShift, router tls terminationopen in new window has to be changed from edge to passthrough.

      NotifyBC internal request does not use client certificate

      Requests sent by a NotifyBC node back to the app cluster use admin ip list authentication.

      - + diff --git a/docs/config-cronJobs/index.html b/docs/config-cronJobs/index.html index b8a29296d..9fe6ae778 100644 --- a/docs/config-cronJobs/index.html +++ b/docs/config-cronJobs/index.html @@ -24,7 +24,7 @@ Cron Jobs | NotifyBC - +

      Cron Jobs

      NotifyBC runs several cron jobs described below. These jobs are controlled by sub-properties defined in config object cron. To change config, create the object and properties in file /src/config.local.js.

      By default cron jobs are enabled. In a multi-node deployment, cron jobs should only run on the master node to ensure single execution.

      All cron jobs have a property named timeSpec with the value of a space separated fields conforming to unix crontab formatopen in new window with an optional left-most seconds field. See allowed rangesopen in new window of each field.

      Purge Data

      This cron job purges old notifications, subscriptions and notification bounces. The default frequency of cron job and retention policy are defined by cron.purgeData config object in file /src/config.ts

      module.exports = {
      @@ -90,6 +90,6 @@
         },
       };
       
      - + diff --git a/docs/config-database/index.html b/docs/config-database/index.html index d0b73f319..6b42cc785 100644 --- a/docs/config-database/index.html +++ b/docs/config-database/index.html @@ -24,7 +24,7 @@ Database | NotifyBC - +

      Database

      By default NotifyBC uses in-memory database backed up by folder /server/database/ for local and docker deployment and MongoDB for Kubernetes deployment. To use MongoDB for non-Kubernetes deployment, add file /src/datasources/db.datasource.(local|<env>).(json|js|ts) with MongoDB connection information such as following:

      module.exports = {
      @@ -33,6 +33,6 @@
         pass: process.env.MONGODB_PASSWORD,
       };
       

      See Mongoose connection optionsopen in new window for more configurable properties.

      - + diff --git a/docs/config-email/index.html b/docs/config-email/index.html index 3ae2b53f6..9a3785deb 100644 --- a/docs/config-email/index.html +++ b/docs/config-email/index.html @@ -24,7 +24,7 @@ Email | NotifyBC - + - + diff --git a/docs/config-httpHost/index.html b/docs/config-httpHost/index.html index 7ec88c6c9..b233242b0 100644 --- a/docs/config-httpHost/index.html +++ b/docs/config-httpHost/index.html @@ -24,13 +24,13 @@ HTTP Host | NotifyBC - +

      HTTP Host

      httpHost config sets the fallback http host used by

      • mail merge token substitution
      • internal HTTP requests spawned by NotifyBC

      httpHost can be overridden by other configs or data. For example

      • internalHttpHost config
      • httpHost field in a notification

      There are contexts where there is no alternatives to httpHost. Therefore this config should be defined.

      Define the config, which has no default value, in /src/config.local.js

      module.exports = {
         httpHost: 'http://foo.com',
       };
       
      - + diff --git a/docs/config-internalHttpHost/index.html b/docs/config-internalHttpHost/index.html index d44394979..52410036a 100644 --- a/docs/config-internalHttpHost/index.html +++ b/docs/config-internalHttpHost/index.html @@ -24,13 +24,13 @@ Internal HTTP Host | NotifyBC - +

      Internal HTTP Host

      By default, HTTP requests submitted by NotifyBC back to itself will be sent to httpHost if defined or the host of the incoming HTTP request that spawns such internal requests. But if config internalHttpHost, which has no default value, is defined, for example in file /src/config.local.js

      module.exports = {
         internalHttpHost: 'http://notifybc:3000',
       };
       

      then the HTTP request will be sent to the configured host. An internal request can be generated, for example, as a sub-request of broadcast push notification. internalHttpHost shouldn't be accessible from internet.

      All internal requests are supposed to be admin requests. The purpose of internalHttpHost is to facilitate identifying the internal server ip as admin ip.

      Kubernetes Use Case

      The Kubernetes deployment script sets internalHttpHost to notify-bc-app service url in config map. The source ip in such case would be in a private Kubernetes ip range. You should add this private ip range to admin ip list. The private ip range varies from Kubernetes installation. In BCGov's OCP4 cluster, it starts with octet 10.

      - + diff --git a/docs/config-middleware/index.html b/docs/config-middleware/index.html index 09792290d..d3dc438ef 100644 --- a/docs/config-middleware/index.html +++ b/docs/config-middleware/index.html @@ -24,7 +24,7 @@ Middleware | NotifyBC - + - + diff --git a/docs/config-nodeRoles/index.html b/docs/config-nodeRoles/index.html index 301f1e955..2ed0fcdd3 100644 --- a/docs/config-nodeRoles/index.html +++ b/docs/config-nodeRoles/index.html @@ -24,10 +24,10 @@ Node Roles | NotifyBC - + - + diff --git a/docs/config-notification/index.html b/docs/config-notification/index.html index 82252dd8b..c77a8abda 100644 --- a/docs/config-notification/index.html +++ b/docs/config-notification/index.html @@ -24,7 +24,7 @@ Notification | NotifyBC - +

      Notification

      Configs in this section customize the handling of notification request or generating notifications from RSS feeds. They are all sub-properties of config object notification. Service-agnostic configs are static and service-dependent configs are dynamic.

      RSS Feeds

      NotifyBC can generate broadcast push notifications automatically by polling RSS feeds periodically and detect changes by comparing with an internally maintained history list. The polling frequency, RSS url, RSS item change detection criteria, and message template can be defined in dynamic configs.

      Only first page is retrieved for paginated RSS feeds

      If a RSS feed is paginated, NotifyBC only retrieves the first page rather than auto-fetch subsequent pages. Hence paginated RSS feeds should be sorted descendingly by last modified timestamp. Refresh interval should be adjusted small enough such that all new or updated items are contained in first page.

      For example, to notify subscribers of myService on updates to feed http://my-serivce/rss, create following config item using POST configuration API

      {
      @@ -99,6 +99,6 @@
         }
       }
       

      Setting logSkippedBroadcastPushDispatches to true only has effect when guaranteedBroadcastPushDispatchProcessing is true.

      - + diff --git a/docs/config-oidc/index.html b/docs/config-oidc/index.html index bd0c97271..7661478c5 100644 --- a/docs/config-oidc/index.html +++ b/docs/config-oidc/index.html @@ -24,7 +24,7 @@ OIDC | NotifyBC - +

      OIDC

      NotifyBC currently can only authenticate RSA signed OIDC access token if the token is a JWT. OIDC providers such as Keycloak meet the requirement.

      To enable OIDC authentication strategy, add oidc configuration object to /src/config.local.js. The object supports following properties

      1. discoveryUrl - OIDC discoveryopen in new window url
      2. clientId - OIDC client id
      3. isAdmin - a predicate function to determine if authenticated user is NotifyBC administrator. The function takes the decoded OIDC access token JWT payload as input user object and should return either a boolean or a promise of boolean, i.e. the function can be both sync or async.
      4. isAuthorizedUser - an optional predicate function to determine if authenticated user is an authorized NotifyBC user. If omitted, any authenticated user is authorized NotifyBC user. This function has same signature as isAdmin

      A example of complete OIDC configuration looks like

      module.exports = {
      @@ -44,6 +44,6 @@
         },
       };
       

      In NotifyBC web console and only in the web console, OIDC authentication takes precedence over built-in admin user, meaning if OIDC is configured, the login button goes to OIDC provider rather than the login form.

      There is no default OIDC configuration in /src/config.ts.

      - + diff --git a/docs/config-overview/index.html b/docs/config-overview/index.html index 4617116a1..ee0a6885e 100644 --- a/docs/config-overview/index.html +++ b/docs/config-overview/index.html @@ -24,10 +24,10 @@ Configuration Overview | NotifyBC - +

      Configuration Overview

      Helm Chart Configurations

      The document pages in this section cover NoitfyBC app level configurations only. If your NotifyBC is deployed to Kubernetes using Helm, you can also customize infrastructure level configurations.

      There are two types of configurations - static and dynamic. Static configurations are defined in files or environment variables, requiring restarting NotifyBC to take effect, whereas dynamic configurations are defined in databases and updates take effect immediately.

      Static Configurations

      Most static configurations are specified in file /src/config.ts. If you need to change, instead of updating /src/config.ts file, create local file /src/config.local.js or environment specific file /src/config.<env>.js, which is only included when environment variable NODE_ENV equals <env>. Besides js, ts and json file extensions are also supported. The rest of the documentation assumes the file extension is js. Content in these files are deeply merged in following ascending precedence

      • default file /src/config.ts
      • environment specific file /src/config.<env>.js
      • local file /src/config.local.js

      Run build script whenever changing file in /src

      Every time a file under /src, including config files, is updated, run npm run build before restarting NotifyBC to take effect.

      Following configs should be customized per installation

      In addition, if installing from source code

      Customizing other configs only if needed.

      Dynamic Configurations

      Dynamic configs are managed using REST configuration api.

      Why Dynamic Configs?

      Dynamic configs are needed in cases such as

      • to allow define service-specific configs such as message templates
      • in a multi-node deployment, configs can be generated by one node (typically master) and shared with other nodes
      - + diff --git a/docs/config-reverseProxyIpLists/index.html b/docs/config-reverseProxyIpLists/index.html index ee0cfaef6..54e05b29b 100644 --- a/docs/config-reverseProxyIpLists/index.html +++ b/docs/config-reverseProxyIpLists/index.html @@ -24,7 +24,7 @@ Reverse Proxy IP Lists | NotifyBC - +

      Reverse Proxy IP Lists

      SiteMinder, being a gateway approached SSO solution, expects the backend HTTP access point of the web sites it protests to be firewall restricted, otherwise the SiteMinder injected HTTP headers can be easily spoofed. However, the restriction cannot be easily implemented on PAAS such as OpenShift. To mitigate, two configuration objects are introduced to create an application-level firewall, both are arrays of ip addresses in the format of dot-decimalopen in new window or CIDRopen in new window notation

      • siteMinderReverseProxyIps contains a list of ips or ranges of SiteMinder Web Agents. If set, then the SiteMinder HTTP headers are trusted only if the request is routed from the listed nodes.
      • trustedReverseProxyIps contains a list of ips or ranges of trusted reverse proxies. If NotifyBC is placed behind SiteMinder Web Agents, then trusted reverse proxies should include only those between SiteMinder Web Agents and NotifyBC application. When running on OpenShift, this is usually the OpenShift router. Express.js trust proxyopen in new window is set to this config object.

      By default trustedReverseProxyIps is empty and siteMinderReverseProxyIps contains only localhost as defined in /src/config.ts

      module.exports = {
      @@ -35,6 +35,6 @@
         trustedReverseProxyIps: ['172.17.0.0/16'],
       };
       

      The rule to determine if the incoming request is authenticated by SiteMinder is

      1. obtain the real client ip address by filtering out trusted proxy ips according to Express behind proxiesopen in new window
      2. if the real client ip is contained in siteMinderReverseProxyIps, then the request is from SiteMinder, and its SiteMinder headers are trusted; otherwise, the request is considered as directly from internet, and its SiteMinder headers are ignored.
      - + diff --git a/docs/config-rsaKeys/index.html b/docs/config-rsaKeys/index.html index 3a2d76c69..03038e842 100644 --- a/docs/config-rsaKeys/index.html +++ b/docs/config-rsaKeys/index.html @@ -24,7 +24,7 @@ RSA Keys | NotifyBC - +

      RSA Keys

      When NotifyBC starts up, it checks if an RSA key pair exists in database as dynamic config. If not it will generate the dynamic config and save it to database. This RSA key pair is used to exchange confidential information with third party server applications through user's browser. For an example of use case, see Subscription API. To make it work, send the public key to the third party and have their server app encrypt the data using the public key. To obtain public key, call the REST Configuration API from an admin ip, for example, by running cURL command

      curl -X GET 'http://localhost:3000/api/configurations?filter=%7B%22where%22%3A%20%7B%22name%22%3A%20%22rsa%22%7D%7D'
      @@ -41,6 +41,6 @@
         }
       ]
       

      The public key is the string -----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----

      In a multi-node deployment, when the cluster is first started up, database is empty and rsa key pair doesn't exist. To prevent multiple rsa keys being generated by different nodes, only the master node can generate the rsa key pair. other nodes will wait for the key pair available in database before proceeding with rest bootstrap.

      Expose RSA public key to only trusted party

      Despite of the adjective public, NotifyBC's public key should only be distributed to trusted third party. The trusted third party should only use the public key at server backend. Using the public key in client-side JavaScript poses a security loophole.

      - + diff --git a/docs/config-sms/index.html b/docs/config-sms/index.html index e52214154..6091fe62a 100644 --- a/docs/config-sms/index.html +++ b/docs/config-sms/index.html @@ -24,7 +24,7 @@ SMS | NotifyBC - + - + diff --git a/docs/config-subscription/index.html b/docs/config-subscription/index.html index 8fbddc2a5..39676fe2f 100644 --- a/docs/config-subscription/index.html +++ b/docs/config-subscription/index.html @@ -24,7 +24,7 @@ Subscription | NotifyBC - +

      Subscription

      Configs in this section customize behavior of subscription and unsubscription workflow. They are all sub-properties of config object subscription. This object can be defined as service-agnostic static config as well as service-specific dynamic config, which overrides the static one on a service-by-service basis. Default static config is defined in file /src/config.ts. There is no default dynamic config.

      To customize static config, create the config object subscription in file /src/config.local.js

      module.exports = {
      @@ -169,6 +169,6 @@
         }
       }
       

      You can redirect the message page by defining anonymousUndoUnsubscription.redirectUrl.

      - + diff --git a/docs/config-workerProcessCount/index.html b/docs/config-workerProcessCount/index.html index ae0147184..a5324ae08 100644 --- a/docs/config-workerProcessCount/index.html +++ b/docs/config-workerProcessCount/index.html @@ -24,10 +24,10 @@ Worker Process Count | NotifyBC - +

      Worker Process Count

      When NotifyBC runs on a host with multiple CPUs, by default it creates a cluster of worker processes of which the count matches CPU count. You can override the number with the environment variable NOTIFYBC_WORKER_PROCESS_COUNT.

      A note about worker process count on OpenShift

      It has been observed that on OpenShift Node.js returns incorrect CPU count. The template therefore sets NOTIFYBC_WORKER_PROCESS_COUNT to 1. After all, on OpenShift NotifyBC is expected to be horizontally scaled by pods rather by CPUs.

      - + diff --git a/docs/developer-notes/index.html b/docs/developer-notes/index.html index 5fcf1d256..651ff3446 100644 --- a/docs/developer-notes/index.html +++ b/docs/developer-notes/index.html @@ -24,12 +24,12 @@ Developer Notes | NotifyBC - +

      Developer Notes

      Setup development environment

      Install Visual Studio Code and following extensions:

      • Prettier
      • ESLint
      • Vetur
      • Code Spell Checker
      • Debugger for Chrome

      Multiple run configs have been created to facilitate debugging server, client, test and docs.

      Client certificate authentication doesn't work in client debugger

      Because Vue cli webpack dev server cannot proxy passthrough HTTPS connections, client certificate authentication doesn't work in client debugger. If testing client certificate authentication in web console is needed, run npm run build to generate prod client distribution and launch server debugger on https://localhost:3000

      Automated Testing

      NotifyBC uses Jestopen in new window test framework bundled in NestJS. To launch test, run npm run test:e2e. A Test launch config is provided to debug in VS Code.

      Github Actions runs tests as part of the build. All test scripts should be able to run unattended, headless, quickly and depend only on local resources.

      Writing Test Specs

      Thanks to supertestopen in new window and MongoDB In-Memory Serveropen in new window, test specs can be written to cover nearly end-to-end request processing workflow (only sendMail and sendSMS need to be mocked). This allows test specs to anchor onto business requirements rather than program units such as functions or files, resulting in regression tests that are more resilient to code refactoring. Whenever possible, a test spec should be written to

      • start at a processing phase as early as possible. For example, to test a REST end point, start with the HTTP user request.
      • assert outcome of a processing phase as late and down below as possible - the HTTP response body/code, the database record created, for example.
      • avoid asserting middleware function input/output to facilitate code refactoring.
      • mock email/sms sending function (implemented by default). Inspect the input of the function, or at least assert the function has been called.

      Install Docs Website

      If you want to contribute to NotifyBC docs beyond simple fix ups, run

      cd docs && npm install && npm run dev
       

      If everything goes well, the last line of the output will be

      > VuePress dev server listening at http://localhost:8080/NotifyBC/
       

      You can now browse to the local docs site http://localhost:8080/NotifyBCopen in new window

      Publish Version Checklist

      1. update version in package.json
      2. update version appVersion in helm/Chart.yaml (major/minor only)
      3. update What's new (major/minor only)
      4. create a new Github release
      - + diff --git a/docs/health-check/index.html b/docs/health-check/index.html index f53086853..8f1961ca6 100644 --- a/docs/health-check/index.html +++ b/docs/health-check/index.html @@ -24,7 +24,7 @@ Health Check | NotifyBC - +

      Health Check

      Health status of NotifyBC can be obtained by querying /health API end point. For example

      $ curl -s http://localhost:3000/api/health | jq
      @@ -57,6 +57,6 @@
         }
       }
       

      If overall health status is OK, the HTTP response code is 200, otherwise 503. The response payload shows status of following indicators and health criteria

      1. MongoDB - MongoDB must be reachable
      2. config - There must be at least 2 items in MongoDB configuration collection
      3. Redis - Redis must be reachable if configured

      /health API end point is also reachable in API Explorer of NotifyBC web console.

      - + diff --git a/docs/index.html b/docs/index.html index 859a27668..bed3250b5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -24,10 +24,10 @@ Welcome | NotifyBC - +

      Welcome

      This site aims to be a comprehensive guide to NotifyBC. We’ll cover topics such as getting your instance up and running, interacting with browser or other server components, deployment, and give you some advice on participating in the future development of NotifyBC itself.

      Helpful Hints

      Throughout this guide there are a number of small-but-handy pieces of information that can make using NotifyBC easier, more interesting, and less hazardous. Here’s what to look out for.

      General information

      These are tips and tricks that will help you become a NotifyBC wizard!

      Important information

      These are tidbits you might want to keep in mind.

      Warnings

      Be aware of these messages if you wish to avoid disaster.

      If you come across anything along the way that we haven’t covered, or if you know of a tip you think others would find handy, please file an issueopen in new window and we’ll see about including it in this guide.

      - + diff --git a/docs/installation/index.html b/docs/installation/index.html index 57e98daef..625c44ce0 100644 --- a/docs/installation/index.html +++ b/docs/installation/index.html @@ -24,7 +24,7 @@ Installation | NotifyBC - +

      Installation

      NotifyBC can be installed in 3 ways:

      1. Deploy locally from Source Code
      2. Deploy to Kubernetes
      3. Deploy Docker Container

      For the purpose of evaluation, both source code and docker container will do. For production, the recommendation is one of

      • deploying to Kubernetes
      • setting up a load balanced app cluster from source code build, backed by MongoDB.

      To setup a development environment in order to contribute to NotifyBC, installing from source code is preferred.

      Deploy locally from Source Code

      System Requirements

      • Software
      • Services
        • MongoDB with replica set, required for production
        • A standard SMTP server to deliver outgoing email, required for production if email is enabled.
        • A tcp proxy server such as nginx stream proxyopen in new window if list-unsubscribe by email is needed and NotifyBC server cannot expose port 25 to internet
        • A SMS service provider if needs to enable SMS channel. The supported service providers are
          • Twilio (default)
          • Swift
        • Redis v6, required if email or sms throttling is enabled
        • SiteMinder, if needs SiteMinder authentication
        • An OIDC provider, if needs OIDC authentication
      • Network and Permissions
        • Minimum runtime firewall requirements:
          • outbound to your ISP DNS server
          • outbound to any on port 80 and 443 in order to run build scripts and send SMS messages
          • outbound to any on SMTP port 25 if using direct mail; for SMTP relay, outbound to your configured SMTP server and port only
          • inbound to listening port (3000 by default) from other authorized server ips
          • if NotifyBC instance will handle anonymous subscription from client browser, the listening port should be open to internet either directly or indirectly through a reverse proxy; If NotifyBC instance will only handle SiteMinder authenticated webapp requests, the listening port should NOT be open to internet. Instead, it should only open to SiteMinder web agent reverse proxy.
        • If list-unsubscribe by email is needed, then one of the following must be met
          • NotifyBC can bind to port 25 opening to internet
          • a tcp proxy server of which port 25 is open to internet. This proxy server can reach NotifyBC on a tcp port.

      Installation

      Run following commands

      git clone https://github.com/bcgov/NotifyBC.git
      @@ -142,6 +142,6 @@
       
    4. Deploy Docker Container

      If you have git and Docker installed, you can run following command to deploy NotifyBC Docker container:

      docker run --platform linux/amd64 --rm -dp 3000:3000 ghcr.io/bcgov/notify-bc
       # open http://localhost:3000
       

      If successful, similar output is displayed as in source code installation.

    - + diff --git a/docs/memory-dump/index.html b/docs/memory-dump/index.html index ea02ce647..0cc978bc7 100644 --- a/docs/memory-dump/index.html +++ b/docs/memory-dump/index.html @@ -24,7 +24,7 @@ Memory Dump | NotifyBC - +

    Memory Dump

    To troubleshoot memory related issues, Super-admin can get a memory dump of NotifyBC by querying /memory API end point. For example

    $ curl -s http://localhost:3000/api/memory
    @@ -32,6 +32,6 @@
     

    The output is the file name of the memory dump. The dump file can be loaded by, for example, Chrome DevTools.

    fileName query parameter can be used to specify the file path and name

    $ curl -s http://localhost:3000/api/memory?fileName=/tmp/my.heapsnapshot
     /tmp/my.heapsnapshot
     

    How to get memory dump of a particular node?

    If you call /memory from a client-facing URL end point, which is usually load balanced, the memory dump occurs only on node handling your request. To perform it on the node you want to troubleshoot, in particular the master node, run the command from the node. Make sure 127.0.0.1 is in adminIps.

    - + diff --git a/docs/overview/index.html b/docs/overview/index.html index 8f95d3b42..9f8b40c7a 100644 --- a/docs/overview/index.html +++ b/docs/overview/index.html @@ -24,10 +24,10 @@ Overview | NotifyBC - +

    Overview

    NotifyBC is a general purpose API Server to manage subscriptions and dispatch notifications. It aims to implement some common backend processes of a notification service without imposing any constraints on the UI frontend, nor impeding other server components' functionality. This is achieved by interacting with user browser and other server components through RESTful API and other standard protocols in a loosely coupled way.

    Features

    NotifyBC facilitates both anonymous and authentication-enabled secure webapps implementing notification feature. A NotifyBC server instance supports multiple notification services. A service is a topic of interest that user wants to receive updates. It is used as the partition of notification messages and user subscriptions. A user may subscribe to a service in multiple push delivery channels allowed. A user may subscribe to multiple services. In-app pull notification doesn't require subscription as it's not intrusive to user.

    notification

    • both in-app pull notifications (a.k.a. messages or alerts) and push notifications
    • multiple push notifications delivery channels
      • email
      • sms
    • unicast and broadcast message types
    • future-dated notifications
    • for in-app pull notifications
      • support read and deleted message states
      • message expiration
      • deleted messages are not deleted immediately for auditing and recovery purposes
    • for broadcast push notifications
      • allow both sync and async POST API calls. For async API call, an optional callback url is supported
      • can be auto-generated from RSS feeds
      • allow user to specify filter rules evaluated against data contained in the notification
      • allow sender to specify filter rules evaluated against data contained in the subscription
      • allow application developer to create custom filter functions used by the filter rules mentioned above

    subscription and un-subscription

    • verify the ownership of push notification subscription channel:
      • generates confirmation code based on a regex input
      • send confirmation request to unconfirmed subscription channel
      • verify confirmation code
    • generate random un-subscription code
    • send acknowledgement message after un-subscription for anonymous subscribers
    • bulk unsubscription
    • list-unsubscribe by email
    • track bounces and unsubscribe the recipient from all subscriptions when hard bounce count exceeds threshold
    • sms user can unsubscribe by replying a shortcode keyword with Swift sms provider

    mail merge

    Strings in notification or subscription message that are enclosed between curly braces { } are called tokens, also known as placeholders. Tokens are replaced based on the context of notification or subscription when dispatching the message. To avoid treating a string between curly braces as a token, escape the curly braces with backslash \. For example \{i_am_not_a_token\} is not a token. It will be rendered as {i_am_not_a_token}.

    Tokens whose names are predetermined by NotifyBC are called static tokens; otherwise they are called dynamic tokens.

    static tokens

    NotifyBC recognizes following case-insensitive static tokens. Most of the names are self-explanatory.

    • {subscription_confirmation_url}
    • {subscription_confirmation_code}
    • {service_name}
    • {http_host} - http host in the form http(s): //<host_name>:<port>. The value is obtained from the http request that triggers the message
    • {rest_api_root} - REST API URL path prefix
    • {subscription_id}
    • anonymous unsubscription related tokens
      • {unsubscription_url}
      • {unsubscription_all_url} - url to unsubscribe all services the user has subscribed on this NotifyBC instance
      • {unsubscription_code}
      • {unsubscription_reversion_url}
      • {unsubscription_service_names} - includes {service_name} and additional services user has unsubscribed, prefixed with conditionally pluralized word service.

    dynamic tokens

    Dynamic tokens are replaced with correspondingly named sub-field of data field in the notification or subscription if exist. Qualify token name with notification:: or subscription:: to indicate the source of substitution. If token name is not qualified, then both notification and subscription are checked, with notification taking precedence. Nested and indexed sub-fields are supported.

    Examples

    • {notification::description} is replaced with field data.description of the notification if exist
    • {subscription::gender} is replaced with field data.gender of the subscription if exist
    • {addresses[0].city} is replaced with field data.addresses[0].city of the notification if exist; otherwise is replaced with field data.addresses[0].city of the subscription if exist
    • {nonexistingDataField} is unreplaced if neither notification nor subscription contains data.nonexistingDataField

    As exception, in order to prevent spamming by unconfirmed subscribers, dynamic tokens in subscription confirmation request message and duplicated subscription message are not replaced with subscription data, for example {subscription::...} tokens are left unchanged.

    Notification by RSS feeds relies on dynamic token

    A notification created by RSS feeds relies on dynamic token to supply the context to message template. In this case the data field contains the RSS item.

    Architecture

    Request Types

    NotifyBC, designed to be a microservice, doesn't use full-blown ACL to secure API calls. Instead, it classifies incoming requests into admin and user types. The key difference is while both admin and user can subscribe to notifications, only admin can post notifications.

    Each type has two subtypes based on following criteria

    • super-admin, if the request meets both of the following two requirements

      1. The request carries one of the following two attributes

        • the source ip is in the admin ip list
        • has a client certificate that is signed using NotifyBC server certificate. See Client certificate authentication on how to sign.
      2. The request doesn't contain any of following case insensitive HTTP headers, with the first three being SiteMinder headers

        • sm_universalid
        • sm_user
        • smgov_userdisplayname
        • is_anonymous
    • admin, if the request is not super-admin and meets one of the following criteria

      • has a valid access token associated with an builtin admin user created and logged in using the administrator api, and the request doesn't contain any HTTP headers listed above
      • has a valid OIDC access token containing customizable admin profile attributes

      access token disambiguation

      Here the term access token has been used to refer two different things

      1. the token associated with a builtin admin user
      2. the token generated by OIDC provider.

      To reduce confusion, throughout the documentation the former is called access token and the latter is called OIDC access token.

    • authenticated user, if the request is neither super-admin nor admin, and meets one fo the following criteria

      • contains any of the 3 SiteMinder headers listed above, and comes from either trusted SiteMinder proxy or admin ip list
      • contains a valid OIDC access token
    • anonymous user, if the request doesn't meet any of the above criteria

    The only extra privileges that a super-admin has over admin are that super-admin can perform CRUD operations on configuration, bounce and administrator entities through REST API. In the remaining docs, when no further distinction is necessary, an admin request refers to both super-admin and admin request; a user request refers to both authenticated and anonymous request.

    An admin request carries full authorization whereas user request has limited access. For example, a user request is not allowed to

    • send notification
    • bypass the delivery channel confirmation process when subscribing to a service
    • retrieve push notifications through API (can only receive notification from push notification channel such as email)
    • retrieve in-app notifications that is not targeted to the current user

    The result of an API call to the same end point may differ depending on the request type. For example, the call GET /notifications without a filter will return all notifications to all users for an admin request, but only non-deleted, non-expired in-app notifications for authenticated user request, and forbidden for anonymous user request. Sometimes it is desirable for a request from admin ip list, which would normally be admin request, to be voluntarily downgraded to user request in order to take advantage of predefined filters such as the ones described above. This can be achieved by adding one of the HTTP headers listed above to the request. This is also why admin request is not determined by ip or token alone.

    The way NotifyBC interacts with other components is diagrammed below. architecture diagram

    Authentication Strategies

    API requests to NotifyBC can be either anonymous or authenticated. As alluded in Request Types above, NotifyBC supports following authentication strategies

    1. ip whitelisting
    2. client certificate
    3. access token associated with an builtin admin user
    4. OpenID Connect (OIDC)
    5. CA SiteMinder

    Authentication is performed in above order. Once a request passed an authentication strategy, the rest strategies are skipped. A request that failed all authentication strategies is anonymous.

    The mapping between authentication strategy and request type is

    AdminUser
    Super-adminadminauthenticatedanonymous
    ip whitelisting
    client certifcate
    access token
    OIDC
    SiteMinder

    Which authentication strategy to use?

    Because ip whitelist doesn't expire and client certificate usually has a relatively long expiration period (say one year), they are suitable for long-running unattended server processes such as server-side code of web apps, cron jobs, IOT sensors etc. The server processes have to be trusted because once authenticated, they have full privilege to NotifyBC. Usually the server processes and NotifyBC instance are in the same administrative domain, i.e. managed by the same admin group of an organization.

    By contrast, OIDC and SiteMinder use short-lived tokens or session cookies. Therefore they are only suitable for interactive user sessions.

    Access token associated with an builtin admin user should be avoided whenever possible.

    Here are some common scenarios and recommendations

    • For server-side code of web apps

      • use OIDC if the web app is OIDC enabled and user requests can be proxied to NotifyBC by web app; otherwise
      • use ip whitelisting if obtaining ip is feasible; otherwise
      • use client certificate (requires a little more config than ip whitelisting)
    • For front-end browser-based web apps such as SPAs

      • use OIDC
    • For server apps that send requests spontaneously such as IOT sensors, cron jobs

      • use ip whitelisting if obtaining ip is feasible; otherwise
      • client certificate
    • If NotifyBC is ued by a SiteMinder protected web apps and NotifyBC is also protected by SiteMinder

      • use SiteMinder

    Application Framework

    NotifyBC is created on NestJSopen in new window. Contributors to source code of NotifyBC should be familiar with NestJS. NestJS Docsopen in new window serves a good complement to this documentation.

    - + diff --git a/docs/quickstart/index.html b/docs/quickstart/index.html index e00b41a98..867731c0c 100644 --- a/docs/quickstart/index.html +++ b/docs/quickstart/index.html @@ -24,7 +24,7 @@ Quick Start | NotifyBC - + - + diff --git a/docs/shared/filterQueryParam.html b/docs/shared/filterQueryParam.html index 92653790e..6d65a7fac 100644 --- a/docs/shared/filterQueryParam.html +++ b/docs/shared/filterQueryParam.html @@ -24,7 +24,7 @@ NotifyBC - + - + diff --git a/docs/shared/filterQueryParamCode.html b/docs/shared/filterQueryParamCode.html index 998cd0b5a..c6100ee67 100644 --- a/docs/shared/filterQueryParamCode.html +++ b/docs/shared/filterQueryParamCode.html @@ -24,10 +24,10 @@ NotifyBC - + - + diff --git a/docs/shared/filterQueryParamExample.html b/docs/shared/filterQueryParamExample.html index 840b8acce..6a95de980 100644 --- a/docs/shared/filterQueryParamExample.html +++ b/docs/shared/filterQueryParamExample.html @@ -24,7 +24,7 @@ NotifyBC - + - + diff --git a/docs/shared/jmespathFilter.html b/docs/shared/jmespathFilter.html index 01f93568f..0e8a654a3 100644 --- a/docs/shared/jmespathFilter.html +++ b/docs/shared/jmespathFilter.html @@ -24,7 +24,7 @@ NotifyBC - +
      <div class="description">a string conforming to jmespath <a href="http://jmespath.org/specification.html#filter-expressions">filter expressions syntax</a> after the question mark (?). The filter is matched against the <i><a href="../api-subscription#data">data</a></i> field of the subscription. Examples of filter
    @@ -45,6 +45,6 @@
         All of above filters will match data object <i>{"province": "BC", "city": "Victoria"}</i>
       </div>
     
    - + diff --git a/docs/shared/throttle.html b/docs/shared/throttle.html index fb1a551ab..6ebd1640c 100644 --- a/docs/shared/throttle.html +++ b/docs/shared/throttle.html @@ -24,10 +24,10 @@ NotifyBC - + - + diff --git a/docs/shared/whereQueryParam.html b/docs/shared/whereQueryParam.html index 149ea1a73..a1c2188ff 100644 --- a/docs/shared/whereQueryParam.html +++ b/docs/shared/whereQueryParam.html @@ -24,7 +24,7 @@ NotifyBC - + - + diff --git a/docs/shared/whereQueryParamCode.html b/docs/shared/whereQueryParamCode.html index b2c359338..f37cd055a 100644 --- a/docs/shared/whereQueryParamCode.html +++ b/docs/shared/whereQueryParamCode.html @@ -24,10 +24,10 @@ NotifyBC - + - + diff --git a/docs/shared/whereQueryParamExample.html b/docs/shared/whereQueryParamExample.html index 4b31bd29d..0c252eb8d 100644 --- a/docs/shared/whereQueryParamExample.html +++ b/docs/shared/whereQueryParamExample.html @@ -24,7 +24,7 @@ NotifyBC - + - + diff --git a/docs/upgrade/index.html b/docs/upgrade/index.html index 9f03e8807..a96793305 100644 --- a/docs/upgrade/index.html +++ b/docs/upgrade/index.html @@ -24,7 +24,7 @@ Upgrade Guide | NotifyBC - +

    Upgrade Guide

    Major version can only be upgraded incrementally from immediate previous major version, i.e. from N to N+1.

    v1 to v2

    Upgrading NotifyBC from v1 to v2 involves two steps

    1. Update your client code if needed
    2. Upgrade NotifyBC server

    Update your client code

    NotifyBC v2 introduced backward incompatible API changes documented in the rest of this section. If your client code will be impacted by the changes, update your code to address the incompatibility first.

    Query parameter array syntax

    In v1 array can be specified in query parameter using two formats

    1. by enclosing array elements in square brackets such as &additionalServices=["s1","s2] in one query parameter
    2. by repeating the query parameters, for example &additionalServices=s1&additionalServices=s2

    In v2 only the latter format is supported.

    Date-Time fields

    In v1 date-time fields can be specified in date-only string such as 2020-01-01. In v2 the field must be specified in ISO 8601 extended format such as 2020-01-01T00:00:00Z.

    Return status codes

    HTTP response code of success calls to following APIs are changed from 200 to 204

    Administrator API

    Upgrade NotifyBC server

    The procedure to upgrade from v1 to v2 depends on how v1 was installed.

    Source-code Installation

    1. Stop NotifyBC
    2. Backup app root and database!
    3. Make sure current branch is tracking correct remote branch
      git remote set-url origin https://github.com/bcgov/NotifyBC.git
      @@ -122,6 +122,6 @@
       git checkout tags/v5.x.x
       helm install <release-name> -f helm/platform-specific/<platform>.yaml -f helm/values.local.yaml helm
       
      Replace
      • v5.x.x with a v5 release, preferably latest, found in GitHub such as v5.0.0.
      • <release-name> with installed helm release name
      • <platform> with openshift or aks depending on your platform
    4. restore MongoDB database
    - + diff --git a/docs/web-console/index.html b/docs/web-console/index.html index 8863785e8..c19addb28 100644 --- a/docs/web-console/index.html +++ b/docs/web-console/index.html @@ -24,13 +24,13 @@ Web Console | NotifyBC - +

    Web Console

    After installing NotifyBC, you can start exploring NotifyBC resources by opening web console, a curated GUI, at http://localhost:3000open in new window. You can further explore full-blown APIs by clicking the API explorer Swagger UI embedded in web console.

    Consult the API docs for valid inputs and expected outcome while you are exploring the APIs. Once you are familiar with the APIs, you can start writing code to call the APIs from either user browser or from a server application.

    What you see in web console and what you get from API calls depend on how your requests are authenticated.

    Ip whitelisting authentication

    The API calls you made with API explorer as well as API calls made by web console from localhost are by default authenticated as super-admin requests because localhost is in admin ip list by default. Ip whitelisting authentication status is indicated by the verified_user icon on top right corner of web console.

    To see the result of non super-admin requests, you can choose one of the following methods

    • customize admin ip list to omit localhost (127.0.0.1)
    • access web console from another ip not in the admin ip list

    Client certificate authentication

    If your ip is not in the admin ip list but you have setup a client certificate issued by NotifyBC server in browser, the API calls you made with API explorer as well as API calls made by web console are also authenticated as super-admin requests. Client certificate authentication status is indicated by the verified icon on top right corner of web console.

    Anonymous

    If you access web console from a client that is not in the admin ip list, you are by default anonymous user. Anonymous authentication status is indicated by the LOGINlogin button on top right corner of web console. Click the button to login.

    Access token authentication

    If you have not configured OIDC, the login button opens a login form. After successful login, the login button is replaced with the Access Token text field on top right corner of web console. You can edit the text field. If the new access token you entered is invalid, you are essentially logging yourself out. In such case Access Token text field is replaced by the LOGINlogin button.

    The procedure to create an admin login account is documented in Administrator API

    Tokens are not shared between API Explorer and web console

    Despite API Explorer appears to be part of web console, it is a separate application. At this point neither the access token nor the OIDC access token are shared between the two applications. You have to use API Explorer's Authorize button to authenticate even if you have logged into web console.

    OIDC authentication

    If you have configured OIDC, then the login button will direct you to OIDC provider's login page. Once login successfully, you will be redirected back to NoitfyBC web console. OIDC authentication status is indicated by the LOGOUTlogout button.

    If you passed isAdmin validation, you are admin; otherwise you are authenticated user.

    SiteMinder authentication

    To get results of a SiteMinder authenticated user, do one of the following

    • access the API via a SiteMinder proxy if you have configured SiteMinder properly
    • use a tool such as curl that allows to specify custom headers, and supply SiteMinder header SM_USER:
    curl -X GET --header "Accept: application/json" \
         --header "SM_USER: foo" \
         "http://localhost:3000/api/notifications"
     
    - + diff --git a/docs/what's-new/index.html b/docs/what's-new/index.html index b25449347..a06cc70ce 100644 --- a/docs/what's-new/index.html +++ b/docs/what's-new/index.html @@ -24,10 +24,10 @@ What's New | NotifyBC - +

    What's New

    NotifyBC uses semantic versioningopen in new window.

    v5

    v5.1.0

    v5.0.0

    See Upgrade Guide for more information.

    • Runs on NestJS
    • Bitnami MongoDB Helm chart is updated from version 10.7.1 to 14.3.2, with corresponding MongoDB from 4.4 to 7.0.4
    • Bitnami Redis Helm chart is updated from version 14.7.2 to 16.13.2, with corresponding Redis from 6.2.4 to 6.2.7

    Why v5?

    NotifyBC was built on LoopBackopen in new window since the beginning. While Loopback is an awesome framework at the time, it is evident by 2022 Loopback is no longer actively maintained

    1. features such as GraphQL have been in experimental state for years
    2. recent commits are mostly chores rather than enhancements
    3. core developers have ceased to contribute

    To pave the way for future growth, switching platform becomes necessary. NestJS was chosen because

    1. both NestJS and Loopback are server-side Node.js frameworks
    2. NestJS has the closest feature set as Loopback. To a large extent NestJS is a superset of Loopback
    3. NestJS incorporates more technologies

    v4

    v4.1.0

    • Issue #50open in new window: Email message throttle
    • applied sms throttle to all sms messages rather than just broadcast push notification.
    • docs updates

    v4.0.0

    See v3 to v4 upgrade guide for more information.

    • Issue #48open in new window: SMS message throttle
    • Re-ordered config file precedence
    • Re-organized Email and SMS configs
    • docs updates

    v3

    v3.1.0

    • Issue #45open in new window: Reliability - Log skipped dispatches for broadcast push notifications
    • docs updates

    v3.0.0

    See v2 to v3 upgrade guide for more information.

    v2

    v2.9.0

    v2.8.0

    v2.7.0

    v2.6.0

    • Helm chart updates
    • docs updates

    v2.5.0

    v2.4.0

    • Issue #16open in new window: Support client certificate authentication
    • misc web console adjustments
    • docs updates

    v2.3.0

    • Issue #15open in new window: Support OIDC authentication for both admin and non-admin user
    • misc web console adjustments
    • docs updates

    v2.2.0

    • Issue #14open in new window: Support Administrator login, changing password, obtain access token in web console
    • misc web console adjustments
    • docs updates

    v2.1.0

    • Issue #13open in new window: Upgraded Vuetify from v0.16.9 to v2.4.3
    • misc web console adjustments
    • docs updates

    v2.0.0

    See Upgrade Guide for more information.

    • Runs on LoopBack v4
    • All code is converted to TypeScript
    • Upgraded OASopen in new window from v2 to v3
    • Docs is converted from Jekyll to VuePress

    Why v2?

    NotifyBC has been built on Node.js LoopBackopen in new window framework since 2016. LoopBack v4, which was released in 2019, is backward incompatible. To keep software stack up-to-date, unless rewriting from scratch, it is necessary to port NotifyBC to LoopBack v4. Great care has been taken to minimize upgrade effort.

    - + diff --git a/help/index.html b/help/index.html index a5445c36f..b2cbdafe0 100644 --- a/help/index.html +++ b/help/index.html @@ -24,10 +24,10 @@ NotifyBC - + - + diff --git a/index.html b/index.html index 08c74f9ed..66b560340 100644 --- a/index.html +++ b/index.html @@ -24,7 +24,7 @@ NotifyBC | A versatile notification API server - +
    hero

    A versatile notification API server

    Quick Start →

    Versatile

      @@ -49,6 +49,6 @@
    - +