@protobase/ui 0.45.18 → 0.45.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/auth-provider.js +47 -4
- package/dist/data/api-provider.d.ts +8 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/notifications/push/push-browser.js +23 -0
- package/dist/notifications/push/push-notifications.js +27 -0
- package/dist/notifications/push/push-records.js +23 -0
- package/dist/notifications/push/use-push-notifications.d.ts +15 -0
- package/dist/notifications/push/use-push-notifications.js +32 -0
- package/dist/notifications/push/web-push.d.ts +5 -0
- package/dist/notifications/push/web-push.js +165 -0
- package/dist/package.js +1 -1
- package/dist/shell/assets/app-1nDh53tv.js +9 -0
- package/dist/shell/assets/{index-B0JFYV-j.js → index-CvTuaRIF.js} +2 -2
- package/dist/shell/assets/{records-map-oHfGVxXB.js → records-map-DXlU-ssW.js} +2 -2
- package/dist/shell/assets/shared-protobase-ui-CUeF-xz_.js +2 -0
- package/dist/shell/index.html +3 -3
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +99 -2
- package/dist/shell-source-maps/assets/app-1nDh53tv.js.map +1 -0
- package/dist/shell-source-maps/assets/{index-B0JFYV-j.js.map → index-CvTuaRIF.js.map} +1 -1
- package/dist/shell-source-maps/assets/{records-map-oHfGVxXB.js.map → records-map-DXlU-ssW.js.map} +1 -1
- package/dist/shell-source-maps/assets/shared-protobase-ui-CUeF-xz_.js.map +1 -0
- package/package.json +4 -4
- package/dist/shell/assets/app-DQVyVFlF.js +0 -9
- package/dist/shell/assets/shared-protobase-ui-CESxq5P1.js +0 -2
- package/dist/shell-source-maps/assets/app-DQVyVFlF.js.map +0 -1
- package/dist/shell-source-maps/assets/shared-protobase-ui-CESxq5P1.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{n as e,r as t}from"./rolldown-runtime-hePW80VL.js";import{i as n,n as r,t as i}from"./react-CR5VJ85Q.js";import{J as a,
|
|
2
|
-
//# debugId=
|
|
1
|
+
import{n as e,r as t}from"./rolldown-runtime-hePW80VL.js";import{i as n,n as r,t as i}from"./react-CR5VJ85Q.js";import{J as a,gt as o,t as s}from"./app-1nDh53tv.js";import{c}from"./preload-helper-C24C2MCK.js";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var l=t(n(),1),u=r(),d=e({default:()=>f}),f={},p=`protobase-api-origin`,m=async e=>{e.getPlatform()===`ios`&&e.isPluginAvailable(`Keyboard`)&&await e.nativePromise(`Keyboard`,`setAccessoryBarVisible`,{isVisible:!0})},h=e=>[...e.split(`,`).map(e=>e.trim()).filter(e=>e&&!/^maximum-scale\s*=/.test(e)),`maximum-scale=1`].join(`, `),g=e=>{let t=e.querySelector(`meta[name="viewport"]`);t&&t.setAttribute(`content`,h(t.getAttribute(`content`)??``))},_=e=>{if(e.length===0)return;if(e.length>1)throw Error(`The page has ${e.length} ${p} meta tags; it may have one`);let t=e[0],n=URL.canParse(t)?new URL(t):void 0;if(!n||n.protocol!==`http:`&&n.protocol!==`https:`||n.origin!==t)throw Error(`The page's ${p} meta tag holds "${t}", which is not an http or https origin such as https://shop.example.com`);return t},v=()=>_([...document.querySelectorAll(`meta[name="${p}"]`)].map(e=>e.content)),y=t(i(),1),b=document.getElementById(`root`);if(!b)throw Error(`index.html needs an element with id "root"`);var x=o(window);x&&(g(document),m(x)),(0,u.createRoot)(b).render((0,y.jsx)(l.StrictMode,{children:(0,y.jsx)(s,{baseUrl:`${v()??``}/api/v1`,ui:a(d),locale:c(),nativeBridge:x})}));
|
|
2
|
+
//# debugId=af4ee691-205d-4943-8b5a-9e25fb8e24b6
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{i as t,t as n}from"./react-CR5VJ85Q.js";import{mt as r,o as i}from"./app-
|
|
2
|
-
//# debugId=
|
|
1
|
+
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{i as t,t as n}from"./react-CR5VJ85Q.js";import{mt as r,o as i}from"./app-1nDh53tv.js";import{a,c as o,i as s,n as c,o as l,r as u,s as d,t as f}from"./maplibre-BlwO7zTh.js";import{t as p}from"./cn-Bi0ZEjD9.js";var m=e(t(),1),h=512,g=85.051129,_=16,v=Array.from({length:23},(e,t)=>t),y=e=>e.type===`LineString`?e.coordinates:e.type===`MultiPolygon`?e.coordinates.flat(2):e.coordinates.flat(),b=e=>{let t=Math.max(-85.051129,Math.min(g,e))*Math.PI/180;return .5-Math.log(Math.tan(Math.PI/4+t/2))/(2*Math.PI)},x=e=>{let t=y(e),n=t.map(([e])=>e),r=t.map(([,e])=>b(e)),i=(Math.max(...n)-Math.min(...n))/360*h,a=(Math.max(...r)-Math.min(...r))*h;return Math.max(i,a)},S=(e,t)=>{let n=e.flatMap(e=>{if(!e.area)return[];let n=e.area.type===`LineString`||e.area.type===`MultiLineString`?`line`:`polygon`;return[{type:`Feature`,geometry:e.area,properties:{key:e.key,kind:n,span:x(e.area),chosen:t?.includes(e.key)??!1}}]});return{type:`FeatureCollection`,features:[...n.filter(e=>!e.properties.chosen),...n.filter(e=>e.properties.chosen)]}},C=e=>[`interpolate`,[`linear`],[`zoom`],...v.flatMap(t=>[t,[`*`,e,[`min`,1,[`max`,0,[`/`,[`-`,[`*`,[`get`,`span`],2**t],_],32]]]]])],w=e(n(),1),T=`position-area-fills`,E=[`==`,[`get`,`kind`],`polygon`],D=[`==`,[`get`,`kind`],`line`],O=(e,t)=>[`case`,[`get`,`chosen`],e,t],k=({points:e,selected:t,colors:n})=>{let r=S(e,t);if(r.features.length===0)return null;let i=[`case`,[`get`,`chosen`],n.chosen,n.area];return(0,w.jsxs)(s,{id:`position-areas`,type:`geojson`,data:r,children:[(0,w.jsx)(u,{id:T,type:`fill`,filter:E,paint:{"fill-color":i,"fill-opacity":C(O(.15,.1))}}),(0,w.jsx)(u,{id:`position-area-outlines`,type:`line`,filter:E,paint:{"line-color":i,"line-width":1.5,"line-dasharray":[3,2],"line-opacity":C(.8)}}),(0,w.jsx)(u,{id:`position-area-lines`,type:`line`,filter:D,layout:{"line-cap":`round`,"line-join":`round`},paint:{"line-color":i,"line-width":6,"line-opacity":C(O(.6,.35))}})]})},A=(e,t)=>{let n=document.createElement(`span`);n.style.color=`var(${t})`,e.append(n);let r=getComputedStyle(n).color;n.remove();let i=document.createElement(`canvas`);i.width=1,i.height=1;let a=i.getContext(`2d`,{willReadFrequently:!0});if(!a)return r;a.fillStyle=r,a.fillRect(0,0,1,1);let[o,s,c,l]=a.getImageData(0,0,1,1).data;return`rgba(${o}, ${s}, ${c}, ${l/255})`},j=28,M={padding:48,maxZoom:15,duration:0},N={padding:48,maxZoom:16,duration:300},P=32,F=()=>r(`compact`,e=>new Intl.NumberFormat(e,{notation:`compact`,maximumFractionDigits:1})),I=e=>[[e.west,e.south],[e.east,e.north]],L=({style:e,layers:t,initial:n,points:r,buckets:s,fit:u,onFitted:h,onMove:g,selected:_,covered:v,onSelect:y,onClear:b,markerLabel:x,bucketLabel:S})=>{let C=(0,m.useRef)(null),[E,D]=(0,m.useState)([]),[O,L]=(0,m.useState)(),R=new globalThis.Map(r.map(e=>[e.key,e])),z=r.map(e=>e.key).join(`,`),B=u&&JSON.stringify(u),V=()=>{let e=C.current?.getMap();e&&D(i(r.map(t=>({key:t.key,...e.project([t.longitude,t.latitude])})),j))},H=()=>{let e=C.current?.getMap();if(!e)return;let t=e.getBounds(),n=e.getCenter();g({bounds:{south:t.getSouth(),west:t.getWest(),north:t.getNorth(),east:t.getEast()},zoom:e.getZoom()},{latitude:n.lat,longitude:n.lng,zoom:e.getZoom()})},U=()=>{let e=C.current?.getMap();e&&u&&(e.fitBounds(I(u),M),h(),H())};(0,m.useEffect)(V,[z]),(0,m.useEffect)(U,[B]);let W=E.find(e=>_!==void 0&&e.keys.some(e=>_.includes(e)))?.at;return(0,m.useEffect)(()=>{let e=C.current?.getMap(),t=W===void 0?void 0:R.get(W);if(!e||!t||v===0)return;let{y:n}=e.project([t.longitude,t.latitude]),r=e.getContainer().clientHeight-v;n>P&&n<r||e.easeTo({center:[t.longitude,t.latitude],offset:[0,(P-v)/2],duration:300})},[W,v]),(0,w.jsxs)(d,{ref:C,mapLib:f,mapStyle:e,initialViewState:n??{latitude:0,longitude:0,zoom:1},style:{width:`100%`,height:`100%`},onLoad:()=>{let e=C.current.getMap().getContainer();L({area:A(e,`--primary`),chosen:A(e,`--foreground`)}),V(),u?U():H()},onZoomEnd:V,onMoveEnd:H,onClick:b,children:[(0,w.jsx)(a,{showCompass:!1}),(0,w.jsx)(c,{layers:t,below:T}),O&&(0,w.jsx)(k,{points:r,selected:_,colors:O}),s.map(e=>(0,w.jsx)(l,{latitude:e.latitude,longitude:e.longitude,anchor:`center`,children:(0,w.jsx)(`button`,{type:`button`,"aria-label":S(e.count),className:`flex size-10 cursor-pointer items-center justify-center rounded-full border-2 border-background bg-primary text-xs font-semibold text-primary-foreground tabular-nums shadow`,onClick:t=>{t.stopPropagation(),C.current?.getMap().fitBounds(I(e.bounds),N)},children:F().format(e.count)})},`${e.latitude},${e.longitude}`)),E.map(e=>{let t=R.get(e.at);if(!t)return null;let n=_!==void 0&&e.keys.some(e=>_.includes(e)),r=e.keys.every(e=>R.get(e)?.approximate);return(0,w.jsx)(l,{latitude:t.latitude,longitude:t.longitude,anchor:`bottom`,style:{zIndex:+!!n},children:(0,w.jsxs)(`button`,{type:`button`,"aria-label":x(e.keys),"aria-pressed":n,className:`relative block cursor-pointer`,onClick:t=>{t.stopPropagation(),y(e.keys)},children:[(0,w.jsx)(o,{"aria-hidden":!0,"data-approximate":r||void 0,className:p(`size-8 drop-shadow`,r?n?`fill-background text-foreground`:`fill-background text-primary`:n?`fill-foreground text-background`:`fill-primary text-primary-foreground`),strokeWidth:r?2:1.5,strokeDasharray:r?`3 2`:void 0}),e.keys.length>1&&(0,w.jsx)(`span`,{className:`absolute -right-2 -top-1 min-w-5 rounded-full border border-background bg-foreground px-1 text-center text-[11px] font-medium leading-4 text-background tabular-nums`,children:e.keys.length})]})},e.at)})]})};export{L as RecordsMap};
|
|
2
|
+
//# debugId=d7954fac-b16c-4d75-b931-9d403602f98d
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{i as t,t as n}from"./react-CR5VJ85Q.js";import{B as r,K as i,W as a}from"./vendor-C4-Hop1T.js";import{$ as o,A as s,B as c,C as l,D as u,E as d,F as f,G as p,H as m,I as h,J as ee,K as te,L as ne,M as re,N as ie,O as ae,P as g,Q as _,R as v,S as y,T as b,U as x,V as S,W as C,X as w,Y as T,Z as E,_ as D,_t as O,a as k,at as A,b as j,bt as M,c as N,ct as P,d as F,dt as I,et as L,f as R,ft as z,g as B,h as V,ht as H,i as U,it as W,j as G,k as K,l as q,lt as J,m as Y,n as oe,nt as se,ot as ce,p as le,pt as ue,q as de,r as fe,rt as pe,s as me,st as he,t as ge,tt as _e,u as ve,ut as ye,v as be,vt as xe,w as Se,x as Ce,y as X,yt as we,z as Te}from"./app-1nDh53tv.js";import{r as Ee}from"./preload-helper-C24C2MCK.js";import{t as Z}from"./cn-Bi0ZEjD9.js";var Q=e(n(),1),De=({row:e,resource:t,recordKey:n,...r})=>{let i=N(),a=Y(),o=t??i?.resource??a?.model.name;if(o===void 0)throw Error(`RecordLink needs a resource outside a list display or a record`);return(0,Q.jsx)(v,{to:{kind:`record`,resource:o,key:e?e.key:n},...r})},$=e(t(),1),Oe=()=>{let{push:e,state:t}=H(),n=t.kind===`signed-in`?t.user.id:void 0,[r,i]=(0,$.useState)(`loading`);return(0,$.useEffect)(()=>{let t=!0;return e.state().then(e=>t&&i(e)),()=>{t=!1}},[e,n]),{state:r,turnOn:(0,$.useCallback)(async()=>i(await e.turnOn()),[e]),turnOff:(0,$.useCallback)(async()=>{await e.turnOff(),i(await e.state())},[e])}},ke=({name:e,label:t,options:n,value:r,onChange:i})=>(0,Q.jsx)(`div`,{role:`radiogroup`,"aria-label":t,className:`flex flex-col gap-2`,children:n.map(t=>(0,Q.jsxs)(`label`,{className:Z(`block rounded-lg border p-3 transition-colors has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-2 has-[:focus-visible]:outline-primary`,t.disabled?`cursor-default bg-background opacity-60`:t.value===r?`cursor-pointer border-primary bg-primary-soft`:`cursor-pointer bg-background hover:bg-surface`),children:[(0,Q.jsx)(`input`,{type:`radio`,name:e,value:t.value,checked:t.value===r,disabled:t.disabled,onChange:()=>i(t.value),className:`sr-only`}),(0,Q.jsx)(`span`,{className:Z(`block font-medium`,t.value===r&&`text-primary-text`),children:t.label}),t.description&&(0,Q.jsx)(`span`,{className:`mt-0.5 block text-xs leading-snug text-muted-foreground`,children:t.description})]},t.value))}),Ae=(e,t,n)=>{let o=A(),s=i(),c=[...new Set(n)].sort();return r({queryKey:he.pickedLabels(e.name,c.join(`,`)),enabled:c.length>0,placeholderData:a,queryFn:()=>ve(s,o,e,c,t)}).data??new Map},je=e=>{let{resources:t,views:n}=se(),r=t[e.resource];if(!r)throw Error(`RecordPicker: the app has no resource named "${e.resource}"`);if(r.primaryKey.length!==1)throw Error(`RecordPicker: "${e.resource}" has a composite key, which a picker cannot name`);let i=n[e.resource],a=e.multiple?e.value:e.value===void 0?[]:[e.value],[o,s]=(0,$.useState)({}),c=Ae(r,i,a),l=e=>o[e]??c.get(e)??e,u=(e,t)=>s(n=>({...n,[e]:t}));if(!e.multiple){let{value:t,onChange:n}=e;return(0,Q.jsx)(f,{target:r,view:i,value:t??``,label:t===void 0?void 0:l(t),invalid:e.invalid,onChange:(e,t)=>{u(e,t),n(e)}})}let{value:d,onChange:p}=e;return(0,Q.jsxs)(`div`,{className:`flex flex-col gap-2`,children:[(0,Q.jsx)(f,{target:r,view:i,value:``,chosen:d,several:!0,invalid:e.invalid,onChange:(e,t)=>{u(e,t),p(d.includes(e)?d.filter(t=>t!==e):[...d,e])}}),d.length>0&&(0,Q.jsx)(`div`,{className:`flex flex-wrap gap-1`,children:d.map(e=>(0,Q.jsx)(q,{label:l(e),onRemove:()=>p(d.filter(t=>t!==e))},e))})]})};export{u as ActionCard,V as ActionInputError,W as ApiProvider,ge as App,v as AppLink,be as AssistantDock,E as AuthLayout,z as Button,G as ChatMessage,s as ChatThread,ke as Choices,T as CodeForm,b as CompactTable,K as Composer,I as Dialog,y as DiffView,S as Field,d as FieldList,U as Form,xe as Input,M as LogoMark,re as Markdown,k as MenuSelect,ye as OtpInput,j as PartView,De as RecordLink,je as RecordPicker,w as ResendCodeButton,J as ResponsiveDialog,c as Section,Ee as Segmented,ae as SidePanel,P as SignInPrompt,O as Spinner,Ce as StepList,ne as Textarea,_ as allowDelayMs,ue as buttonClass,ie as definePlugin,g as defineUi,l as diffStats,oe as isConflict,o as isFramed,de as mergeUi,Se as parseDiff,C as pluginApi,p as protobaseVersion,ee as uiFromModule,x as useApiToken,Te as useAppAddress,L as useArmedAfterFocus,D as useAssistant,B as useAssistantClient,X as useBackendData,A as useClient,F as useFacets,we as useFieldControl,_e as useGuardedPress,R as useHistogram,Y as useLayoutRecord,h as useList,me as useListDisplay,pe as useMeta,te as usePlugin,Oe as usePushNotifications,m as useRecord,ce as useResourceClient,le as useSeries,fe as useUpdateRecord};
|
|
2
|
+
//# debugId=94785fb1-cffa-4977-8b0a-83b4f0c8d191
|
package/dist/shell/index.html
CHANGED
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
}
|
|
19
19
|
document.documentElement.dataset.theme = stored === 'light' || stored === 'dark' ? stored : matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
20
20
|
</script>
|
|
21
|
-
<script type="importmap">{"imports":{"react":"/assets/shared-react-CuZpzdyh.js","react/jsx-runtime":"/assets/shared-react-jsx-runtime-Csr2Kx2w.js","react-dom":"/assets/shared-react-dom-BNlY7-7i.js","@protobase/ui":"/assets/shared-protobase-ui-
|
|
22
|
-
<script type="module" crossorigin src="/assets/index-
|
|
21
|
+
<script type="importmap">{"imports":{"react":"/assets/shared-react-CuZpzdyh.js","react/jsx-runtime":"/assets/shared-react-jsx-runtime-Csr2Kx2w.js","react-dom":"/assets/shared-react-dom-BNlY7-7i.js","@protobase/ui":"/assets/shared-protobase-ui-CUeF-xz_.js"}}</script>
|
|
22
|
+
<script type="module" crossorigin src="/assets/index-CvTuaRIF.js"></script>
|
|
23
23
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
24
24
|
<link rel="modulepreload" crossorigin href="/assets/react-CR5VJ85Q.js">
|
|
25
25
|
<link rel="modulepreload" crossorigin href="/assets/vendor-C4-Hop1T.js">
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/cn-Bi0ZEjD9.js">
|
|
27
27
|
<link rel="modulepreload" crossorigin href="/assets/preload-helper-C24C2MCK.js">
|
|
28
|
-
<link rel="modulepreload" crossorigin href="/assets/app-
|
|
28
|
+
<link rel="modulepreload" crossorigin href="/assets/app-1nDh53tv.js">
|
|
29
29
|
<link rel="stylesheet" crossorigin href="/assets/app-GUjJKojE.css">
|
|
30
30
|
<link rel="stylesheet" crossorigin href="/assets/index-DVdwieyN.css">
|
|
31
31
|
<link rel="stylesheet" href="/protobase.theme.css">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const cacheName = "protobase-shell-
|
|
2
|
-
const precache = ["/index.html","/assets/shared-react-CuZpzdyh.js","/assets/shared-react-jsx-runtime-Csr2Kx2w.js","/assets/shared-react-dom-BNlY7-7i.js","/assets/shared-protobase-ui-
|
|
1
|
+
const cacheName = "protobase-shell-d9848fdb970d0ee8"
|
|
2
|
+
const precache = ["/index.html","/assets/shared-react-CuZpzdyh.js","/assets/shared-react-jsx-runtime-Csr2Kx2w.js","/assets/shared-react-dom-BNlY7-7i.js","/assets/shared-protobase-ui-CUeF-xz_.js","/assets/index-CvTuaRIF.js","/assets/rolldown-runtime-hePW80VL.js","/assets/react-CR5VJ85Q.js","/assets/vendor-C4-Hop1T.js","/assets/cn-Bi0ZEjD9.js","/assets/preload-helper-C24C2MCK.js","/assets/app-1nDh53tv.js","/assets/app-GUjJKojE.css","/assets/index-DVdwieyN.css","/protobase.theme.css","/protobase.favicon.svg"]
|
|
3
3
|
const revalidated = ["/protobase.theme.css","/protobase.favicon.svg"]
|
|
4
4
|
|
|
5
5
|
const fromCache = (request) =>
|
|
@@ -31,3 +31,100 @@ self.addEventListener('fetch', (event) => {
|
|
|
31
31
|
else if (url.pathname.startsWith('/assets/')) event.respondWith(fromCache(request))
|
|
32
32
|
else if (revalidated.includes(url.pathname)) event.respondWith(fromNetwork(request, request))
|
|
33
33
|
})
|
|
34
|
+
|
|
35
|
+
const pushRecord = {"database":"protobase-push","store":"registration","key":"current"}
|
|
36
|
+
|
|
37
|
+
const pushRecordRequest = (mode, act) => new Promise((resolve, reject) => {
|
|
38
|
+
const opened = self.indexedDB.open(pushRecord.database, 1)
|
|
39
|
+
opened.onupgradeneeded = () => opened.result.createObjectStore(pushRecord.store)
|
|
40
|
+
opened.onerror = () => reject(opened.error)
|
|
41
|
+
opened.onsuccess = () => {
|
|
42
|
+
const db = opened.result
|
|
43
|
+
const request = act(db.transaction(pushRecord.store, mode).objectStore(pushRecord.store))
|
|
44
|
+
request.onsuccess = () => (db.close(), resolve(request.result))
|
|
45
|
+
request.onerror = () => (db.close(), reject(request.error))
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
const readPushRecord = () => pushRecordRequest('readonly', (store) => store.get(pushRecord.key))
|
|
49
|
+
const writePushRecord = (record) => pushRecordRequest('readwrite', (store) => store.put(record, pushRecord.key))
|
|
50
|
+
const dropPushRecord = () => pushRecordRequest('readwrite', (store) => store.delete(pushRecord.key))
|
|
51
|
+
|
|
52
|
+
// No whitespace or control character, which a URL parser drops silently, so java\tscript: cannot pass as javascript:.
|
|
53
|
+
const plainUrl = /^[^\s\u0000-\u001f\u007f-\u009f]+$/
|
|
54
|
+
|
|
55
|
+
const tapUrl = (value) => {
|
|
56
|
+
if (typeof value !== 'string' || value.length > 2048 || !plainUrl.test(value)) return undefined
|
|
57
|
+
let url
|
|
58
|
+
try {
|
|
59
|
+
url = new URL(value)
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof TypeError) return undefined
|
|
62
|
+
throw error
|
|
63
|
+
}
|
|
64
|
+
return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const pushData = (data) => {
|
|
68
|
+
if (!data) return undefined
|
|
69
|
+
let value
|
|
70
|
+
try {
|
|
71
|
+
value = data.json()
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof SyntaxError) return undefined
|
|
74
|
+
throw error
|
|
75
|
+
}
|
|
76
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : undefined
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const text = (value) => (typeof value === 'string' ? value : undefined)
|
|
80
|
+
|
|
81
|
+
// This browser is no longer registered for anyone: nothing it showed stays, and it gets no more pushes.
|
|
82
|
+
const forgetPush = async () => {
|
|
83
|
+
for (const shown of await self.registration.getNotifications()) shown.close()
|
|
84
|
+
if (self.navigator.clearAppBadge) await self.navigator.clearAppBadge()
|
|
85
|
+
await dropPushRecord()
|
|
86
|
+
const subscription = await self.registration.pushManager.getSubscription()
|
|
87
|
+
if (subscription) await subscription.unsubscribe()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const showPush = async (data) => {
|
|
91
|
+
const [record, subscription] = await Promise.all([readPushRecord(), self.registration.pushManager.getSubscription()])
|
|
92
|
+
if (!record || !subscription || record.endpoint !== subscription.endpoint) return forgetPush()
|
|
93
|
+
const push = pushData(data)
|
|
94
|
+
if (!push || text(push.title) === undefined) return self.registration.showNotification(text(record.appName) || self.location.host, {})
|
|
95
|
+
if (push.user !== record.userId) return forgetPush()
|
|
96
|
+
const url = tapUrl(push.url)
|
|
97
|
+
const options = {}
|
|
98
|
+
if (text(push.body) !== undefined) options.body = push.body
|
|
99
|
+
if (text(push.tag) !== undefined) options.tag = push.tag
|
|
100
|
+
if (url !== undefined) options.data = { url }
|
|
101
|
+
if (typeof push.badge === 'number' && self.navigator.setAppBadge) await self.navigator.setAppBadge(push.badge)
|
|
102
|
+
return self.registration.showNotification(push.title, options)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A window of the app navigated to the url as a page load; a window the worker cannot navigate gets a new one instead.
|
|
106
|
+
const openInApp = async (url) => {
|
|
107
|
+
const windows = await self.clients.matchAll({ type: 'window', includeUncontrolled: true })
|
|
108
|
+
const open = windows.find((client) => new URL(client.url).origin === self.location.origin)
|
|
109
|
+
if (!open) return self.clients.openWindow(url)
|
|
110
|
+
try {
|
|
111
|
+
const focused = await open.focus()
|
|
112
|
+
return await focused.navigate(url)
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (error instanceof TypeError) return self.clients.openWindow(url)
|
|
115
|
+
throw error
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const openTap = (notification) => {
|
|
120
|
+
notification.close()
|
|
121
|
+
const url = tapUrl(notification.data && notification.data.url)
|
|
122
|
+
if (url === undefined) return Promise.resolve()
|
|
123
|
+
return new URL(url).origin === self.location.origin ? openInApp(url) : self.clients.openWindow(url)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
self.addEventListener('push', (event) => event.waitUntil(showPush(event.data)))
|
|
127
|
+
self.addEventListener('notificationclick', (event) => event.waitUntil(openTap(event.notification)))
|
|
128
|
+
self.addEventListener('pushsubscriptionchange', (event) => {
|
|
129
|
+
event.waitUntil(readPushRecord().then((record) => record && writePushRecord({ ...record, renew: true })))
|
|
130
|
+
})
|