diff --git a/preview/404.html b/preview/404.html index e3a4c7398..a4b1e8ca7 100644 --- a/preview/404.html +++ b/preview/404.html @@ -24,10 +24,10 @@ NotifyBC - + -

404

There's nothing here.
Take me home
- +

404

How did we get here?
Take me home
+ diff --git a/preview/assets/404.html-63353ee4.js b/preview/assets/404.html-26429aa4.js similarity index 63% rename from preview/assets/404.html-63353ee4.js rename to preview/assets/404.html-26429aa4.js index e22786762..bfc433eef 100644 --- a/preview/assets/404.html-63353ee4.js +++ b/preview/assets/404.html-26429aa4.js @@ -1 +1 @@ -import{_ as e,o as c,c as t}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/app-a9ebd1ff.js b/preview/assets/app-57f81094.js similarity index 95% rename from preview/assets/app-a9ebd1ff.js rename to preview/assets/app-57f81094.js index d5bc99be2..e4c7cb461 100644 --- a/preview/assets/app-a9ebd1ff.js +++ b/preview/assets/app-57f81094.js @@ -1,4 +1,4 @@ -const ma="modulepreload",va=function(e){return"/NotifyBC/preview/"+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)}`:""),$n=(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,$i=e=>(e.n&Mt)>0,Da=({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:Dn;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?$n(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:Dn;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:Dn;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 Dn=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:Dn(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),$n(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:Dn(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 $r(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 $t(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=$t(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 $t(k,a,2)})):re(e)?t?c=()=>$t(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=()=>{$t(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)=>$n(W,w[ee])):$n(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=()=>Do(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=$=>{L||(L=!0,$?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)&&Do(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 $c(e,t){sl(e,"a",t)}function Dc(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=()=>Do(e.update)),$nextTick:e=>e.n||(e.n=$o.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:$}=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,De=!re(ie)&&re(ie.set)?ie.set.bind(n):st,Ue=z({get:He,set:De});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($c,w),U(Dc,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))$t(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 $=null;switch(F){case fn:R!==3?b.children===""?(a(b.el=r(""),i(g),g),$=g):$=m():(g.data!==b.children&&(Pt=!0,g.data=b.children),$=s(g));break;case Ye:R!==8||N?$=m():$=s(g);break;case On:if(N&&(g=s(g),R=g.nodeType),R===1||R===3){$=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 $ in m)(L&&$.endsWith("value")||zn($)&&!xn($))&&o(g,$,null,m[$],!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 $=v(g.firstChild,b,g,I,k,W,ee);for(;$;){Pt=!0;const le=$;$=$.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,D)}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,$e);"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,$e);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,$e)}"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):$(h,_,E,T,P,j,M):le(d,h,M)},$=(d,h,_,E,T,P,j)=>{const A=d.component=mu(d,E,T);if(Wn(d)&&(A.ctx.renderer=D),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,()=>Do(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){De(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&&$e(S,T,P),K!==S&&u(_,K)):G&16?oe&16?De(S,K,_,E,T,P,j,A,M):$e(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?$e(d,T,P,!0,!1,K):m(h,_,E,T,P,j,A,M,K)},De=(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,_,D);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,D,E):S&&(P!==Ee||K>0&&K&64)?$e(S,h,_,!1,!0):(P===Ee&&K&384||!T&&G&16)&&$e(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())},$e=(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},D={p:w,um:Be,m:Ue,r:Tt,mt:$,mc:m,pc:ie,pbc:B,n:C,o:e};let J,ue;return t&&([J,ue]=t(D)),{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=$t(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||(Du.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 $s=/^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&&$s.test(t)&&re(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||$s.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)},Ds=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,$)=>{jt(L,R?u:l),jt(L,R?c:i),$&&$()},B=(L,R)=>{L._isLeaving=!1,jt(L,f),jt(L,v),jt(L,p),R&&R()},Y=L=>(R,$)=>{const le=L?N:b,U=()=>F(R,L,$);Bt(le,[R,U]),Ms(()=>{jt(R,L?a:s),St(R,L?u:l),Ds(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 $=()=>B(L,R);St(L,f),Ku(),St(L,p),Ms(()=>{L._isLeaving&&(jt(L,f),St(L,v),Ds(k)||Ns(L,o,x,$))}),Bt(k,[L,$])},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-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-7bc6c067.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-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-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-f503a0ef.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-6f0f54f6.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-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/preview/","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/preview/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-eeb4738b.js"),[])),"v-14ac19b5":te(()=>O(()=>import("./index.html-25b8b71a.js"),[])),"v-7ed00a2a":te(()=>O(()=>import("./index.html-debccb11.js"),[])),"v-326db923":te(()=>O(()=>import("./index.html-c070cb70.js"),[])),"v-587df7db":te(()=>O(()=>import("./index.html-ec383b87.js"),[])),"v-0b2aad78":te(()=>O(()=>import("./index.html-9487b1e4.js"),[])),"v-23f62a3a":te(()=>O(()=>import("./index.html-3223dcc9.js"),[])),"v-1963670f":te(()=>O(()=>import("./index.html-e47ebbe7.js"),[])),"v-4cf2565c":te(()=>O(()=>import("./index.html-b8cbc790.js"),[])),"v-17bdcfe6":te(()=>O(()=>import("./index.html-1454bf95.js"),[])),"v-3481b484":te(()=>O(()=>import("./index.html-c963bb48.js"),[])),"v-b6a1f058":te(()=>O(()=>import("./index.html-3adf5625.js"),[])),"v-94b7dab4":te(()=>O(()=>import("./index.html-cadb2b1b.js"),[])),"v-391365f4":te(()=>O(()=>import("./index.html-b67cd5a6.js"),[])),"v-32b5e2dd":te(()=>O(()=>import("./index.html-c022ac12.js"),[])),"v-02a19d2b":te(()=>O(()=>import("./index.html-3b4c18e5.js"),[])),"v-26e624c6":te(()=>O(()=>import("./index.html-2f8174c6.js"),[])),"v-6165843c":te(()=>O(()=>import("./index.html-830af744.js"),[])),"v-22e054a1":te(()=>O(()=>import("./index.html-afe07b6c.js"),[])),"v-e5065f60":te(()=>O(()=>import("./index.html-5e2bc3ad.js"),[])),"v-eb8745ce":te(()=>O(()=>import("./index.html-663b6b5b.js"),[])),"v-828730c2":te(()=>O(()=>import("./index.html-592e9e5d.js"),[])),"v-563ef996":te(()=>O(()=>import("./index.html-b6bd1fa4.js"),[])),"v-04b96fc8":te(()=>O(()=>import("./index.html-244cb12a.js"),[])),"v-fe45a0b8":te(()=>O(()=>import("./index.html-0295cc5b.js"),[])),"v-147825fb":te(()=>O(()=>import("./index.html-410f75e0.js"),[])),"v-255f131a":te(()=>O(()=>import("./index.html-e44171c5.js"),[])),"v-6768263b":te(()=>O(()=>import("./index.html-b43ba34b.js"),[])),"v-6a4de75f":te(()=>O(()=>import("./index.html-61c00d77.js"),[])),"v-a20dfce8":te(()=>O(()=>import("./index.html-5d8859dc.js"),[])),"v-9a955b1e":te(()=>O(()=>import("./index.html-401c2d9a.js"),[])),"v-ca3407c4":te(()=>O(()=>import("./index.html-c2db5354.js"),[])),"v-3cf0fa66":te(()=>O(()=>import("./index.html-a030c63e.js"),[])),"v-b09aba04":te(()=>O(()=>import("./index.html-d1513f69.js"),[])),"v-b341ee2c":te(()=>O(()=>import("./index.html-a5a90e47.js"),[])),"v-0c9564ec":te(()=>O(()=>import("./index.html-78e9411a.js"),[])),"v-36e2ae9d":te(()=>O(()=>import("./index.html-d4291969.js"),[])),"v-5b6d532c":te(()=>O(()=>import("./index.html-4397fa2a.js"),[])),"v-9712b6e4":te(()=>O(()=>import("./index.html-2b19c0ea.js"),[])),"v-bdba93e6":te(()=>O(()=>import("./filterQueryParam.html-f3fc8291.js"),[])),"v-31ddcbc0":te(()=>O(()=>import("./filterQueryParamCode.html-48747d9d.js"),[])),"v-0e79de1b":te(()=>O(()=>import("./filterQueryParamExample.html-b12de830.js"),[])),"v-9a1a7988":te(()=>O(()=>import("./jmespathFilter.html-5efd875f.js"),[])),"v-17bf8008":te(()=>O(()=>import("./whereQueryParam.html-fe91a344.js"),[])),"v-0b4a148f":te(()=>O(()=>import("./whereQueryParamCode.html-e2e47303.js"),[])),"v-5119194c":te(()=>O(()=>import("./whereQueryParamExample.html-87786646.js"),[])),"v-3706649a":te(()=>O(()=>import("./404.html-63353ee4.js"),[]))};var sf=Symbol(""),lf=be(Xu),Il=_n({key:"",path:"",title:"",lang:"",frontmatter:{},headers:[]}),It=be(Il),Dt=()=>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(""),$l=Symbol(""),Dl=()=>{const e=Oe($l);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=Dt(),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/preview/${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/preview/"+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)}`:""),$n=(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,$i=e=>(e.n&Mt)>0,Da=({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:Dn;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?$n(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:Dn;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:Dn;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 Dn=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:Dn(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),$n(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:Dn(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 $r(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 $t(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=$t(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 $t(k,a,2)})):re(e)?t?c=()=>$t(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=()=>{$t(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)=>$n(W,w[ee])):$n(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=()=>Do(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=$=>{L||(L=!0,$?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)&&Do(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 $c(e,t){sl(e,"a",t)}function Dc(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=()=>Do(e.update)),$nextTick:e=>e.n||(e.n=$o.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:$}=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,De=!re(ie)&&re(ie.set)?ie.set.bind(n):st,Ue=z({get:He,set:De});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($c,w),U(Dc,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))$t(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 $=null;switch(F){case fn:R!==3?b.children===""?(a(b.el=r(""),i(g),g),$=g):$=m():(g.data!==b.children&&(Pt=!0,g.data=b.children),$=s(g));break;case Ye:R!==8||N?$=m():$=s(g);break;case On:if(N&&(g=s(g),R=g.nodeType),R===1||R===3){$=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 $ in m)(L&&$.endsWith("value")||zn($)&&!xn($))&&o(g,$,null,m[$],!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 $=v(g.firstChild,b,g,I,k,W,ee);for(;$;){Pt=!0;const le=$;$=$.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,D)}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,$e);"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,$e);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,$e)}"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):$(h,_,E,T,P,j,M):le(d,h,M)},$=(d,h,_,E,T,P,j)=>{const A=d.component=mu(d,E,T);if(Wn(d)&&(A.ctx.renderer=D),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,()=>Do(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){De(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&&$e(S,T,P),K!==S&&u(_,K)):G&16?oe&16?De(S,K,_,E,T,P,j,A,M):$e(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?$e(d,T,P,!0,!1,K):m(h,_,E,T,P,j,A,M,K)},De=(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,_,D);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,D,E):S&&(P!==Ee||K>0&&K&64)?$e(S,h,_,!1,!0):(P===Ee&&K&384||!T&&G&16)&&$e(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())},$e=(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},D={p:w,um:Be,m:Ue,r:Tt,mt:$,mc:m,pc:ie,pbc:B,n:C,o:e};let J,ue;return t&&([J,ue]=t(D)),{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=$t(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||(Du.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 $s=/^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&&$s.test(t)&&re(n)):t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||$s.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)},Ds=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,$)=>{jt(L,R?u:l),jt(L,R?c:i),$&&$()},B=(L,R)=>{L._isLeaving=!1,jt(L,f),jt(L,v),jt(L,p),R&&R()},Y=L=>(R,$)=>{const le=L?N:b,U=()=>F(R,L,$);Bt(le,[R,U]),Ms(()=>{jt(R,L?a:s),St(R,L?u:l),Ds(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 $=()=>B(L,R);St(L,f),Ku(),St(L,p),Ms(()=>{L._isLeaving&&(jt(L,f),St(L,v),Ds(k)||Ns(L,o,x,$))}),Bt(k,[L,$])},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-7bc6c067.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-f503a0ef.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-6f0f54f6.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-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/preview/","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/preview/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-f5aabd75.js"),[])),"v-14ac19b5":te(()=>O(()=>import("./index.html-e6ea0289.js"),[])),"v-e5065f60":te(()=>O(()=>import("./index.html-83be7aee.js"),[])),"v-eb8745ce":te(()=>O(()=>import("./index.html-94d7debb.js"),[])),"v-828730c2":te(()=>O(()=>import("./index.html-c53f88f1.js"),[])),"v-563ef996":te(()=>O(()=>import("./index.html-cddee61f.js"),[])),"v-04b96fc8":te(()=>O(()=>import("./index.html-520cda5c.js"),[])),"v-fe45a0b8":te(()=>O(()=>import("./index.html-ca85d788.js"),[])),"v-7ed00a2a":te(()=>O(()=>import("./index.html-d0c6035b.js"),[])),"v-326db923":te(()=>O(()=>import("./index.html-7ff7e0c7.js"),[])),"v-587df7db":te(()=>O(()=>import("./index.html-b2880e45.js"),[])),"v-0b2aad78":te(()=>O(()=>import("./index.html-89c5ddfd.js"),[])),"v-23f62a3a":te(()=>O(()=>import("./index.html-f5e745a8.js"),[])),"v-1963670f":te(()=>O(()=>import("./index.html-99312a5f.js"),[])),"v-4cf2565c":te(()=>O(()=>import("./index.html-54965770.js"),[])),"v-17bdcfe6":te(()=>O(()=>import("./index.html-2a749c99.js"),[])),"v-3481b484":te(()=>O(()=>import("./index.html-f12c3ea5.js"),[])),"v-b6a1f058":te(()=>O(()=>import("./index.html-ca106b78.js"),[])),"v-94b7dab4":te(()=>O(()=>import("./index.html-7f0e449e.js"),[])),"v-391365f4":te(()=>O(()=>import("./index.html-1c0d3481.js"),[])),"v-32b5e2dd":te(()=>O(()=>import("./index.html-10da9696.js"),[])),"v-02a19d2b":te(()=>O(()=>import("./index.html-649a5b87.js"),[])),"v-26e624c6":te(()=>O(()=>import("./index.html-b1ef58e0.js"),[])),"v-6165843c":te(()=>O(()=>import("./index.html-261651d0.js"),[])),"v-22e054a1":te(()=>O(()=>import("./index.html-9e0f2ff2.js"),[])),"v-147825fb":te(()=>O(()=>import("./index.html-dec68473.js"),[])),"v-255f131a":te(()=>O(()=>import("./index.html-dc19642b.js"),[])),"v-6768263b":te(()=>O(()=>import("./index.html-d0b6e376.js"),[])),"v-6a4de75f":te(()=>O(()=>import("./index.html-e8d37df8.js"),[])),"v-a20dfce8":te(()=>O(()=>import("./index.html-77e2410d.js"),[])),"v-9a955b1e":te(()=>O(()=>import("./index.html-75eeca44.js"),[])),"v-ca3407c4":te(()=>O(()=>import("./index.html-2256305c.js"),[])),"v-3cf0fa66":te(()=>O(()=>import("./index.html-83977ecd.js"),[])),"v-b09aba04":te(()=>O(()=>import("./index.html-91497e8b.js"),[])),"v-b341ee2c":te(()=>O(()=>import("./index.html-5fdffca2.js"),[])),"v-0c9564ec":te(()=>O(()=>import("./index.html-1f86b387.js"),[])),"v-36e2ae9d":te(()=>O(()=>import("./index.html-be9a683e.js"),[])),"v-5b6d532c":te(()=>O(()=>import("./index.html-b4912afd.js"),[])),"v-9712b6e4":te(()=>O(()=>import("./index.html-31b8fb3c.js"),[])),"v-bdba93e6":te(()=>O(()=>import("./filterQueryParam.html-10296553.js"),[])),"v-31ddcbc0":te(()=>O(()=>import("./filterQueryParamCode.html-bed9251e.js"),[])),"v-0e79de1b":te(()=>O(()=>import("./filterQueryParamExample.html-594fb329.js"),[])),"v-9a1a7988":te(()=>O(()=>import("./jmespathFilter.html-ffbf5b0e.js"),[])),"v-17bf8008":te(()=>O(()=>import("./whereQueryParam.html-c282207c.js"),[])),"v-0b4a148f":te(()=>O(()=>import("./whereQueryParamCode.html-9259b1a1.js"),[])),"v-5119194c":te(()=>O(()=>import("./whereQueryParamExample.html-3d4517fd.js"),[])),"v-3706649a":te(()=>O(()=>import("./404.html-26429aa4.js"),[]))};var sf=Symbol(""),lf=be(Xu),Il=_n({key:"",path:"",title:"",lang:"",frontmatter:{},headers:[]}),It=be(Il),Dt=()=>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(""),$l=Symbol(""),Dl=()=>{const e=Oe($l);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=Dt(),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/preview/${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 $=ho(L.template)?L.template:document.querySelector(L.template);R.template=$}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),$=0;$0?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 $=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return I.forEach(function(le){le.addEventListener("medium-zoom:"+L,R,$)}),k.push({type:"medium-zoom:"+L,listener:R,options:$}),B},p=function(L,R){var $=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return I.forEach(function(le){le.removeEventListener("medium-zoom:"+L,R,$)}),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,$=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),De=He.getBoundingClientRect(),Ue=De.width,Be=De.height,Tt=De.left,Lt=De.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,$e=ai(lt)?se:lt.naturalWidth||se,C=ai(lt)?ie:lt.naturalHeight||ie,V=lt.getBoundingClientRect(),D=V.top,J=V.left,ue=V.width,d=V.height,h=Math.min(Math.max(ue,$e),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=(-D+(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,$()};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),$())},10)}else if(m.original.hasAttribute("srcset")){m.zoomedHd=m.zoomed.cloneNode(),m.zoomedHd.removeAttribute("sizes"),m.zoomedHd.removeAttribute("loading");var De=m.zoomedHd.addEventListener("load",function(){m.zoomedHd.removeEventListener("load",De),m.zoomedHd.classList.add("medium-zoom-image--opened"),m.zoomedHd.addEventListener("click",y),document.body.appendChild(m.zoomedHd),$()})}else $()})},y=function(){return new o(function(L){if(W||!m.original){L(B);return}var R=function $(){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",$),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 $d(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 Dd=".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}";$d(Dd);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/bullmq":"^10.2.1","@nestjs/common":"^10.4.5","@nestjs/core":"^10.4.5","@nestjs/mongoose":"^10.0.10","@nestjs/platform-express":"^10.4.5","@nestjs/swagger":"^7.4.2","@nestjs/terminus":"^10.2.3","async":"^3.2.4","axios":"^1.6.8","bcryptjs":"^2.4.3","bullmq":"^5.21.1","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","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","redis-memory-server":"^0.10.0","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.4.5","@nestjs/schematics":"^10.2.2","@nestjs/testing":"^10.4.5","@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"},"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()?($a(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 $p(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 Dp=Object.defineProperty,_i=Object.getOwnPropertySymbols,Mp=Object.prototype.hasOwnProperty,Np=Object.prototype.propertyIsEnumerable,bi=(e,t,n)=>t in e?Dp(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=$p(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?$o(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=Dt();return[{text:t.value.title,children:Xr(t.value.headers,e)}]},aa=(e,t)=>{const n=Zt(),o=Dt(),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}=$r(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"},$h=ae("span",{class:"arrow down"},null,-1),Dh=["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}=$r(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),$h],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,Dh),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=Dt(),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=Dt(),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=Dt(),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"},$m=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}=$r(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=>{$o(()=>{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)])}}}),Dm=Le($m,[["__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(Dm,{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=Dt(),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=Dl(),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=Dt(),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-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-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-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-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/preview/")),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($l,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=Dl(),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/bullmq":"^10.2.1","@nestjs/common":"^10.4.5","@nestjs/core":"^10.4.5","@nestjs/mongoose":"^10.0.10","@nestjs/platform-express":"^10.4.5","@nestjs/swagger":"^7.4.2","@nestjs/terminus":"^10.2.3","async":"^3.2.4","axios":"^1.6.8","bcryptjs":"^2.4.3","bullmq":"^5.21.1","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","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","redis-memory-server":"^0.10.0","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.4.5","@nestjs/schematics":"^10.2.2","@nestjs/testing":"^10.4.5","@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"},"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()?($a(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 $p(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 Dp=Object.defineProperty,_i=Object.getOwnPropertySymbols,Mp=Object.prototype.hasOwnProperty,Np=Object.prototype.propertyIsEnumerable,bi=(e,t,n)=>t in e?Dp(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=$p(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?$o(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=Dt();return[{text:t.value.title,children:Xr(t.value.headers,e)}]},aa=(e,t)=>{const n=Zt(),o=Dt(),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}=$r(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"},$h=ae("span",{class:"arrow down"},null,-1),Dh=["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}=$r(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),$h],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,Dh),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=Dt(),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=Dt(),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=Dt(),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"},$m=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}=$r(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=>{$o(()=>{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)])}}}),Dm=Le($m,[["__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(Dm,{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=Dt(),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=Dl(),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=Dt(),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-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/preview/")),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($l,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=Dl(),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/preview/assets/filterQueryParam.html-f3fc8291.js b/preview/assets/filterQueryParam.html-10296553.js similarity index 96% rename from preview/assets/filterQueryParam.html-f3fc8291.js rename to preview/assets/filterQueryParam.html-10296553.js index 7fb2036e9..ed15495ba 100644 --- a/preview/assets/filterQueryParam.html-f3fc8291.js +++ b/preview/assets/filterQueryParam.html-10296553.js @@ -1,4 +1,4 @@ -import{_ as r,o as t,c as n,a as e,b as o}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/filterQueryParamCode.html-48747d9d.js b/preview/assets/filterQueryParamCode.html-bed9251e.js similarity index 81% rename from preview/assets/filterQueryParamCode.html-48747d9d.js rename to preview/assets/filterQueryParamCode.html-bed9251e.js index 946f2c9ea..77b33ee92 100644 --- a/preview/assets/filterQueryParamCode.html-48747d9d.js +++ b/preview/assets/filterQueryParamCode.html-bed9251e.js @@ -1 +1 @@ -import{_ as e,o as t,c as r,a as o}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/filterQueryParamExample.html-b12de830.js b/preview/assets/filterQueryParamExample.html-594fb329.js similarity index 86% rename from preview/assets/filterQueryParamExample.html-b12de830.js rename to preview/assets/filterQueryParamExample.html-594fb329.js index be53e6a42..64f00a494 100644 --- a/preview/assets/filterQueryParamExample.html-b12de830.js +++ b/preview/assets/filterQueryParamExample.html-594fb329.js @@ -1,4 +1,4 @@ -import{_ as t,o as n,c as r,a as e}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/index.html-c022ac12.js b/preview/assets/index.html-10da9696.js similarity index 98% rename from preview/assets/index.html-c022ac12.js rename to preview/assets/index.html-10da9696.js index c0db2ddff..6a4217ee8 100644 --- a/preview/assets/index.html-c022ac12.js +++ b/preview/assets/index.html-10da9696.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-b67cd5a6.js b/preview/assets/index.html-1c0d3481.js
similarity index 98%
rename from preview/assets/index.html-b67cd5a6.js
rename to preview/assets/index.html-1c0d3481.js
index c322830d1..f5829532c 100644
--- a/preview/assets/index.html-b67cd5a6.js
+++ b/preview/assets/index.html-1c0d3481.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-78e9411a.js b/preview/assets/index.html-1f86b387.js similarity index 98% rename from preview/assets/index.html-78e9411a.js rename to preview/assets/index.html-1f86b387.js index a97ea116c..439cd6bf8 100644 --- a/preview/assets/index.html-78e9411a.js +++ b/preview/assets/index.html-1f86b387.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-c2db5354.js b/preview/assets/index.html-2256305c.js similarity index 96% rename from preview/assets/index.html-c2db5354.js rename to preview/assets/index.html-2256305c.js index 60b1c865d..985a73911 100644 --- a/preview/assets/index.html-c2db5354.js +++ b/preview/assets/index.html-2256305c.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-830af744.js b/preview/assets/index.html-261651d0.js similarity index 99% rename from preview/assets/index.html-830af744.js rename to preview/assets/index.html-261651d0.js index 14954849f..c143ae5d5 100644 --- a/preview/assets/index.html-830af744.js +++ b/preview/assets/index.html-261651d0.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-1454bf95.js b/preview/assets/index.html-2a749c99.js
similarity index 99%
rename from preview/assets/index.html-1454bf95.js
rename to preview/assets/index.html-2a749c99.js
index 68e866f45..83e505362 100644
--- a/preview/assets/index.html-1454bf95.js
+++ b/preview/assets/index.html-2a749c99.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-2b19c0ea.js b/preview/assets/index.html-31b8fb3c.js
similarity index 99%
rename from preview/assets/index.html-2b19c0ea.js
rename to preview/assets/index.html-31b8fb3c.js
index 2e6ce358f..a02fb8cdd 100644
--- a/preview/assets/index.html-2b19c0ea.js
+++ b/preview/assets/index.html-31b8fb3c.js
@@ -1,4 +1,4 @@
-import{_ as p,r as l,o as r,c,a as e,b as n,d as a,w as t,e as o}from"./app-a9ebd1ff.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.

v5 to v6

v6 introduced following backward incompatible changes

  1. Redis is required. Redis connection is moved from sms.throttle.clientOptions and email.throttle.clientOptions to queue.connection. Update file src/config.local.[json|js|ts] from, for example,

    module.exports = {
    +import{_ as p,r as l,o as r,c,a as e,b as n,d as a,w as t,e as o}from"./app-57f81094.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.

    v5 to v6

    v6 introduced following backward incompatible changes

    1. Redis is required. Redis connection is moved from sms.throttle.clientOptions and email.throttle.clientOptions to queue.connection. Update file src/config.local.[json|js|ts] from, for example,

      module.exports = {
         // ...
         sms: {
           throttle: {
      diff --git a/preview/assets/index.html-244cb12a.js b/preview/assets/index.html-520cda5c.js
      similarity index 94%
      rename from preview/assets/index.html-244cb12a.js
      rename to preview/assets/index.html-520cda5c.js
      index afe6e58f4..27de36765 100644
      --- a/preview/assets/index.html-244cb12a.js
      +++ b/preview/assets/index.html-520cda5c.js
      @@ -1 +1 @@
      -import{_ as e,o as i,c as a,e as o}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/index.html-b8cbc790.js b/preview/assets/index.html-54965770.js similarity index 97% rename from preview/assets/index.html-b8cbc790.js rename to preview/assets/index.html-54965770.js index 2539d48aa..7d05a625f 100644 --- a/preview/assets/index.html-b8cbc790.js +++ b/preview/assets/index.html-54965770.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-a5a90e47.js b/preview/assets/index.html-5fdffca2.js similarity index 98% rename from preview/assets/index.html-a5a90e47.js rename to preview/assets/index.html-5fdffca2.js index 788bb6024..39b80b0cb 100644 --- a/preview/assets/index.html-a5a90e47.js +++ b/preview/assets/index.html-5fdffca2.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-3b4c18e5.js b/preview/assets/index.html-649a5b87.js
      similarity index 99%
      rename from preview/assets/index.html-3b4c18e5.js
      rename to preview/assets/index.html-649a5b87.js
      index 079c11143..4978d86b4 100644
      --- a/preview/assets/index.html-3b4c18e5.js
      +++ b/preview/assets/index.html-649a5b87.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-401c2d9a.js b/preview/assets/index.html-75eeca44.js
      similarity index 99%
      rename from preview/assets/index.html-401c2d9a.js
      rename to preview/assets/index.html-75eeca44.js
      index cbd0a6cf8..a4e3e4bfa 100644
      --- a/preview/assets/index.html-401c2d9a.js
      +++ b/preview/assets/index.html-75eeca44.js
      @@ -1 +1 @@
      -import{_ as l,r,o as d,c as h,a as e,b as t,d as o,w as i,e as a}from"./app-a9ebd1ff.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),f={href:"https://semver.org/",target:"_blank",rel:"noopener noreferrer"},p=a('

      v6

      • Replaced Bottleneck with BullMQ
      • Redis is required
      • Bitnami Redis Helm chart is updated from version 16.13.2 to 20.1.0, with corresponding Redis from 6.2.7 to 7.4.0
      • Added loggingLevels config

      v5

      v5.1.0

      ',4),m={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),b=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),g=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),k={class:"custom-container tip"},y=e("p",{class:"custom-container-title"},"Why v5?",-1),B=e("em",null,"NotifyBC",-1),N={href:"https://loopback.io/",target:"_blank",rel:"noopener noreferrer"},w=e("em",null,"Loopback",-1),x=e("em",null,"Loopback",-1),C=a("
      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),S=e("h2",{id:"v4",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v4","aria-hidden":"true"},"#"),t(" v4")],-1),L=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),I={href:"https://github.com/bcgov/NotifyBC/issues/50",target:"_blank",rel:"noopener noreferrer"},R=e("li",null,"applied sms throttle to all sms messages rather than just broadcast push notification.",-1),J=e("li",null,"docs updates",-1),j=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),A={href:"https://github.com/bcgov/NotifyBC/issues/48",target:"_blank",rel:"noopener noreferrer"},H=e("li",null,"Re-ordered config file precedence",-1),M=e("li",null,"Re-organized Email and SMS configs",-1),V=e("li",null,"docs updates",-1),D=e("h2",{id:"v3",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v3","aria-hidden":"true"},"#"),t(" v3")],-1),E=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),T={href:"https://github.com/bcgov/NotifyBC/issues/45",target:"_blank",rel:"noopener noreferrer"},G=e("li",null,"docs updates",-1),U=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),W={href:"https://github.com/bcgov/NotifyBC/issues/36",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/bcgov/NotifyBC/issues/37",target:"_blank",rel:"noopener noreferrer"},z={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"},P={href:"https://github.com/bcgov/NotifyBC/issues/41",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/bcgov/NotifyBC/issues/42",target:"_blank",rel:"noopener noreferrer"},K=e("li",null,"docs updates",-1),X=e("h2",{id:"v2",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2","aria-hidden":"true"},"#"),t(" v2")],-1),Y=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),Z={href:"https://github.com/bcgov/NotifyBC/issues/34",target:"_blank",rel:"noopener noreferrer"},$=e("li",null,"docs updates",-1),ee=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),te={href:"https://github.com/bcgov/NotifyBC/issues/28",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/bcgov/NotifyBC/issues/32",target:"_blank",rel:"noopener noreferrer"},se=e("li",null,"docs updates",-1),ne=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"},re=e("li",null,"docs updates",-1),ae=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),le=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),he={href:"https://github.com/bcgov/NotifyBC/tree/main/helm",target:"_blank",rel:"noopener noreferrer"},ce=e("li",null,"docs updates",-1),ue=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),_e={href:"https://github.com/bcgov/NotifyBC/issues/16",target:"_blank",rel:"noopener noreferrer"},fe=e("li",null,"misc web console adjustments",-1),pe=e("li",null,"docs updates",-1),me=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"},be=e("li",null,"misc web console adjustments",-1),ge=e("li",null,"docs updates",-1),ke=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),ye={href:"https://github.com/bcgov/NotifyBC/issues/14",target:"_blank",rel:"noopener noreferrer"},Be=e("li",null,"misc web console adjustments",-1),Ne=e("li",null,"docs updates",-1),we=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),xe={href:"https://github.com/bcgov/NotifyBC/issues/13",target:"_blank",rel:"noopener noreferrer"},Ce=e("li",null,"misc web console adjustments",-1),Se=e("li",null,"docs updates",-1),Le=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),Ie=e("li",null,"Runs on LoopBack v4",-1),Re=e("li",null,"All code is converted to TypeScript",-1),Je={href:"https://swagger.io/specification/",target:"_blank",rel:"noopener noreferrer"},je=e("li",null,"Docs is converted from Jekyll to VuePress",-1),Ae={class:"custom-container tip"},He=e("p",{class:"custom-container-title"},"Why v2?",-1),Me=e("em",null,"NotifyBC",-1),Ve={href:"https://loopback.io/",target:"_blank",rel:"noopener noreferrer"},De=e("em",null,"NotifyBC",-1);function Ee(Te,Ge){const s=r("ExternalLinkIcon"),n=r("RouterLink");return d(),h("div",null,[u,e("p",null,[_,t(" uses "),e("a",f,[t("semantic versioning"),o(s)]),t(".")]),p,e("ul",null,[e("li",null,[t("Issue "),e("a",m,[t("#85"),o(s)]),t(": added health check")]),v]),b,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v4-to-v5"},{default:i(()=>[t("Upgrade Guide")]),_:1}),t(" for more information.")]),g,e("div",k,[y,e("p",null,[B,t(" was built on "),e("a",N,[t("LoopBack"),o(s)]),t(" since the beginning. While "),w,t(" is an awesome framework at the time, it is evident by 2022 "),x,t(" is no longer actively maintained")]),C]),S,L,e("ul",null,[e("li",null,[t("Issue "),e("a",I,[t("#50"),o(s)]),t(": Email message throttle")]),R,J]),j,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v3-to-v4"},{default:i(()=>[t("v3 to v4 upgrade guide")]),_:1}),t(" for more information.")]),e("ul",null,[e("li",null,[t("Issue "),e("a",A,[t("#48"),o(s)]),t(": SMS message throttle")]),H,M,V]),D,E,e("ul",null,[e("li",null,[t("Issue "),e("a",T,[t("#45"),o(s)]),t(": Reliability - Log skipped dispatches for broadcast push notifications")]),G]),U,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v2-to-v3"},{default:i(()=>[t("v2 to v3 upgrade guide")]),_:1}),t(" for more information.")]),e("ul",null,[e("li",null,[t("Reliability improvements - issues "),e("a",W,[t("#36"),o(s)]),t(","),e("a",O,[t("#37"),o(s)]),t(","),e("a",z,[t("#38"),o(s)]),t(","),e("a",Q,[t("#39"),o(s)]),t(","),e("a",q,[t("#40"),o(s)]),t(","),e("a",P,[t("#41"),o(s)]),t(","),e("a",F,[t("#42"),o(s)])]),K]),X,Y,e("ul",null,[e("li",null,[t("Issue "),e("a",Z,[t("#34"),o(s)]),t(": Helm - add k8s cronJob to backup MongoDB")]),$]),ee,e("ul",null,[e("li",null,[t("Issue "),e("a",te,[t("#28"),o(s)]),t(": Allow subscription data be used by mail merge dynamic tokens")]),e("li",null,[t("Issue "),e("a",oe,[t("#32"),o(s)]),t(": Allow escape mail merge delimiter")]),se]),ne,e("ul",null,[e("li",null,[t("Issue "),e("a",ie,[t("#26"),o(s)]),t(": Allow filter specified in a notification")]),re]),ae,le,de,e("ul",null,[e("li",null,[t("added "),e("a",he,[t("helm chart"),o(s)]),t(". See "),o(n,{to:"/docs/miscellaneous/upgrade.html#openshift-template-to-helm"},{default:i(()=>[t("OpenShift template to Helm upgrade guide")]),_:1})]),ce]),ue,e("ul",null,[e("li",null,[t("Issue "),e("a",_e,[t("#16"),o(s)]),t(": Support client certificate authentication")]),fe,pe]),me,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")]),be,ge]),ke,e("ul",null,[e("li",null,[t("Issue "),e("a",ye,[t("#14"),o(s)]),t(": Support Administrator login, changing password, obtain access token in web console")]),Be,Ne]),we,e("ul",null,[e("li",null,[t("Issue "),e("a",xe,[t("#13"),o(s)]),t(": Upgraded Vuetify from v0.16.9 to v2.4.3")]),Ce,Se]),Le,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v1-to-v2"},{default:i(()=>[t("Upgrade Guide")]),_:1}),t(" for more information.")]),e("ul",null,[Ie,Re,e("li",null,[t("Upgraded "),e("a",Je,[t("OAS"),o(s)]),t(" from v2 to v3")]),je]),e("div",Ae,[He,e("p",null,[Me,t(" has been built on Node.js "),e("a",Ve,[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 "),De,t(" to LoopBack v4. Great care has been taken to minimize upgrade effort.")])])])}const We=l(c,[["render",Ee],["__file","index.html.vue"]]);export{We as default}; +import{_ as l,r,o as d,c as h,a as e,b as t,d as o,w as i,e as a}from"./app-57f81094.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),f={href:"https://semver.org/",target:"_blank",rel:"noopener noreferrer"},p=a('

      v6

      • Replaced Bottleneck with BullMQ
      • Redis is required
      • Bitnami Redis Helm chart is updated from version 16.13.2 to 20.1.0, with corresponding Redis from 6.2.7 to 7.4.0
      • Added loggingLevels config

      v5

      v5.1.0

      ',4),m={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),b=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),g=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),k={class:"custom-container tip"},y=e("p",{class:"custom-container-title"},"Why v5?",-1),B=e("em",null,"NotifyBC",-1),N={href:"https://loopback.io/",target:"_blank",rel:"noopener noreferrer"},w=e("em",null,"Loopback",-1),x=e("em",null,"Loopback",-1),C=a("
      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),S=e("h2",{id:"v4",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v4","aria-hidden":"true"},"#"),t(" v4")],-1),L=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),I={href:"https://github.com/bcgov/NotifyBC/issues/50",target:"_blank",rel:"noopener noreferrer"},R=e("li",null,"applied sms throttle to all sms messages rather than just broadcast push notification.",-1),J=e("li",null,"docs updates",-1),j=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),A={href:"https://github.com/bcgov/NotifyBC/issues/48",target:"_blank",rel:"noopener noreferrer"},H=e("li",null,"Re-ordered config file precedence",-1),M=e("li",null,"Re-organized Email and SMS configs",-1),V=e("li",null,"docs updates",-1),D=e("h2",{id:"v3",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v3","aria-hidden":"true"},"#"),t(" v3")],-1),E=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),T={href:"https://github.com/bcgov/NotifyBC/issues/45",target:"_blank",rel:"noopener noreferrer"},G=e("li",null,"docs updates",-1),U=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),W={href:"https://github.com/bcgov/NotifyBC/issues/36",target:"_blank",rel:"noopener noreferrer"},O={href:"https://github.com/bcgov/NotifyBC/issues/37",target:"_blank",rel:"noopener noreferrer"},z={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"},P={href:"https://github.com/bcgov/NotifyBC/issues/41",target:"_blank",rel:"noopener noreferrer"},F={href:"https://github.com/bcgov/NotifyBC/issues/42",target:"_blank",rel:"noopener noreferrer"},K=e("li",null,"docs updates",-1),X=e("h2",{id:"v2",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#v2","aria-hidden":"true"},"#"),t(" v2")],-1),Y=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),Z={href:"https://github.com/bcgov/NotifyBC/issues/34",target:"_blank",rel:"noopener noreferrer"},$=e("li",null,"docs updates",-1),ee=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),te={href:"https://github.com/bcgov/NotifyBC/issues/28",target:"_blank",rel:"noopener noreferrer"},oe={href:"https://github.com/bcgov/NotifyBC/issues/32",target:"_blank",rel:"noopener noreferrer"},se=e("li",null,"docs updates",-1),ne=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"},re=e("li",null,"docs updates",-1),ae=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),le=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),he={href:"https://github.com/bcgov/NotifyBC/tree/main/helm",target:"_blank",rel:"noopener noreferrer"},ce=e("li",null,"docs updates",-1),ue=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),_e={href:"https://github.com/bcgov/NotifyBC/issues/16",target:"_blank",rel:"noopener noreferrer"},fe=e("li",null,"misc web console adjustments",-1),pe=e("li",null,"docs updates",-1),me=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"},be=e("li",null,"misc web console adjustments",-1),ge=e("li",null,"docs updates",-1),ke=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),ye={href:"https://github.com/bcgov/NotifyBC/issues/14",target:"_blank",rel:"noopener noreferrer"},Be=e("li",null,"misc web console adjustments",-1),Ne=e("li",null,"docs updates",-1),we=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),xe={href:"https://github.com/bcgov/NotifyBC/issues/13",target:"_blank",rel:"noopener noreferrer"},Ce=e("li",null,"misc web console adjustments",-1),Se=e("li",null,"docs updates",-1),Le=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),Ie=e("li",null,"Runs on LoopBack v4",-1),Re=e("li",null,"All code is converted to TypeScript",-1),Je={href:"https://swagger.io/specification/",target:"_blank",rel:"noopener noreferrer"},je=e("li",null,"Docs is converted from Jekyll to VuePress",-1),Ae={class:"custom-container tip"},He=e("p",{class:"custom-container-title"},"Why v2?",-1),Me=e("em",null,"NotifyBC",-1),Ve={href:"https://loopback.io/",target:"_blank",rel:"noopener noreferrer"},De=e("em",null,"NotifyBC",-1);function Ee(Te,Ge){const s=r("ExternalLinkIcon"),n=r("RouterLink");return d(),h("div",null,[u,e("p",null,[_,t(" uses "),e("a",f,[t("semantic versioning"),o(s)]),t(".")]),p,e("ul",null,[e("li",null,[t("Issue "),e("a",m,[t("#85"),o(s)]),t(": added health check")]),v]),b,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v4-to-v5"},{default:i(()=>[t("Upgrade Guide")]),_:1}),t(" for more information.")]),g,e("div",k,[y,e("p",null,[B,t(" was built on "),e("a",N,[t("LoopBack"),o(s)]),t(" since the beginning. While "),w,t(" is an awesome framework at the time, it is evident by 2022 "),x,t(" is no longer actively maintained")]),C]),S,L,e("ul",null,[e("li",null,[t("Issue "),e("a",I,[t("#50"),o(s)]),t(": Email message throttle")]),R,J]),j,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v3-to-v4"},{default:i(()=>[t("v3 to v4 upgrade guide")]),_:1}),t(" for more information.")]),e("ul",null,[e("li",null,[t("Issue "),e("a",A,[t("#48"),o(s)]),t(": SMS message throttle")]),H,M,V]),D,E,e("ul",null,[e("li",null,[t("Issue "),e("a",T,[t("#45"),o(s)]),t(": Reliability - Log skipped dispatches for broadcast push notifications")]),G]),U,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v2-to-v3"},{default:i(()=>[t("v2 to v3 upgrade guide")]),_:1}),t(" for more information.")]),e("ul",null,[e("li",null,[t("Reliability improvements - issues "),e("a",W,[t("#36"),o(s)]),t(","),e("a",O,[t("#37"),o(s)]),t(","),e("a",z,[t("#38"),o(s)]),t(","),e("a",Q,[t("#39"),o(s)]),t(","),e("a",q,[t("#40"),o(s)]),t(","),e("a",P,[t("#41"),o(s)]),t(","),e("a",F,[t("#42"),o(s)])]),K]),X,Y,e("ul",null,[e("li",null,[t("Issue "),e("a",Z,[t("#34"),o(s)]),t(": Helm - add k8s cronJob to backup MongoDB")]),$]),ee,e("ul",null,[e("li",null,[t("Issue "),e("a",te,[t("#28"),o(s)]),t(": Allow subscription data be used by mail merge dynamic tokens")]),e("li",null,[t("Issue "),e("a",oe,[t("#32"),o(s)]),t(": Allow escape mail merge delimiter")]),se]),ne,e("ul",null,[e("li",null,[t("Issue "),e("a",ie,[t("#26"),o(s)]),t(": Allow filter specified in a notification")]),re]),ae,le,de,e("ul",null,[e("li",null,[t("added "),e("a",he,[t("helm chart"),o(s)]),t(". See "),o(n,{to:"/docs/miscellaneous/upgrade.html#openshift-template-to-helm"},{default:i(()=>[t("OpenShift template to Helm upgrade guide")]),_:1})]),ce]),ue,e("ul",null,[e("li",null,[t("Issue "),e("a",_e,[t("#16"),o(s)]),t(": Support client certificate authentication")]),fe,pe]),me,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")]),be,ge]),ke,e("ul",null,[e("li",null,[t("Issue "),e("a",ye,[t("#14"),o(s)]),t(": Support Administrator login, changing password, obtain access token in web console")]),Be,Ne]),we,e("ul",null,[e("li",null,[t("Issue "),e("a",xe,[t("#13"),o(s)]),t(": Upgraded Vuetify from v0.16.9 to v2.4.3")]),Ce,Se]),Le,e("p",null,[t("See "),o(n,{to:"/docs/upgrade/#v1-to-v2"},{default:i(()=>[t("Upgrade Guide")]),_:1}),t(" for more information.")]),e("ul",null,[Ie,Re,e("li",null,[t("Upgraded "),e("a",Je,[t("OAS"),o(s)]),t(" from v2 to v3")]),je]),e("div",Ae,[He,e("p",null,[Me,t(" has been built on Node.js "),e("a",Ve,[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 "),De,t(" to LoopBack v4. Great care has been taken to minimize upgrade effort.")])])])}const We=l(c,[["render",Ee],["__file","index.html.vue"]]);export{We as default}; diff --git a/preview/assets/index.html-5d8859dc.js b/preview/assets/index.html-77e2410d.js similarity index 99% rename from preview/assets/index.html-5d8859dc.js rename to preview/assets/index.html-77e2410d.js index 75d885792..5491282fa 100644 --- a/preview/assets/index.html-5d8859dc.js +++ b/preview/assets/index.html-77e2410d.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-cadb2b1b.js b/preview/assets/index.html-7f0e449e.js similarity index 98% rename from preview/assets/index.html-cadb2b1b.js rename to preview/assets/index.html-7f0e449e.js index fc2e22798..915ff7368 100644 --- a/preview/assets/index.html-cadb2b1b.js +++ b/preview/assets/index.html-7f0e449e.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-a9ebd1ff.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("
    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
    5. ",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-57f81094.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("
    6. clientId - OIDC client id
    7. 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.
    8. 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
    9. ",3),h=a(`

      A example of complete OIDC configuration looks like

      module.exports = {
         ...
         oidc: {
           discoveryUrl:
      diff --git a/preview/assets/index.html-c070cb70.js b/preview/assets/index.html-7ff7e0c7.js
      similarity index 99%
      rename from preview/assets/index.html-c070cb70.js
      rename to preview/assets/index.html-7ff7e0c7.js
      index 762dfcccc..0436cd933 100644
      --- a/preview/assets/index.html-c070cb70.js
      +++ b/preview/assets/index.html-7ff7e0c7.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-a030c63e.js b/preview/assets/index.html-83977ecd.js
      similarity index 97%
      rename from preview/assets/index.html-a030c63e.js
      rename to preview/assets/index.html-83977ecd.js
      index 618e84009..8a7418ac4 100644
      --- a/preview/assets/index.html-a030c63e.js
      +++ b/preview/assets/index.html-83977ecd.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-5e2bc3ad.js b/preview/assets/index.html-83be7aee.js similarity index 99% rename from preview/assets/index.html-5e2bc3ad.js rename to preview/assets/index.html-83be7aee.js index 7713884dd..0a6f79c47 100644 --- a/preview/assets/index.html-5e2bc3ad.js +++ b/preview/assets/index.html-83be7aee.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-9487b1e4.js b/preview/assets/index.html-89c5ddfd.js
          similarity index 97%
          rename from preview/assets/index.html-9487b1e4.js
          rename to preview/assets/index.html-89c5ddfd.js
          index 638a47edf..bb58c4cfa 100644
          --- a/preview/assets/index.html-9487b1e4.js
          +++ b/preview/assets/index.html-89c5ddfd.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-d1513f69.js b/preview/assets/index.html-91497e8b.js
          similarity index 99%
          rename from preview/assets/index.html-d1513f69.js
          rename to preview/assets/index.html-91497e8b.js
          index c2bd1e694..8af928215 100644
          --- a/preview/assets/index.html-d1513f69.js
          +++ b/preview/assets/index.html-91497e8b.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-663b6b5b.js b/preview/assets/index.html-94d7debb.js
          similarity index 98%
          rename from preview/assets/index.html-663b6b5b.js
          rename to preview/assets/index.html-94d7debb.js
          index a0861641a..efd1cff3b 100644
          --- a/preview/assets/index.html-663b6b5b.js
          +++ b/preview/assets/index.html-94d7debb.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-e47ebbe7.js b/preview/assets/index.html-99312a5f.js similarity index 95% rename from preview/assets/index.html-e47ebbe7.js rename to preview/assets/index.html-99312a5f.js index 2986926a9..4542d4686 100644 --- a/preview/assets/index.html-e47ebbe7.js +++ b/preview/assets/index.html-99312a5f.js @@ -1,4 +1,4 @@ -import{_ as e,o as t,c as s,e as n}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/index.html-afe07b6c.js b/preview/assets/index.html-9e0f2ff2.js similarity index 93% rename from preview/assets/index.html-afe07b6c.js rename to preview/assets/index.html-9e0f2ff2.js index d1f42b084..d53b82e8c 100644 --- a/preview/assets/index.html-afe07b6c.js +++ b/preview/assets/index.html-9e0f2ff2.js @@ -1 +1 @@ -import{_ as e,o as t,c as o,e as r}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/index.html-2f8174c6.js b/preview/assets/index.html-b1ef58e0.js similarity index 99% rename from preview/assets/index.html-2f8174c6.js rename to preview/assets/index.html-b1ef58e0.js index 6b83e41a0..5ba5561ff 100644 --- a/preview/assets/index.html-2f8174c6.js +++ b/preview/assets/index.html-b1ef58e0.js @@ -1,4 +1,4 @@ -import{_ as i,r as o,o as l,c as r,a as n,b as s,d as e,w as c,e as a}from"./app-a9ebd1ff.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),v=n("p",null,[n("em",null,"NotifyBC"),s(" depends on underlying SMS service providers to deliver SMS messages. The supported service providers are")],-1),k={href:"https://twilio.com/",target:"_blank",rel:"noopener noreferrer"},h={href:"https://www.swiftsmsgateway.com",target:"_blank",rel:"noopener noreferrer"},b=a(`

          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 o,o as l,c as r,a as n,b as s,d as e,w as c,e as a}from"./app-57f81094.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),v=n("p",null,[n("em",null,"NotifyBC"),s(" depends on underlying SMS service providers to deliver SMS messages. The supported service providers are")],-1),k={href:"https://twilio.com/",target:"_blank",rel:"noopener noreferrer"},h={href:"https://www.swiftsmsgateway.com",target:"_blank",rel:"noopener noreferrer"},b=a(`

          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/preview/assets/index.html-ec383b87.js b/preview/assets/index.html-b2880e45.js
          similarity index 99%
          rename from preview/assets/index.html-ec383b87.js
          rename to preview/assets/index.html-b2880e45.js
          index 08219379a..0d43cca33 100644
          --- a/preview/assets/index.html-ec383b87.js
          +++ b/preview/assets/index.html-b2880e45.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-4397fa2a.js b/preview/assets/index.html-b4912afd.js
          similarity index 96%
          rename from preview/assets/index.html-4397fa2a.js
          rename to preview/assets/index.html-b4912afd.js
          index dbe558742..052abd261 100644
          --- a/preview/assets/index.html-4397fa2a.js
          +++ b/preview/assets/index.html-b4912afd.js
          @@ -1,4 +1,4 @@
          -import{_ as e,o as a,c as n,e as o}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/index.html-d4291969.js b/preview/assets/index.html-be9a683e.js
          similarity index 98%
          rename from preview/assets/index.html-d4291969.js
          rename to preview/assets/index.html-be9a683e.js
          index 0dd109cc6..e240bcd93 100644
          --- a/preview/assets/index.html-d4291969.js
          +++ b/preview/assets/index.html-be9a683e.js
          @@ -1,4 +1,4 @@
          -import{_ as n,o as s,c as a,e as t}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/index.html-592e9e5d.js b/preview/assets/index.html-c53f88f1.js
          similarity index 99%
          rename from preview/assets/index.html-592e9e5d.js
          rename to preview/assets/index.html-c53f88f1.js
          index 90d8db887..bedd74f8f 100644
          --- a/preview/assets/index.html-592e9e5d.js
          +++ b/preview/assets/index.html-c53f88f1.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-3adf5625.js b/preview/assets/index.html-ca106b78.js
          similarity index 99%
          rename from preview/assets/index.html-3adf5625.js
          rename to preview/assets/index.html-ca106b78.js
          index 5278c1f04..1cbe575d0 100644
          --- a/preview/assets/index.html-3adf5625.js
          +++ b/preview/assets/index.html-ca106b78.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-0295cc5b.js b/preview/assets/index.html-ca85d788.js
          similarity index 99%
          rename from preview/assets/index.html-0295cc5b.js
          rename to preview/assets/index.html-ca85d788.js
          index cb9ea68b8..a3bd7444f 100644
          --- a/preview/assets/index.html-0295cc5b.js
          +++ b/preview/assets/index.html-ca85d788.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-b6bd1fa4.js b/preview/assets/index.html-cddee61f.js
          similarity index 99%
          rename from preview/assets/index.html-b6bd1fa4.js
          rename to preview/assets/index.html-cddee61f.js
          index 1a0f030bd..a1f8a114a 100644
          --- a/preview/assets/index.html-b6bd1fa4.js
          +++ b/preview/assets/index.html-cddee61f.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-b43ba34b.js b/preview/assets/index.html-d0b6e376.js
          similarity index 99%
          rename from preview/assets/index.html-b43ba34b.js
          rename to preview/assets/index.html-d0b6e376.js
          index ef15e137d..1c1332cb2 100644
          --- a/preview/assets/index.html-b43ba34b.js
          +++ b/preview/assets/index.html-d0b6e376.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-a9ebd1ff.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('
        4. 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.

        5. 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
        6. anonymous user, if the request doesn't meet any of the above criteria

        7. ',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-57f81094.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('
        8. 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.

        9. 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
        10. anonymous user, if the request doesn't meet any of the above criteria

        11. ',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/preview/assets/index.html-debccb11.js b/preview/assets/index.html-d0c6035b.js similarity index 97% rename from preview/assets/index.html-debccb11.js rename to preview/assets/index.html-d0c6035b.js index 2dd16e66f..86e99bf66 100644 --- a/preview/assets/index.html-debccb11.js +++ b/preview/assets/index.html-d0c6035b.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-e44171c5.js b/preview/assets/index.html-dc19642b.js
          similarity index 99%
          rename from preview/assets/index.html-e44171c5.js
          rename to preview/assets/index.html-dc19642b.js
          index 6a815bf04..25d1c84f7 100644
          --- a/preview/assets/index.html-e44171c5.js
          +++ b/preview/assets/index.html-dc19642b.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-a9ebd1ff.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",-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("
        12. 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.
        13. ",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-57f81094.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",-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("
        14. 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.
        15. ",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/preview/assets/index.html-410f75e0.js b/preview/assets/index.html-dec68473.js
          similarity index 96%
          rename from preview/assets/index.html-410f75e0.js
          rename to preview/assets/index.html-dec68473.js
          index 91f40bb09..9dffc50e5 100644
          --- a/preview/assets/index.html-410f75e0.js
          +++ b/preview/assets/index.html-dec68473.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-25b8b71a.js b/preview/assets/index.html-e6ea0289.js similarity index 96% rename from preview/assets/index.html-25b8b71a.js rename to preview/assets/index.html-e6ea0289.js index 01d370f62..c4e112ae5 100644 --- a/preview/assets/index.html-25b8b71a.js +++ b/preview/assets/index.html-e6ea0289.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-61c00d77.js b/preview/assets/index.html-e8d37df8.js similarity index 95% rename from preview/assets/index.html-61c00d77.js rename to preview/assets/index.html-e8d37df8.js index 9059b8e77..f50bb4d9f 100644 --- a/preview/assets/index.html-61c00d77.js +++ b/preview/assets/index.html-e8d37df8.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-a9ebd1ff.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-57f81094.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/preview/assets/index.html-c963bb48.js b/preview/assets/index.html-f12c3ea5.js
          similarity index 91%
          rename from preview/assets/index.html-c963bb48.js
          rename to preview/assets/index.html-f12c3ea5.js
          index 9549de626..180b22808 100644
          --- a/preview/assets/index.html-c963bb48.js
          +++ b/preview/assets/index.html-f12c3ea5.js
          @@ -1 +1 @@
          -import{_ as t,o,c as s,a as e,b as n}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/index.html-eeb4738b.js b/preview/assets/index.html-f5aabd75.js
          similarity index 63%
          rename from preview/assets/index.html-eeb4738b.js
          rename to preview/assets/index.html-f5aabd75.js
          index 7be5a7887..cdeba3c72 100644
          --- a/preview/assets/index.html-eeb4738b.js
          +++ b/preview/assets/index.html-f5aabd75.js
          @@ -1 +1 @@
          -import{_ as e,o as _,c as t}from"./app-a9ebd1ff.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-57f81094.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/preview/assets/index.html-3223dcc9.js b/preview/assets/index.html-f5e745a8.js
          similarity index 99%
          rename from preview/assets/index.html-3223dcc9.js
          rename to preview/assets/index.html-f5e745a8.js
          index 042ccf726..44e1424ab 100644
          --- a/preview/assets/index.html-3223dcc9.js
          +++ b/preview/assets/index.html-f5e745a8.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 r,e as t}from"./app-a9ebd1ff.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 r,e as t}from"./app-57f81094.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/preview/assets/jmespathFilter.html-5efd875f.js b/preview/assets/jmespathFilter.html-ffbf5b0e.js
          similarity index 94%
          rename from preview/assets/jmespathFilter.html-5efd875f.js
          rename to preview/assets/jmespathFilter.html-ffbf5b0e.js
          index 5c94baea9..052e33f4a 100644
          --- a/preview/assets/jmespathFilter.html-5efd875f.js
          +++ b/preview/assets/jmespathFilter.html-ffbf5b0e.js
          @@ -1,4 +1,4 @@
          -import{_ as t,o as n,c as e,a as i}from"./app-a9ebd1ff.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-57f81094.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
          - + diff --git a/preview/docs/api-bounce/index.html b/preview/docs/api-bounce/index.html index bff9fdc4d..ac396401b 100644 --- a/preview/docs/api-bounce/index.html +++ b/preview/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/preview/docs/api-config/index.html b/preview/docs/api-config/index.html index e42475f2e..411980714 100644 --- a/preview/docs/api-config/index.html +++ b/preview/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 @@
           
        16. 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/preview/docs/api-notification/index.html b/preview/docs/api-notification/index.html index 758f92552..02aa1656d 100644 --- a/preview/docs/api-notification/index.html +++ b/preview/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/preview/docs/api-overview/index.html b/preview/docs/api-overview/index.html index 72ceb2b9d..88eb211da 100644 --- a/preview/docs/api-overview/index.html +++ b/preview/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/preview/docs/api-subscription/index.html b/preview/docs/api-subscription/index.html index 1011d308d..5b7eae30b 100644 --- a/preview/docs/api-subscription/index.html +++ b/preview/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/preview/docs/benchmarks/index.html b/preview/docs/benchmarks/index.html index 0c3562c11..fc08f05aa 100644 --- a/preview/docs/benchmarks/index.html +++ b/preview/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/preview/docs/bulk-import/index.html b/preview/docs/bulk-import/index.html index 70d458c5a..0f828c163 100644 --- a/preview/docs/bulk-import/index.html +++ b/preview/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/preview/docs/conduct/index.html b/preview/docs/conduct/index.html index c2dbec6e2..54ca0e127 100644 --- a/preview/docs/conduct/index.html +++ b/preview/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/preview/docs/config-adminIpList/index.html b/preview/docs/config-adminIpList/index.html index f56177f46..2c386bf73 100644 --- a/preview/docs/config-adminIpList/index.html +++ b/preview/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/preview/docs/config-certificates/index.html b/preview/docs/config-certificates/index.html index e4ed9806c..8424409e3 100644 --- a/preview/docs/config-certificates/index.html +++ b/preview/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/preview/docs/config-cronJobs/index.html b/preview/docs/config-cronJobs/index.html index 97a737358..4af8b0337 100644 --- a/preview/docs/config-cronJobs/index.html +++ b/preview/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 = {
      @@ -82,6 +82,6 @@
         },
       };
       
      - + diff --git a/preview/docs/config-database/index.html b/preview/docs/config-database/index.html index 4a93dccfb..c98afe37b 100644 --- a/preview/docs/config-database/index.html +++ b/preview/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/preview/docs/config-email/index.html b/preview/docs/config-email/index.html index 7efcaee45..d7f357dc9 100644 --- a/preview/docs/config-email/index.html +++ b/preview/docs/config-email/index.html @@ -24,7 +24,7 @@ Email | NotifyBC - + - + diff --git a/preview/docs/config-httpHost/index.html b/preview/docs/config-httpHost/index.html index 85180226f..609faf0c8 100644 --- a/preview/docs/config-httpHost/index.html +++ b/preview/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/preview/docs/config-internalHttpHost/index.html b/preview/docs/config-internalHttpHost/index.html index 2305acb7e..f7d2e1a21 100644 --- a/preview/docs/config-internalHttpHost/index.html +++ b/preview/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/preview/docs/config-middleware/index.html b/preview/docs/config-middleware/index.html index 49b88c49d..b1f7c8f2e 100644 --- a/preview/docs/config-middleware/index.html +++ b/preview/docs/config-middleware/index.html @@ -24,7 +24,7 @@ Middleware | NotifyBC - + - + diff --git a/preview/docs/config-nodeRoles/index.html b/preview/docs/config-nodeRoles/index.html index c4f07069d..16bc13e04 100644 --- a/preview/docs/config-nodeRoles/index.html +++ b/preview/docs/config-nodeRoles/index.html @@ -24,10 +24,10 @@ Node Roles | NotifyBC - + - + diff --git a/preview/docs/config-notification/index.html b/preview/docs/config-notification/index.html index 3f65e3f41..f61ae3a2d 100644 --- a/preview/docs/config-notification/index.html +++ b/preview/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/preview/docs/config-oidc/index.html b/preview/docs/config-oidc/index.html index 2e26e28e3..40f8a2cf6 100644 --- a/preview/docs/config-oidc/index.html +++ b/preview/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/preview/docs/config-overview/index.html b/preview/docs/config-overview/index.html index fb44be03c..d9b9d535b 100644 --- a/preview/docs/config-overview/index.html +++ b/preview/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/preview/docs/config-reverseProxyIpLists/index.html b/preview/docs/config-reverseProxyIpLists/index.html index 12f2102a4..650f5d342 100644 --- a/preview/docs/config-reverseProxyIpLists/index.html +++ b/preview/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/preview/docs/config-rsaKeys/index.html b/preview/docs/config-rsaKeys/index.html index 6c95324dd..c120f72f3 100644 --- a/preview/docs/config-rsaKeys/index.html +++ b/preview/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/preview/docs/config-sms/index.html b/preview/docs/config-sms/index.html index 00516a607..9d771ed5c 100644 --- a/preview/docs/config-sms/index.html +++ b/preview/docs/config-sms/index.html @@ -24,7 +24,7 @@ SMS | NotifyBC - + - + diff --git a/preview/docs/config-subscription/index.html b/preview/docs/config-subscription/index.html index e56e05b21..d7847484d 100644 --- a/preview/docs/config-subscription/index.html +++ b/preview/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/preview/docs/config-workerProcessCount/index.html b/preview/docs/config-workerProcessCount/index.html index 94ea2a664..e8bdab381 100644 --- a/preview/docs/config-workerProcessCount/index.html +++ b/preview/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/preview/docs/developer-notes/index.html b/preview/docs/developer-notes/index.html index 5aa8c321c..461e79638 100644 --- a/preview/docs/developer-notes/index.html +++ b/preview/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/preview/docs/health-check/index.html b/preview/docs/health-check/index.html index 604f465ee..473d25064 100644 --- a/preview/docs/health-check/index.html +++ b/preview/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/preview/docs/index.html b/preview/docs/index.html index 91c9308bc..ef22b7743 100644 --- a/preview/docs/index.html +++ b/preview/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/preview/docs/installation/index.html b/preview/docs/installation/index.html index 668b5a2f6..38245f780 100644 --- a/preview/docs/installation/index.html +++ b/preview/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
        • 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 @@
       
    10. 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/preview/docs/memory-dump/index.html b/preview/docs/memory-dump/index.html index 6aa6a9987..43628df85 100644 --- a/preview/docs/memory-dump/index.html +++ b/preview/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/preview/docs/overview/index.html b/preview/docs/overview/index.html index 8be380b3e..3c71235ed 100644 --- a/preview/docs/overview/index.html +++ b/preview/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/preview/docs/quickstart/index.html b/preview/docs/quickstart/index.html index 8666d68b2..a43ae4468 100644 --- a/preview/docs/quickstart/index.html +++ b/preview/docs/quickstart/index.html @@ -24,7 +24,7 @@ Quick Start | NotifyBC - + - + diff --git a/preview/docs/shared/filterQueryParam.html b/preview/docs/shared/filterQueryParam.html index 8b7ddbbde..037db2c73 100644 --- a/preview/docs/shared/filterQueryParam.html +++ b/preview/docs/shared/filterQueryParam.html @@ -24,7 +24,7 @@ NotifyBC - + - + diff --git a/preview/docs/shared/filterQueryParamCode.html b/preview/docs/shared/filterQueryParamCode.html index 33d3d5466..7d88aa77b 100644 --- a/preview/docs/shared/filterQueryParamCode.html +++ b/preview/docs/shared/filterQueryParamCode.html @@ -24,10 +24,10 @@ NotifyBC - + - + diff --git a/preview/docs/shared/filterQueryParamExample.html b/preview/docs/shared/filterQueryParamExample.html index 2a49790d6..259baa9ca 100644 --- a/preview/docs/shared/filterQueryParamExample.html +++ b/preview/docs/shared/filterQueryParamExample.html @@ -24,7 +24,7 @@ NotifyBC - + - + diff --git a/preview/docs/shared/jmespathFilter.html b/preview/docs/shared/jmespathFilter.html index d4f068baa..8ecf81eea 100644 --- a/preview/docs/shared/jmespathFilter.html +++ b/preview/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/preview/docs/shared/whereQueryParam.html b/preview/docs/shared/whereQueryParam.html index f8cdeda65..e9bef5bb4 100644 --- a/preview/docs/shared/whereQueryParam.html +++ b/preview/docs/shared/whereQueryParam.html @@ -24,7 +24,7 @@ NotifyBC - + - + diff --git a/preview/docs/shared/whereQueryParamCode.html b/preview/docs/shared/whereQueryParamCode.html index 3f54bd7ef..b178b7410 100644 --- a/preview/docs/shared/whereQueryParamCode.html +++ b/preview/docs/shared/whereQueryParamCode.html @@ -24,10 +24,10 @@ NotifyBC - + - + diff --git a/preview/docs/shared/whereQueryParamExample.html b/preview/docs/shared/whereQueryParamExample.html index 0d9b1329a..c05922f17 100644 --- a/preview/docs/shared/whereQueryParamExample.html +++ b/preview/docs/shared/whereQueryParamExample.html @@ -24,7 +24,7 @@ NotifyBC - + - + diff --git a/preview/docs/upgrade/index.html b/preview/docs/upgrade/index.html index b97ca1488..76c858925 100644 --- a/preview/docs/upgrade/index.html +++ b/preview/docs/upgrade/index.html @@ -24,7 +24,7 @@ Upgrade Guide | NotifyBC - + - + diff --git a/preview/docs/web-console/index.html b/preview/docs/web-console/index.html index f2b1a36db..0633f2801 100644 --- a/preview/docs/web-console/index.html +++ b/preview/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/preview/docs/what's-new/index.html b/preview/docs/what's-new/index.html index b37cbb161..edde25c56 100644 --- a/preview/docs/what's-new/index.html +++ b/preview/docs/what's-new/index.html @@ -24,10 +24,10 @@ What's New | NotifyBC - +

What's New

NotifyBC uses semantic versioningopen in new window.

v6

  • Replaced Bottleneck with BullMQ
  • Redis is required
  • Bitnami Redis Helm chart is updated from version 16.13.2 to 20.1.0, with corresponding Redis from 6.2.7 to 7.4.0
  • Added loggingLevels config

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/preview/help/index.html b/preview/help/index.html index 1e34b51a5..ec514cde6 100644 --- a/preview/help/index.html +++ b/preview/help/index.html @@ -24,10 +24,10 @@ NotifyBC - + - + diff --git a/preview/index.html b/preview/index.html index 3ebb316c8..4c87ab1f5 100644 --- a/preview/index.html +++ b/preview/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 @@
- +