@zydon/common 2.9.44 → 2.9.46
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/README.md
CHANGED
|
@@ -171,7 +171,7 @@ const { currentItems, totalPages, goToPage } = usePagination({
|
|
|
171
171
|
|
|
172
172
|
### Contexts
|
|
173
173
|
|
|
174
|
-
- **PushNotificationsV2** (`@zydon/common/contexts/PushNotificationsV2`): provider de push de tempo real. Canais `webpush` (Service Worker, com permissão) ou `stream` (SSE, sem permissão) — não há mais polling. Um único *sink* faz dedup + ack `DELIVERED`, e a entrega é roteada por tópico via `on(topic, handler)
|
|
174
|
+
- **PushNotificationsV2** (`@zydon/common/contexts/PushNotificationsV2`): provider de push de tempo real. Canais `webpush` (Service Worker, com permissão) ou `stream` (SSE, sem permissão) — não há mais polling. Um único *sink* faz dedup + ack `DELIVERED`, e a entrega é roteada por tópico via `on(topic, handler)`; o consumidor usa `usePushTopic(topic, handler)`, que registra o roteamento local uma vez por tópico e entrega sempre ao handler do render mais recente, sem `useRef` nem cleanup no consumidor — a inscrição do client no tópico (`subscribeToTopic`/`subscribeTopicsBatch`) continua sendo responsabilidade do consumidor. O transporte (auth/URL/reconexão) é responsabilidade do adapter injetado (`apiAdapters`, incluindo o `subscribeToStream`). Não recebe `bearerToken`. A prop opcional `contextKey` habilita a **transferência de organização** (só admin): quando muda, o provider re-registra o client sob o contexto (token) atual e remove o client antigo. Os tópicos são org-scoped, então as inscrições **não** são carregadas da org antiga; inscrever nos tópicos da org nova é responsabilidade do consumidor. A `contextKey` **deve ser derivada do token/sessão** (ex.: organization id do token), nunca de um valor que possa estar adiantado em relação ao token, senão o re-registro cai na org errada. Consumidores de org única por sessão (ex.: portal storefront) não passam `contextKey`.
|
|
175
175
|
|
|
176
176
|
### Types
|
|
177
177
|
- **PagedRequest** interface para tipar requisições paginadas. Ex: `PagedRequest<{userId: string}>` *(agora o userId também vai estar disponivel no objeto)*
|
|
@@ -63,5 +63,6 @@ interface PushNotificationV2ContextData {
|
|
|
63
63
|
|
|
64
64
|
declare const PushNotificationV2Provider: ({ children, originId, originType, vapidKey, serviceWorkerPath, serviceWorkerFile, apiAdapters, onError, contextKey, }: PushNotificationV2ProviderProps) => react_jsx_runtime.JSX.Element;
|
|
65
65
|
declare function usePushNotificationsV2(): PushNotificationV2ContextData;
|
|
66
|
+
declare function usePushTopic<TPayload = unknown>(topic: string | null, handler: (message: PushMessageV2<TPayload>) => void): void;
|
|
66
67
|
|
|
67
|
-
export { ClientRegistrationPayloadV2, ClientRegistrationResultV2, MessageStatusPayloadV2, MessageStatusV2, PushChannel, PushMessageHandler, PushMessageV2, PushNotificationApiAdaptersV2, PushNotificationV2ContextData, PushNotificationV2Provider, PushNotificationV2ProviderProps, usePushNotificationsV2 };
|
|
68
|
+
export { ClientRegistrationPayloadV2, ClientRegistrationResultV2, MessageStatusPayloadV2, MessageStatusV2, PushChannel, PushMessageHandler, PushMessageV2, PushNotificationApiAdaptersV2, PushNotificationV2ContextData, PushNotificationV2Provider, PushNotificationV2ProviderProps, usePushNotificationsV2, usePushTopic };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createContext, useState, useRef, useCallback, useEffect, useMemo, useContext } from 'react';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
|
-
function O(
|
|
4
|
+
function O(r){let a="=".repeat((4-r.length%4)%4),n=(r+a).replace(/-/g,"+").replace(/_/g,"/"),c=window.atob(n),l=new Uint8Array(c.length);for(let P=0;P<c.length;++P)l[P]=c.charCodeAt(P);return l}async function z({vapidKey:r,serviceWorkerPath:a,serviceWorkerFile:n}){if(!("serviceWorker"in navigator)||!("PushManager"in window))return null;try{if(await Notification.requestPermission()!=="granted")return null;let l=await navigator.serviceWorker.register(`${a}${n}`);return await navigator.serviceWorker.ready,await l.pushManager.subscribe({userVisibleOnly:!0,applicationServerKey:O(r)})}catch{return null}}var K=(a=>(a.DELIVERED="DELIVERED",a))(K||{}),$=(n=>(n.WEBPUSH="WEBPUSH",n.STREAM="STREAM",n))($||{});var ee=500,J=createContext(void 0),ce=({children:r,originId:a,originType:n,vapidKey:c,serviceWorkerPath:l="/",serviceWorkerFile:P="service-worker.js",apiAdapters:u,onError:d,contextKey:C})=>{let[i,X]=useState(null),[h,j]=useState(null),[S,T]=useState(!1),[x,E]=useState(!1),[w,N]=useState(0),I=useRef(null),W=useRef(new Set),p=useRef(new Map),k=useRef(!1),B=useRef(void 0),b=useCallback(e=>{if(!e?.id||W.current.has(e.id))return;let t=W.current;if(t.size>=ee){let s=t.values().next().value;s!==void 0&&t.delete(s);}t.add(e.id),N(s=>s+1),p.current.get(e.topic)?.forEach(s=>s(e)),u.updateMessageStatus(e.id,{status:"DELIVERED"}).catch(s=>d?.(s));},[u,d]),y=useCallback(async()=>{T(!0);let e=I.current;try{let t=await z({vapidKey:c,serviceWorkerPath:l,serviceWorkerFile:P}),o=t?.toJSON().keys,s={origin_type:n,origin_id:a,...t?{endpoint:t.endpoint,auth:o?.auth,p256dh:o?.p256dh}:{}},{id:M}=await u.registerClient(s);return X(M),I.current=M,E(!1),j(t?"WEBPUSH":"STREAM"),e&&e!==M&&u.removeClient?.(e).catch(Q=>d?.(Q)),M}catch(t){return E(!0),d?.(t),null}finally{T(!1);}},[u,d,a,n,P,l,c]),D=useCallback(()=>y(),[y]),V=useCallback(async e=>{if(!(!i||e.length===0))try{await u.subscribeTopicsBatch(i,e);}catch(t){d?.(t);}},[u,i,d]),m=useCallback(async e=>{if(!(!i||e.length===0))try{await u.unsubscribeTopicsBatch(i,e);}catch(t){d?.(t);}},[u,i,d]),H=useCallback(e=>V([e]),[V]),A=useCallback(e=>m([e]),[m]),U=useCallback((e,t)=>{let o=p.current.get(e)??new Set;return o.add(t),p.current.set(e,o),()=>{let s=p.current.get(e);s&&(s.delete(t),s.size===0&&p.current.delete(e));}},[]),L=useCallback(()=>{N(0);},[]),_=useRef(y);_.current=y,useEffect(()=>{k.current&&B.current===C||(k.current=!0,B.current=C,_.current());},[C]),useEffect(()=>{if(h!=="WEBPUSH"||!("serviceWorker"in navigator))return;let e=t=>{let o=t.data;!o?.id||o.payload===void 0||b({id:o.id,topic:o.topic??"",payload:o.payload});};return navigator.serviceWorker.addEventListener("message",e),()=>{navigator.serviceWorker.removeEventListener("message",e);}},[h,b]),useEffect(()=>{if(h!=="STREAM"||!i)return;let e=u.subscribeToStream(i,b);return ()=>{e();}},[u,h,i,b]);let F=useMemo(()=>!i||S,[i,S]),G=useMemo(()=>({clientId:i,channel:h,isLoading:F,isRegisterError:x,newMessageCount:w,resetMessageCount:L,registerClient:D,subscribeToTopic:H,unsubscribeFromTopic:A,subscribeTopicsBatch:V,unsubscribeTopicsBatch:m,on:U}),[h,i,F,x,w,U,D,L,H,V,A,m]);return jsx(J.Provider,{value:G,children:r})};function te(){let r=useContext(J);if(!r)throw new Error("usePushNotificationsV2 precisa estar dentro de PushNotificationV2Provider");return r}function le(r,a){let{on:n}=te(),c=useRef(a);c.current=a,useEffect(()=>{if(r)return n(r,l=>c.current(l))},[n,r]);}
|
|
5
5
|
|
|
6
|
-
export { K as MessageStatusV2, $ as PushChannel, ce as PushNotificationV2Provider,
|
|
6
|
+
export { K as MessageStatusV2, $ as PushChannel, ce as PushNotificationV2Provider, te as usePushNotificationsV2, le as usePushTopic };
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/contexts/PushNotificationsV2/index.tsx","../../../src/contexts/PushNotificationsV2/urlBase64ToUint8Array.ts","../../../src/contexts/PushNotificationsV2/resolveWebPushSubscription.ts","../../../src/contexts/PushNotificationsV2/types.ts"],"names":["createContext","useCallback","useContext","useEffect","useMemo","useRef","useState","urlBase64ToUint8Array","base64String","padding","base64","rawData","outputArray","i","resolveWebPushSubscription","vapidKey","serviceWorkerPath","serviceWorkerFile","registration","MessageStatusV2","PushChannel","jsx","MAX_PROCESSED_IDS","PushNotificationV2Context","PushNotificationV2Provider","children","originId","originType","apiAdapters","onError","contextKey","clientId","setClientId","channel","setChannel","isRegistering","setIsRegistering","isRegisterError","setIsRegisterError","newMessageCount","setNewMessageCount","clientIdRef","processedIdsRef","topicHandlersRef","hasRegisteredRef","registeredKeyRef","deliver","message","processedIds","oldest","prev","handler","error","performRegistration","previousClientId","subscription","keys","payload","id","registerClient","subscribeTopicsBatch","topics","unsubscribeTopicsBatch","subscribeToTopic","topic","unsubscribeFromTopic","on","handlers","currentHandlers","resetMessageCount","performRegistrationRef","event","data","unsubscribe","isLoading","value","usePushNotificationsV2","context"],"mappings":"AAAA,OACE,iBAAAA,EACA,eAAAC,EACA,cAAAC,EACA,aAAAC,EACA,WAAAC,EACA,UAAAC,EACA,YAAAC,MACK,QCRA,SAASC,EAAsBC,EAAkC,CACtE,IAAMC,EAAU,IAAI,QAAQ,EAAKD,EAAa,OAAS,GAAM,CAAC,EACxDE,GAAUF,EAAeC,GAAS,QAAQ,KAAM,GAAG,EAAE,QAAQ,KAAM,GAAG,EAEtEE,EAAU,OAAO,KAAKD,CAAM,EAC5BE,EAAc,IAAI,WAAWD,EAAQ,MAAM,EACjD,QAASE,EAAI,EAAGA,EAAIF,EAAQ,OAAQ,EAAEE,EACpCD,EAAYC,CAAC,EAAIF,EAAQ,WAAWE,CAAC,EAEvC,OAAOD,CACT,CCFA,eAAsBE,EAA2B,CAC/C,SAAAC,EACA,kBAAAC,EACA,kBAAAC,CACF,EAAuE,CACrE,GAAI,EAAE,kBAAmB,YAAc,EAAE,gBAAiB,QACxD,OAAO,KAGT,GAAI,CAGF,GAFmB,MAAM,aAAa,kBAAkB,IAErC,UACjB,OAAO,KAGT,IAAMC,EAAe,MAAM,UAAU,cAAc,SACjD,GAAGF,IAAoBC,GACzB,EACA,aAAM,UAAU,cAAc,MAEvB,MAAMC,EAAa,YAAY,UAAU,CAC9C,gBAAiB,GACjB,qBAAsBX,EAAsBQ,CAAQ,CACtD,CAAC,CACH,MAAE,CACA,OAAO,IACT,CACF,CC5BO,IAAKI,OACVA,EAAA,UAAY,YADFA,OAAA,IAoBAC,OACVA,EAAA,QAAU,UACVA,EAAA,OAAS,SAFCA,OAAA,IH4QR,cAAAC,OAAA,oBAnRJ,IAAMC,GAAoB,IAEpBC,EAA4BvB,EAEhC,MAAS,EAEEwB,GAA6B,CAAC,CACzC,SAAAC,EACA,SAAAC,EACA,WAAAC,EACA,SAAAZ,EACA,kBAAAC,EAAoB,IACpB,kBAAAC,EAAoB,oBACpB,YAAAW,EACA,QAAAC,EACA,WAAAC,CACF,IAAuC,CACrC,GAAM,CAACC,EAAUC,CAAW,EAAI1B,EAAwB,IAAI,EACtD,CAAC2B,EAASC,CAAU,EAAI5B,EAA6B,IAAI,EACzD,CAAC6B,EAAeC,CAAgB,EAAI9B,EAAkB,EAAK,EAC3D,CAAC+B,EAAiBC,CAAkB,EAAIhC,EAAkB,EAAK,EAC/D,CAACiC,EAAiBC,CAAkB,EAAIlC,EAAiB,CAAC,EAE1DmC,EAAcpC,EAAsB,IAAI,EACxCqC,EAAkBrC,EAAoB,IAAI,GAAK,EAC/CsC,EAAmBtC,EACvB,IAAI,GACN,EACMuC,EAAmBvC,EAAgB,EAAK,EACxCwC,EAAmBxC,EAA2B,MAAS,EAEvDyC,EAAU7C,EACb8C,GAA2B,CAC1B,GAAI,CAACA,GAAS,IAAML,EAAgB,QAAQ,IAAIK,EAAQ,EAAE,EACxD,OAGF,IAAMC,EAAeN,EAAgB,QAErC,GAAIM,EAAa,MAAQ1B,GAAmB,CAC1C,IAAM2B,EAASD,EAAa,OAAO,EAAE,KAAK,EAAE,MAExCC,IAAW,QACbD,EAAa,OAAOC,CAAM,EAI9BD,EAAa,IAAID,EAAQ,EAAE,EAE3BP,EAAmBU,GAAQA,EAAO,CAAC,EAElBP,EAAiB,QAAQ,IAAII,EAAQ,KAAK,GACjD,QAAQI,GAAWA,EAAQJ,CAAO,CAAC,EAE7CnB,EACG,oBAAoBmB,EAAQ,GAAI,CAAE,kBAAkC,CAAC,EACrE,MAAMK,GAASvB,IAAUuB,CAAc,CAAC,CAC7C,EACA,CAACxB,EAAaC,CAAO,CACvB,EAEMwB,EAAsBpD,EAAY,SAAoC,CAC1EmC,EAAiB,EAAI,EAErB,IAAMkB,EAAmBb,EAAY,QAErC,GAAI,CACF,IAAMc,EAAe,MAAMzC,EAA2B,CACpD,SAAAC,EACA,kBAAAC,EACA,kBAAAC,CACF,CAAC,EACKuC,EAAOD,GAAc,OAAO,EAAE,KAE9BE,EAAuC,CAC3C,YAAa9B,EACb,UAAWD,EACX,GAAI6B,EACA,CACE,SAAUA,EAAa,SACvB,KAAMC,GAAM,KACZ,OAAQA,GAAM,MAChB,EACA,CAAC,CACP,EAEM,CAAE,GAAAE,CAAG,EAAI,MAAM9B,EAAY,eAAe6B,CAAO,EAEvD,OAAAzB,EAAY0B,CAAE,EACdjB,EAAY,QAAUiB,EACtBpB,EAAmB,EAAK,EACxBJ,EAAWqB,oBAAuD,EAE9DD,GAAoBA,IAAqBI,GACtC9B,EACF,eAAe0B,CAAgB,EAC/B,MAAMF,GAASvB,IAAUuB,CAAc,CAAC,EAGtCM,CACT,OAASN,EAAP,CACA,OAAAd,EAAmB,EAAI,EACvBT,IAAUuB,CAAc,EAEjB,IACT,QAAE,CACAhB,EAAiB,EAAK,CACxB,CACF,EAAG,CACDR,EACAC,EACAH,EACAC,EACAV,EACAD,EACAD,CACF,CAAC,EAEK4C,EAAiB1D,EACrB,IAAMoD,EAAoB,EAC1B,CAACA,CAAmB,CACtB,EAEMO,EAAuB3D,EAC3B,MAAO4D,GAAqB,CAC1B,GAAI,GAAC9B,GAAY8B,EAAO,SAAW,GAInC,GAAI,CACF,MAAMjC,EAAY,qBAAqBG,EAAU8B,CAAM,CACzD,OAAST,EAAP,CACAvB,IAAUuB,CAAc,CAC1B,CACF,EACA,CAACxB,EAAaG,EAAUF,CAAO,CACjC,EAEMiC,EAAyB7D,EAC7B,MAAO4D,GAAqB,CAC1B,GAAI,GAAC9B,GAAY8B,EAAO,SAAW,GAInC,GAAI,CACF,MAAMjC,EAAY,uBAAuBG,EAAU8B,CAAM,CAC3D,OAAST,EAAP,CACAvB,IAAUuB,CAAc,CAC1B,CACF,EACA,CAACxB,EAAaG,EAAUF,CAAO,CACjC,EAEMkC,EAAmB9D,EACtB+D,GAAkBJ,EAAqB,CAACI,CAAK,CAAC,EAC/C,CAACJ,CAAoB,CACvB,EAEMK,EAAuBhE,EAC1B+D,GAAkBF,EAAuB,CAACE,CAAK,CAAC,EACjD,CAACF,CAAsB,CACzB,EAEMI,EAAKjE,EAAY,CAAC+D,EAAeb,IAAgC,CACrE,IAAMgB,EACJxB,EAAiB,QAAQ,IAAIqB,CAAK,GAAK,IAAI,IAC7C,OAAAG,EAAS,IAAIhB,CAAO,EACpBR,EAAiB,QAAQ,IAAIqB,EAAOG,CAAQ,EAErC,IAAM,CACX,IAAMC,EAAkBzB,EAAiB,QAAQ,IAAIqB,CAAK,EAErDI,IAILA,EAAgB,OAAOjB,CAAO,EAE1BiB,EAAgB,OAAS,GAC3BzB,EAAiB,QAAQ,OAAOqB,CAAK,EAEzC,CACF,EAAG,CAAC,CAAC,EAECK,EAAoBpE,EAAY,IAAM,CAC1CuC,EAAmB,CAAC,CACtB,EAAG,CAAC,CAAC,EAEC8B,EAAyBjE,EAAOgD,CAAmB,EACzDiB,EAAuB,QAAUjB,EAEjClD,EAAU,IAAM,CACVyC,EAAiB,SAAWC,EAAiB,UAAYf,IAI7Dc,EAAiB,QAAU,GAC3BC,EAAiB,QAAUf,EAEtBwC,EAAuB,QAAQ,EACtC,EAAG,CAACxC,CAAU,CAAC,EAEf3B,EAAU,IAAM,CACd,GAAI8B,eAAmC,EAAE,kBAAmB,WAC1D,OAGF,IAAMkB,EAAWoB,GAAwB,CACvC,IAAMC,EAAOD,EAAM,KAIf,CAACC,GAAM,IAAMA,EAAK,UAAY,QAIlC1B,EAAQ,CAAE,GAAI0B,EAAK,GAAI,MAAOA,EAAK,OAAS,GAAI,QAASA,EAAK,OAAQ,CAAC,CACzE,EAEA,iBAAU,cAAc,iBAAiB,UAAWrB,CAAO,EAEpD,IAAM,CACX,UAAU,cAAc,oBAAoB,UAAWA,CAAO,CAChE,CACF,EAAG,CAAClB,EAASa,CAAO,CAAC,EAErB3C,EAAU,IAAM,CACd,GAAI8B,cAAkC,CAACF,EACrC,OAGF,IAAM0C,EAAc7C,EAAY,kBAAkBG,EAAUe,CAAO,EAEnE,MAAO,IAAM,CACX2B,EAAY,CACd,CACF,EAAG,CAAC7C,EAAaK,EAASF,EAAUe,CAAO,CAAC,EAE5C,IAAM4B,EAAYtE,EAChB,IAAM,CAAC2B,GAAYI,EACnB,CAACJ,EAAUI,CAAa,CAC1B,EAEMwC,EAAuCvE,EAC3C,KAAO,CACL,SAAA2B,EACA,QAAAE,EACA,UAAAyC,EACA,gBAAArC,EACA,gBAAAE,EACA,kBAAA8B,EACA,eAAAV,EACA,iBAAAI,EACA,qBAAAE,EACA,qBAAAL,EACA,uBAAAE,EACA,GAAAI,CACF,GACA,CACEjC,EACAF,EACA2C,EACArC,EACAE,EACA2B,EACAP,EACAU,EACAN,EACAH,EACAK,EACAH,CACF,CACF,EAEA,OACEzC,GAACE,EAA0B,SAA1B,CAAmC,MAAOoD,EACxC,SAAAlD,EACH,CAEJ,EAEO,SAASmD,IAAwD,CACtE,IAAMC,EAAU3E,EAAWqB,CAAyB,EAEpD,GAAI,CAACsD,EACH,MAAM,IAAI,MACR,2EACF,EAGF,OAAOA,CACT","sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { resolveWebPushSubscription } from './resolveWebPushSubscription';\nimport {\n ClientRegistrationPayloadV2,\n MessageStatusV2,\n PushChannel,\n PushMessageHandler,\n PushMessageV2,\n PushNotificationV2ContextData,\n PushNotificationV2ProviderProps,\n} from './types';\n\nconst MAX_PROCESSED_IDS = 500;\n\nconst PushNotificationV2Context = createContext<\n PushNotificationV2ContextData | undefined\n>(undefined);\n\nexport const PushNotificationV2Provider = ({\n children,\n originId,\n originType,\n vapidKey,\n serviceWorkerPath = '/',\n serviceWorkerFile = 'service-worker.js',\n apiAdapters,\n onError,\n contextKey,\n}: PushNotificationV2ProviderProps) => {\n const [clientId, setClientId] = useState<string | null>(null);\n const [channel, setChannel] = useState<PushChannel | null>(null);\n const [isRegistering, setIsRegistering] = useState<boolean>(false);\n const [isRegisterError, setIsRegisterError] = useState<boolean>(false);\n const [newMessageCount, setNewMessageCount] = useState<number>(0);\n\n const clientIdRef = useRef<string | null>(null);\n const processedIdsRef = useRef<Set<string>>(new Set());\n const topicHandlersRef = useRef<Map<string, Set<PushMessageHandler>>>(\n new Map(),\n );\n const hasRegisteredRef = useRef<boolean>(false);\n const registeredKeyRef = useRef<string | undefined>(undefined);\n\n const deliver = useCallback(\n (message: PushMessageV2) => {\n if (!message?.id || processedIdsRef.current.has(message.id)) {\n return;\n }\n\n const processedIds = processedIdsRef.current;\n\n if (processedIds.size >= MAX_PROCESSED_IDS) {\n const oldest = processedIds.values().next().value;\n\n if (oldest !== undefined) {\n processedIds.delete(oldest);\n }\n }\n\n processedIds.add(message.id);\n\n setNewMessageCount(prev => prev + 1);\n\n const handlers = topicHandlersRef.current.get(message.topic);\n handlers?.forEach(handler => handler(message));\n\n apiAdapters\n .updateMessageStatus(message.id, { status: MessageStatusV2.DELIVERED })\n .catch(error => onError?.(error as Error));\n },\n [apiAdapters, onError],\n );\n\n const performRegistration = useCallback(async (): Promise<string | null> => {\n setIsRegistering(true);\n\n const previousClientId = clientIdRef.current;\n\n try {\n const subscription = await resolveWebPushSubscription({\n vapidKey,\n serviceWorkerPath,\n serviceWorkerFile,\n });\n const keys = subscription?.toJSON().keys;\n\n const payload: ClientRegistrationPayloadV2 = {\n origin_type: originType,\n origin_id: originId,\n ...(subscription\n ? {\n endpoint: subscription.endpoint,\n auth: keys?.auth,\n p256dh: keys?.p256dh,\n }\n : {}),\n };\n\n const { id } = await apiAdapters.registerClient(payload);\n\n setClientId(id);\n clientIdRef.current = id;\n setIsRegisterError(false);\n setChannel(subscription ? PushChannel.WEBPUSH : PushChannel.STREAM);\n\n if (previousClientId && previousClientId !== id) {\n void apiAdapters\n .removeClient?.(previousClientId)\n .catch(error => onError?.(error as Error));\n }\n\n return id;\n } catch (error) {\n setIsRegisterError(true);\n onError?.(error as Error);\n\n return null;\n } finally {\n setIsRegistering(false);\n }\n }, [\n apiAdapters,\n onError,\n originId,\n originType,\n serviceWorkerFile,\n serviceWorkerPath,\n vapidKey,\n ]);\n\n const registerClient = useCallback(\n () => performRegistration(),\n [performRegistration],\n );\n\n const subscribeTopicsBatch = useCallback(\n async (topics: string[]) => {\n if (!clientId || topics.length === 0) {\n return;\n }\n\n try {\n await apiAdapters.subscribeTopicsBatch(clientId, topics);\n } catch (error) {\n onError?.(error as Error);\n }\n },\n [apiAdapters, clientId, onError],\n );\n\n const unsubscribeTopicsBatch = useCallback(\n async (topics: string[]) => {\n if (!clientId || topics.length === 0) {\n return;\n }\n\n try {\n await apiAdapters.unsubscribeTopicsBatch(clientId, topics);\n } catch (error) {\n onError?.(error as Error);\n }\n },\n [apiAdapters, clientId, onError],\n );\n\n const subscribeToTopic = useCallback(\n (topic: string) => subscribeTopicsBatch([topic]),\n [subscribeTopicsBatch],\n );\n\n const unsubscribeFromTopic = useCallback(\n (topic: string) => unsubscribeTopicsBatch([topic]),\n [unsubscribeTopicsBatch],\n );\n\n const on = useCallback((topic: string, handler: PushMessageHandler) => {\n const handlers =\n topicHandlersRef.current.get(topic) ?? new Set<PushMessageHandler>();\n handlers.add(handler);\n topicHandlersRef.current.set(topic, handlers);\n\n return () => {\n const currentHandlers = topicHandlersRef.current.get(topic);\n\n if (!currentHandlers) {\n return;\n }\n\n currentHandlers.delete(handler);\n\n if (currentHandlers.size === 0) {\n topicHandlersRef.current.delete(topic);\n }\n };\n }, []);\n\n const resetMessageCount = useCallback(() => {\n setNewMessageCount(0);\n }, []);\n\n const performRegistrationRef = useRef(performRegistration);\n performRegistrationRef.current = performRegistration;\n\n useEffect(() => {\n if (hasRegisteredRef.current && registeredKeyRef.current === contextKey) {\n return;\n }\n\n hasRegisteredRef.current = true;\n registeredKeyRef.current = contextKey;\n\n void performRegistrationRef.current();\n }, [contextKey]);\n\n useEffect(() => {\n if (channel !== PushChannel.WEBPUSH || !('serviceWorker' in navigator)) {\n return;\n }\n\n const handler = (event: MessageEvent) => {\n const data = event.data as\n | { id?: string; topic?: string; payload?: unknown }\n | undefined;\n\n if (!data?.id || data.payload === undefined) {\n return;\n }\n\n deliver({ id: data.id, topic: data.topic ?? '', payload: data.payload });\n };\n\n navigator.serviceWorker.addEventListener('message', handler);\n\n return () => {\n navigator.serviceWorker.removeEventListener('message', handler);\n };\n }, [channel, deliver]);\n\n useEffect(() => {\n if (channel !== PushChannel.STREAM || !clientId) {\n return;\n }\n\n const unsubscribe = apiAdapters.subscribeToStream(clientId, deliver);\n\n return () => {\n unsubscribe();\n };\n }, [apiAdapters, channel, clientId, deliver]);\n\n const isLoading = useMemo(\n () => !clientId || isRegistering,\n [clientId, isRegistering],\n );\n\n const value: PushNotificationV2ContextData = useMemo(\n () => ({\n clientId,\n channel,\n isLoading,\n isRegisterError,\n newMessageCount,\n resetMessageCount,\n registerClient,\n subscribeToTopic,\n unsubscribeFromTopic,\n subscribeTopicsBatch,\n unsubscribeTopicsBatch,\n on,\n }),\n [\n channel,\n clientId,\n isLoading,\n isRegisterError,\n newMessageCount,\n on,\n registerClient,\n resetMessageCount,\n subscribeToTopic,\n subscribeTopicsBatch,\n unsubscribeFromTopic,\n unsubscribeTopicsBatch,\n ],\n );\n\n return (\n <PushNotificationV2Context.Provider value={value}>\n {children}\n </PushNotificationV2Context.Provider>\n );\n};\n\nexport function usePushNotificationsV2(): PushNotificationV2ContextData {\n const context = useContext(PushNotificationV2Context);\n\n if (!context) {\n throw new Error(\n 'usePushNotificationsV2 precisa estar dentro de PushNotificationV2Provider',\n );\n }\n\n return context;\n}\n\nexport type {\n ClientRegistrationPayloadV2,\n ClientRegistrationResultV2,\n MessageStatusPayloadV2,\n PushMessageHandler,\n PushMessageV2,\n PushNotificationApiAdaptersV2,\n PushNotificationV2ContextData,\n PushNotificationV2ProviderProps,\n} from './types';\nexport { MessageStatusV2, PushChannel } from './types';\n","export function urlBase64ToUint8Array(base64String: string): Uint8Array {\n const padding = '='.repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');\n\n const rawData = window.atob(base64);\n const outputArray = new Uint8Array(rawData.length);\n for (let i = 0; i < rawData.length; ++i) {\n outputArray[i] = rawData.charCodeAt(i);\n }\n return outputArray;\n}\n","import { urlBase64ToUint8Array } from './urlBase64ToUint8Array';\n\ninterface ResolveWebPushSubscriptionParams {\n vapidKey: string;\n serviceWorkerPath: string;\n serviceWorkerFile: string;\n}\n\nexport async function resolveWebPushSubscription({\n vapidKey,\n serviceWorkerPath,\n serviceWorkerFile,\n}: ResolveWebPushSubscriptionParams): Promise<PushSubscription | null> {\n if (!('serviceWorker' in navigator) || !('PushManager' in window)) {\n return null;\n }\n\n try {\n const permission = await Notification.requestPermission();\n\n if (permission !== 'granted') {\n return null;\n }\n\n const registration = await navigator.serviceWorker.register(\n `${serviceWorkerPath}${serviceWorkerFile}`,\n );\n await navigator.serviceWorker.ready;\n\n return await registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: urlBase64ToUint8Array(vapidKey) as BufferSource,\n });\n } catch {\n return null;\n }\n}\n","import { ReactNode } from 'react';\n\nexport interface PushMessageV2<TPayload = unknown> {\n id: string;\n topic: string;\n payload: TPayload;\n}\n\nexport enum MessageStatusV2 {\n DELIVERED = 'DELIVERED',\n}\n\nexport interface MessageStatusPayloadV2 {\n status: MessageStatusV2;\n}\n\nexport interface ClientRegistrationPayloadV2 {\n origin_type: string;\n origin_id: string;\n endpoint?: string;\n auth?: string;\n p256dh?: string;\n}\n\nexport interface ClientRegistrationResultV2 {\n id: string;\n}\n\nexport enum PushChannel {\n WEBPUSH = 'WEBPUSH',\n STREAM = 'STREAM',\n}\n\nexport type PushMessageHandler = (message: PushMessageV2) => void;\n\nexport interface PushNotificationApiAdaptersV2 {\n registerClient: (\n payload: ClientRegistrationPayloadV2,\n ) => Promise<ClientRegistrationResultV2>;\n\n removeClient?: (clientId: string) => Promise<void>;\n\n subscribeTopicsBatch: (clientId: string, topics: string[]) => Promise<void>;\n\n unsubscribeTopicsBatch: (clientId: string, topics: string[]) => Promise<void>;\n\n updateMessageStatus: (\n messageId: string,\n status: MessageStatusPayloadV2,\n ) => Promise<void>;\n\n subscribeToStream: (\n clientId: string,\n onMessage: PushMessageHandler,\n ) => () => void;\n}\n\nexport interface PushNotificationV2ProviderProps {\n children: ReactNode;\n originId: string;\n originType: string;\n vapidKey: string;\n serviceWorkerPath?: string;\n serviceWorkerFile?: string;\n apiAdapters: PushNotificationApiAdaptersV2;\n onError?: (error: Error) => void;\n contextKey?: string;\n}\n\nexport interface PushNotificationV2ContextData {\n clientId: string | null;\n channel: PushChannel | null;\n isLoading: boolean;\n isRegisterError: boolean;\n newMessageCount: number;\n resetMessageCount: () => void;\n registerClient: () => Promise<string | null>;\n subscribeToTopic: (topic: string) => Promise<void>;\n unsubscribeFromTopic: (topic: string) => Promise<void>;\n subscribeTopicsBatch: (topics: string[]) => Promise<void>;\n unsubscribeTopicsBatch: (topics: string[]) => Promise<void>;\n on: (topic: string, handler: PushMessageHandler) => () => void;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/contexts/PushNotificationsV2/index.tsx","../../../src/contexts/PushNotificationsV2/urlBase64ToUint8Array.ts","../../../src/contexts/PushNotificationsV2/resolveWebPushSubscription.ts","../../../src/contexts/PushNotificationsV2/types.ts"],"names":["createContext","useCallback","useContext","useEffect","useMemo","useRef","useState","urlBase64ToUint8Array","base64String","padding","base64","rawData","outputArray","i","resolveWebPushSubscription","vapidKey","serviceWorkerPath","serviceWorkerFile","registration","MessageStatusV2","PushChannel","jsx","MAX_PROCESSED_IDS","PushNotificationV2Context","PushNotificationV2Provider","children","originId","originType","apiAdapters","onError","contextKey","clientId","setClientId","channel","setChannel","isRegistering","setIsRegistering","isRegisterError","setIsRegisterError","newMessageCount","setNewMessageCount","clientIdRef","processedIdsRef","topicHandlersRef","hasRegisteredRef","registeredKeyRef","deliver","message","processedIds","oldest","prev","handler","error","performRegistration","previousClientId","subscription","keys","payload","id","registerClient","subscribeTopicsBatch","topics","unsubscribeTopicsBatch","subscribeToTopic","topic","unsubscribeFromTopic","on","handlers","currentHandlers","resetMessageCount","performRegistrationRef","event","data","unsubscribe","isLoading","value","usePushNotificationsV2","context","usePushTopic","handlerRef"],"mappings":"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","sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\nimport { resolveWebPushSubscription } from './resolveWebPushSubscription';\nimport {\n ClientRegistrationPayloadV2,\n MessageStatusV2,\n PushChannel,\n PushMessageHandler,\n PushMessageV2,\n PushNotificationV2ContextData,\n PushNotificationV2ProviderProps,\n} from './types';\n\nconst MAX_PROCESSED_IDS = 500;\n\nconst PushNotificationV2Context = createContext<\n PushNotificationV2ContextData | undefined\n>(undefined);\n\nexport const PushNotificationV2Provider = ({\n children,\n originId,\n originType,\n vapidKey,\n serviceWorkerPath = '/',\n serviceWorkerFile = 'service-worker.js',\n apiAdapters,\n onError,\n contextKey,\n}: PushNotificationV2ProviderProps) => {\n const [clientId, setClientId] = useState<string | null>(null);\n const [channel, setChannel] = useState<PushChannel | null>(null);\n const [isRegistering, setIsRegistering] = useState<boolean>(false);\n const [isRegisterError, setIsRegisterError] = useState<boolean>(false);\n const [newMessageCount, setNewMessageCount] = useState<number>(0);\n\n const clientIdRef = useRef<string | null>(null);\n const processedIdsRef = useRef<Set<string>>(new Set());\n const topicHandlersRef = useRef<Map<string, Set<PushMessageHandler>>>(\n new Map(),\n );\n const hasRegisteredRef = useRef<boolean>(false);\n const registeredKeyRef = useRef<string | undefined>(undefined);\n\n const deliver = useCallback(\n (message: PushMessageV2) => {\n if (!message?.id || processedIdsRef.current.has(message.id)) {\n return;\n }\n\n const processedIds = processedIdsRef.current;\n\n if (processedIds.size >= MAX_PROCESSED_IDS) {\n const oldest = processedIds.values().next().value;\n\n if (oldest !== undefined) {\n processedIds.delete(oldest);\n }\n }\n\n processedIds.add(message.id);\n\n setNewMessageCount(prev => prev + 1);\n\n const handlers = topicHandlersRef.current.get(message.topic);\n handlers?.forEach(handler => handler(message));\n\n apiAdapters\n .updateMessageStatus(message.id, { status: MessageStatusV2.DELIVERED })\n .catch(error => onError?.(error as Error));\n },\n [apiAdapters, onError],\n );\n\n const performRegistration = useCallback(async (): Promise<string | null> => {\n setIsRegistering(true);\n\n const previousClientId = clientIdRef.current;\n\n try {\n const subscription = await resolveWebPushSubscription({\n vapidKey,\n serviceWorkerPath,\n serviceWorkerFile,\n });\n const keys = subscription?.toJSON().keys;\n\n const payload: ClientRegistrationPayloadV2 = {\n origin_type: originType,\n origin_id: originId,\n ...(subscription\n ? {\n endpoint: subscription.endpoint,\n auth: keys?.auth,\n p256dh: keys?.p256dh,\n }\n : {}),\n };\n\n const { id } = await apiAdapters.registerClient(payload);\n\n setClientId(id);\n clientIdRef.current = id;\n setIsRegisterError(false);\n setChannel(subscription ? PushChannel.WEBPUSH : PushChannel.STREAM);\n\n if (previousClientId && previousClientId !== id) {\n void apiAdapters\n .removeClient?.(previousClientId)\n .catch(error => onError?.(error as Error));\n }\n\n return id;\n } catch (error) {\n setIsRegisterError(true);\n onError?.(error as Error);\n\n return null;\n } finally {\n setIsRegistering(false);\n }\n }, [\n apiAdapters,\n onError,\n originId,\n originType,\n serviceWorkerFile,\n serviceWorkerPath,\n vapidKey,\n ]);\n\n const registerClient = useCallback(\n () => performRegistration(),\n [performRegistration],\n );\n\n const subscribeTopicsBatch = useCallback(\n async (topics: string[]) => {\n if (!clientId || topics.length === 0) {\n return;\n }\n\n try {\n await apiAdapters.subscribeTopicsBatch(clientId, topics);\n } catch (error) {\n onError?.(error as Error);\n }\n },\n [apiAdapters, clientId, onError],\n );\n\n const unsubscribeTopicsBatch = useCallback(\n async (topics: string[]) => {\n if (!clientId || topics.length === 0) {\n return;\n }\n\n try {\n await apiAdapters.unsubscribeTopicsBatch(clientId, topics);\n } catch (error) {\n onError?.(error as Error);\n }\n },\n [apiAdapters, clientId, onError],\n );\n\n const subscribeToTopic = useCallback(\n (topic: string) => subscribeTopicsBatch([topic]),\n [subscribeTopicsBatch],\n );\n\n const unsubscribeFromTopic = useCallback(\n (topic: string) => unsubscribeTopicsBatch([topic]),\n [unsubscribeTopicsBatch],\n );\n\n const on = useCallback((topic: string, handler: PushMessageHandler) => {\n const handlers =\n topicHandlersRef.current.get(topic) ?? new Set<PushMessageHandler>();\n handlers.add(handler);\n topicHandlersRef.current.set(topic, handlers);\n\n return () => {\n const currentHandlers = topicHandlersRef.current.get(topic);\n\n if (!currentHandlers) {\n return;\n }\n\n currentHandlers.delete(handler);\n\n if (currentHandlers.size === 0) {\n topicHandlersRef.current.delete(topic);\n }\n };\n }, []);\n\n const resetMessageCount = useCallback(() => {\n setNewMessageCount(0);\n }, []);\n\n const performRegistrationRef = useRef(performRegistration);\n performRegistrationRef.current = performRegistration;\n\n useEffect(() => {\n if (hasRegisteredRef.current && registeredKeyRef.current === contextKey) {\n return;\n }\n\n hasRegisteredRef.current = true;\n registeredKeyRef.current = contextKey;\n\n void performRegistrationRef.current();\n }, [contextKey]);\n\n useEffect(() => {\n if (channel !== PushChannel.WEBPUSH || !('serviceWorker' in navigator)) {\n return;\n }\n\n const handler = (event: MessageEvent) => {\n const data = event.data as\n | { id?: string; topic?: string; payload?: unknown }\n | undefined;\n\n if (!data?.id || data.payload === undefined) {\n return;\n }\n\n deliver({ id: data.id, topic: data.topic ?? '', payload: data.payload });\n };\n\n navigator.serviceWorker.addEventListener('message', handler);\n\n return () => {\n navigator.serviceWorker.removeEventListener('message', handler);\n };\n }, [channel, deliver]);\n\n useEffect(() => {\n if (channel !== PushChannel.STREAM || !clientId) {\n return;\n }\n\n const unsubscribe = apiAdapters.subscribeToStream(clientId, deliver);\n\n return () => {\n unsubscribe();\n };\n }, [apiAdapters, channel, clientId, deliver]);\n\n const isLoading = useMemo(\n () => !clientId || isRegistering,\n [clientId, isRegistering],\n );\n\n const value: PushNotificationV2ContextData = useMemo(\n () => ({\n clientId,\n channel,\n isLoading,\n isRegisterError,\n newMessageCount,\n resetMessageCount,\n registerClient,\n subscribeToTopic,\n unsubscribeFromTopic,\n subscribeTopicsBatch,\n unsubscribeTopicsBatch,\n on,\n }),\n [\n channel,\n clientId,\n isLoading,\n isRegisterError,\n newMessageCount,\n on,\n registerClient,\n resetMessageCount,\n subscribeToTopic,\n subscribeTopicsBatch,\n unsubscribeFromTopic,\n unsubscribeTopicsBatch,\n ],\n );\n\n return (\n <PushNotificationV2Context.Provider value={value}>\n {children}\n </PushNotificationV2Context.Provider>\n );\n};\n\nexport function usePushNotificationsV2(): PushNotificationV2ContextData {\n const context = useContext(PushNotificationV2Context);\n\n if (!context) {\n throw new Error(\n 'usePushNotificationsV2 precisa estar dentro de PushNotificationV2Provider',\n );\n }\n\n return context;\n}\n\nexport function usePushTopic<TPayload = unknown>(\n topic: string | null,\n handler: (message: PushMessageV2<TPayload>) => void,\n): void {\n const { on } = usePushNotificationsV2();\n const handlerRef = useRef(handler);\n\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!topic) return undefined;\n\n return on(topic, message =>\n handlerRef.current(message as PushMessageV2<TPayload>),\n );\n }, [on, topic]);\n}\n\nexport type {\n ClientRegistrationPayloadV2,\n ClientRegistrationResultV2,\n MessageStatusPayloadV2,\n PushMessageHandler,\n PushMessageV2,\n PushNotificationApiAdaptersV2,\n PushNotificationV2ContextData,\n PushNotificationV2ProviderProps,\n} from './types';\nexport { MessageStatusV2, PushChannel } from './types';\n","export function urlBase64ToUint8Array(base64String: string): Uint8Array {\n const padding = '='.repeat((4 - (base64String.length % 4)) % 4);\n const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');\n\n const rawData = window.atob(base64);\n const outputArray = new Uint8Array(rawData.length);\n for (let i = 0; i < rawData.length; ++i) {\n outputArray[i] = rawData.charCodeAt(i);\n }\n return outputArray;\n}\n","import { urlBase64ToUint8Array } from './urlBase64ToUint8Array';\n\ninterface ResolveWebPushSubscriptionParams {\n vapidKey: string;\n serviceWorkerPath: string;\n serviceWorkerFile: string;\n}\n\nexport async function resolveWebPushSubscription({\n vapidKey,\n serviceWorkerPath,\n serviceWorkerFile,\n}: ResolveWebPushSubscriptionParams): Promise<PushSubscription | null> {\n if (!('serviceWorker' in navigator) || !('PushManager' in window)) {\n return null;\n }\n\n try {\n const permission = await Notification.requestPermission();\n\n if (permission !== 'granted') {\n return null;\n }\n\n const registration = await navigator.serviceWorker.register(\n `${serviceWorkerPath}${serviceWorkerFile}`,\n );\n await navigator.serviceWorker.ready;\n\n return await registration.pushManager.subscribe({\n userVisibleOnly: true,\n applicationServerKey: urlBase64ToUint8Array(vapidKey) as BufferSource,\n });\n } catch {\n return null;\n }\n}\n","import { ReactNode } from 'react';\n\nexport interface PushMessageV2<TPayload = unknown> {\n id: string;\n topic: string;\n payload: TPayload;\n}\n\nexport enum MessageStatusV2 {\n DELIVERED = 'DELIVERED',\n}\n\nexport interface MessageStatusPayloadV2 {\n status: MessageStatusV2;\n}\n\nexport interface ClientRegistrationPayloadV2 {\n origin_type: string;\n origin_id: string;\n endpoint?: string;\n auth?: string;\n p256dh?: string;\n}\n\nexport interface ClientRegistrationResultV2 {\n id: string;\n}\n\nexport enum PushChannel {\n WEBPUSH = 'WEBPUSH',\n STREAM = 'STREAM',\n}\n\nexport type PushMessageHandler = (message: PushMessageV2) => void;\n\nexport interface PushNotificationApiAdaptersV2 {\n registerClient: (\n payload: ClientRegistrationPayloadV2,\n ) => Promise<ClientRegistrationResultV2>;\n\n removeClient?: (clientId: string) => Promise<void>;\n\n subscribeTopicsBatch: (clientId: string, topics: string[]) => Promise<void>;\n\n unsubscribeTopicsBatch: (clientId: string, topics: string[]) => Promise<void>;\n\n updateMessageStatus: (\n messageId: string,\n status: MessageStatusPayloadV2,\n ) => Promise<void>;\n\n subscribeToStream: (\n clientId: string,\n onMessage: PushMessageHandler,\n ) => () => void;\n}\n\nexport interface PushNotificationV2ProviderProps {\n children: ReactNode;\n originId: string;\n originType: string;\n vapidKey: string;\n serviceWorkerPath?: string;\n serviceWorkerFile?: string;\n apiAdapters: PushNotificationApiAdaptersV2;\n onError?: (error: Error) => void;\n contextKey?: string;\n}\n\nexport interface PushNotificationV2ContextData {\n clientId: string | null;\n channel: PushChannel | null;\n isLoading: boolean;\n isRegisterError: boolean;\n newMessageCount: number;\n resetMessageCount: () => void;\n registerClient: () => Promise<string | null>;\n subscribeToTopic: (topic: string) => Promise<void>;\n unsubscribeFromTopic: (topic: string) => Promise<void>;\n subscribeTopicsBatch: (topics: string[]) => Promise<void>;\n unsubscribeTopicsBatch: (topics: string[]) => Promise<void>;\n on: (topic: string, handler: PushMessageHandler) => () => void;\n}\n"]}
|