diff --git a/.github/workflows/release/versions.json b/.github/workflows/release/versions.json
index 51884f614..dc23f3b23 100644
--- a/.github/workflows/release/versions.json
+++ b/.github/workflows/release/versions.json
@@ -20,7 +20,7 @@
"clearedSuffix": false
}
],
- "src/core/components/config.js": [
+ "src/core/components/configuration.ts": [
{
"pattern": "^\\s{2,}return '(v?(\\.?\\d+){2,}([a-zA-Z0-9-]+(\\.?\\d+)?)?)';$",
"clearedPrefix": true,
diff --git a/dist/web/pubnub.js b/dist/web/pubnub.js
index 4fa0dd604..92b49723d 100644
--- a/dist/web/pubnub.js
+++ b/dist/web/pubnub.js
@@ -3672,12 +3672,13 @@
var uuidGenerator$1 = /*@__PURE__*/getDefaultExportFromCjs(uuidExports);
var uuidGenerator = {
- createUUID() {
- if (uuidGenerator$1.uuid) {
- return uuidGenerator$1.uuid();
- }
- return uuidGenerator$1();
- },
+ createUUID() {
+ if (uuidGenerator$1.uuid) {
+ return uuidGenerator$1.uuid();
+ }
+ // @ts-expect-error Depending on module type it may be callable.
+ return uuidGenerator$1();
+ },
};
/**
@@ -6450,7 +6451,6 @@
channels,
groups,
}));
- // TODO: Find out actual `status` type.
/* eslint-disable @typescript-eslint/no-explicit-any */
const emitStatus$1 = createEffect('EMIT_STATUS', (status) => status);
const wait = createManagedEffect('WAIT', () => ({}));
@@ -6685,7 +6685,6 @@
return {
delay: configuration.delay,
maximumRetry: configuration.maximumRetry,
- // TODO: Find out actual `error` type.
/* eslint-disable @typescript-eslint/no-explicit-any */
shouldRetry(error, attempt) {
var _a;
@@ -6699,7 +6698,6 @@
const delay = (_a = reason.retryAfter) !== null && _a !== void 0 ? _a : this.delay;
return (delay + Math.random()) * 1000;
},
- // TODO: Find out actual `error` type.
/* eslint-disable @typescript-eslint/no-explicit-any */
getGiveupReason(error, attempt) {
var _a;
diff --git a/dist/web/pubnub.worker.js b/dist/web/pubnub.worker.js
index 6470d2b27..31a3ef3ac 100644
--- a/dist/web/pubnub.worker.js
+++ b/dist/web/pubnub.worker.js
@@ -90,12 +90,13 @@
var uuidGenerator$1 = /*@__PURE__*/getDefaultExportFromCjs(uuidExports);
var uuidGenerator = {
- createUUID() {
- if (uuidGenerator$1.uuid) {
- return uuidGenerator$1.uuid();
- }
- return uuidGenerator$1();
- },
+ createUUID() {
+ if (uuidGenerator$1.uuid) {
+ return uuidGenerator$1.uuid();
+ }
+ // @ts-expect-error Depending on module type it may be callable.
+ return uuidGenerator$1();
+ },
};
///
@@ -208,6 +209,11 @@
markRequestCompleted(clients, requestOrId.identifier);
});
};
+ /**
+ * Handle client request to leave request.
+ *
+ * @param event - Leave event details.
+ */
const handleSendLeaveRequestEvent = (event) => {
const data = event.data;
const request = leaveTransportRequestFromEvent(data);
@@ -222,7 +228,10 @@
result.url = `${data.request.origin}${data.request.path}`;
result.clientIdentifier = data.clientIdentifier;
result.identifier = data.request.identifier;
- publishClientEvent(event.source.id, result);
+ publishClientEvent(event.source.id, result).then((sent) => {
+ if (sent)
+ invalidateClient(client.subscriptionKey, client.clientIdentifier, client.userId);
+ });
return;
}
sendRequest(request, () => [client], (clients, response) => {
@@ -504,10 +513,11 @@
* @param event - Service worker event object.
*/
const publishClientEvent = (identifier, event) => {
- self.clients.get(identifier).then((client) => {
+ return self.clients.get(identifier).then((client) => {
if (!client)
- return;
+ return false;
client.postMessage(event);
+ return true;
});
};
/**
@@ -559,7 +569,10 @@
const { request: clientRequest } = client.subscription;
const decidedRequest = clientRequest !== null && clientRequest !== void 0 ? clientRequest : request;
if (client.logVerbosity && serviceWorkerClientId && decidedRequest) {
- publishClientEvent(serviceWorkerClientId, Object.assign(Object.assign({}, event), { clientIdentifier: client.clientIdentifier, url: `${decidedRequest.origin}${decidedRequest.path}`, query: decidedRequest.queryParameters }));
+ publishClientEvent(serviceWorkerClientId, Object.assign(Object.assign({}, event), { clientIdentifier: client.clientIdentifier, url: `${decidedRequest.origin}${decidedRequest.path}`, query: decidedRequest.queryParameters })).then((sent) => {
+ if (sent)
+ invalidateClient(client.subscriptionKey, client.clientIdentifier, client.userId);
+ });
}
});
};
@@ -590,7 +603,10 @@
const { request: clientRequest } = client.subscription;
const decidedRequest = clientRequest !== null && clientRequest !== void 0 ? clientRequest : request;
if (serviceWorkerClientId && decidedRequest) {
- publishClientEvent(serviceWorkerClientId, Object.assign(Object.assign({}, result), { clientIdentifier: client.clientIdentifier, identifier: decidedRequest.identifier, url: `${decidedRequest.origin}${decidedRequest.path}` }));
+ publishClientEvent(serviceWorkerClientId, Object.assign(Object.assign({}, result), { clientIdentifier: client.clientIdentifier, identifier: decidedRequest.identifier, url: `${decidedRequest.origin}${decidedRequest.path}` })).then((sent) => {
+ if (sent)
+ invalidateClient(client.subscriptionKey, client.clientIdentifier, client.userId);
+ });
}
});
};
@@ -696,7 +712,6 @@
userId: query.uuid,
authKey: ((_c = query.auth) !== null && _c !== void 0 ? _c : ''),
logVerbosity: information.logVerbosity,
- lastAvailabilityCheck: new Date().getTime(),
subscription: {
path: !isPresenceLeave ? information.request.path : '',
channelGroupQuery: !isPresenceLeave ? channelGroupQuery : '',
@@ -728,7 +743,6 @@
client.subscription.filterExpression = ((_o = query['filter-expr']) !== null && _o !== void 0 ? _o : '');
client.subscription.previousTimetoken = client.subscription.timetoken;
client.subscription.timetoken = ((_p = query.tt) !== null && _p !== void 0 ? _p : '0');
- client.lastAvailabilityCheck = new Date().getTime();
client.subscription.request = information.request;
client.authKey = ((_q = query.auth) !== null && _q !== void 0 ? _q : '');
client.userId = query.uuid;
@@ -759,6 +773,40 @@
}
}
};
+ /**
+ * Clean up resources used by registered PubNub client instance.
+ *
+ * @param subscriptionKey - Subscription key which has been used by the
+ * invalidated instance.
+ * @param clientId - Unique PubNub client identifier.
+ * @param userId - Unique identifier of the user used by PubNub client instance.
+ */
+ const invalidateClient = (subscriptionKey, clientId, userId) => {
+ delete pubNubClients[clientId];
+ let clients = pubNubClientsBySubscriptionKey[subscriptionKey];
+ if (clients) {
+ // Clean up linkage between client and subscription key.
+ clients = clients.filter((client) => client.clientIdentifier !== clientId);
+ if (clients.length > 0)
+ pubNubClientsBySubscriptionKey[subscriptionKey] = clients;
+ else
+ delete pubNubClientsBySubscriptionKey[subscriptionKey];
+ // Clean up presence state information if not in use anymore.
+ if (clients.length === 0)
+ delete presenceState[subscriptionKey];
+ // Clean up service workers client linkage to PubNub clients.
+ if (clients.length > 0) {
+ const workerClients = serviceWorkerClients[subscriptionKey];
+ if (workerClients) {
+ delete workerClients[clientId];
+ if (Object.keys(workerClients).length === 0)
+ delete serviceWorkerClients[subscriptionKey];
+ }
+ }
+ else
+ delete serviceWorkerClients[subscriptionKey];
+ }
+ };
/**
* Validate received event payload.
*/
diff --git a/dist/web/pubnub.worker.min.js b/dist/web/pubnub.worker.min.js
index 90002fb5b..864385802 100644
--- a/dist/web/pubnub.worker.min.js
+++ b/dist/web/pubnub.worker.min.js
@@ -1,2 +1,2 @@
!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";function e(e,t,n,i){return new(n||(n=Promise))((function(s,r){function o(e){try{c(i.next(e))}catch(e){r(e)}}function u(e){try{c(i.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?s(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,u)}c((i=i.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function t(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var n,i,s={exports:{}};
-/*! lil-uuid - v0.1 - MIT License - https://github.com/lil-js/uuid */n=s,function(e){var t="0.1.0",n={3:/^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i,4:/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,5:/^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,all:/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i};function i(){var e,t,n="";for(e=0;e<32;e++)t=16*Math.random()|0,8!==e&&12!==e&&16!==e&&20!==e||(n+="-"),n+=(12===e?4:16===e?3&t|8:t).toString(16);return n}function s(e,t){var i=n[t||"all"];return i&&i.test(e)||!1}i.isUUID=s,i.VERSION=t,e.uuid=i,e.isUUID=s}(i=s.exports),null!==n&&(n.exports=i.uuid);var r=t(s.exports),o={createUUID:()=>r.uuid?r.uuid():r()};const u=new TextDecoder,c=new Map,l={},a={},d={},p={},f={};self.addEventListener("activate",(e=>{e.waitUntil(self.clients.claim())})),self.addEventListener("message",(e=>{if(!x(e))return;const t=e.data;"send-request"===t.type?t.request.path.startsWith("/v2/subscribe")?(k(e),h(t)):(l[t.clientIdentifier]||k(e),b(e)):"cancel-request"===t.type&&v(t)}));const h=e=>{const t=j(e),n=l[e.clientIdentifier];n&&w("start",[n],(new Date).toISOString()),"string"!=typeof t?(e.request.cancellable&&c.set(t.identifier,new AbortController),g(t,(()=>q(t.identifier)),((e,n)=>{E(e,n),m(e,t.identifier)}),((e,n)=>{E(e,null,t,T(n)),m(e,t.identifier)}))):n&&(n.subscription.previousTimetoken=n.subscription.timetoken,n.subscription.timetoken=f[t].timetoken,n.subscription.serviceRequestId=t)},b=e=>{const t=e.data,n=O(t),i=l[t.clientIdentifier];if(i){if(!n){const n=(new TextEncoder).encode('{"status": 200, "action": "leave", "message": "OK", "service":"Presence"}'),i=new Headers({"Content-Type":'text/javascript; charset="UTF-8"',"Content-Length":"74"}),s=new Response(n,{status:200,headers:i}),r=S([s,n]);return r.url=`${t.request.origin}${t.request.path}`,r.clientIdentifier=t.clientIdentifier,r.identifier=t.request.identifier,void A(e.source.id,r)}g(n,(()=>[i]),((e,n)=>{E(e,n,t.request)}),((e,n)=>{E(e,null,t.request,T(n))}))}},v=e=>{const t=l[e.clientIdentifier],n=t?t.subscription.serviceRequestId:void 0;if(t&&n&&(delete t.subscription.serviceRequestId,delete t.subscription.request,0===q(n).length)){const e=c.get(n);c.delete(n),delete f[n],e&&e.abort()}},g=(t,n,i,s)=>{e(void 0,void 0,void 0,(function*(){var e;const r=(new Date).getTime();Promise.race([fetch(I(t),{signal:null===(e=c.get(t.identifier))||void 0===e?void 0:e.signal,keepalive:!0}),y(t.identifier,t.timeout)]).then((e=>e.arrayBuffer().then((t=>[e,t])))).then((e=>{const s=e[1].byteLength>0?e[1]:void 0,o=n();0!==o.length&&(w("end",o,(new Date).toISOString(),t,s,e[0].headers.get("Content-Type"),(new Date).getTime()-r),i(o,e))})).catch((e=>{const t=n();0!==t.length&&s(t,e)}))}))},y=(e,t)=>new Promise(((n,i)=>{const s=setTimeout((()=>{c.delete(e),clearTimeout(s),i(new Error("Request timeout"))}),1e3*t)})),q=e=>Object.values(l).filter((t=>void 0!==t&&t.subscription.serviceRequestId===e)),m=(e,t)=>{delete f[t],e.forEach((e=>{delete e.subscription.request,delete e.subscription.serviceRequestId}))},I=e=>{let t;const n=e.queryParameters;let i=e.path;if(e.headers){t={};for(const[n,i]of Object.entries(e.headers))t[n]=i}return n&&0!==Object.keys(n).length&&(i=`${i}?${U(n)}`),new Request(`${e.origin}${i}`,{method:e.method,headers:t,redirect:"follow"})},j=e=>{var t,n,i,s;const r=l[e.clientIdentifier],u=R(r.subscription.previousTimetoken,e),c=o.createUUID(),a=Object.assign({},e.request);if(u.length>1){const s=F(u,e);if(s)return s;const o=(null!==(t=d[r.subscriptionKey])&&void 0!==t?t:{})[r.userId],l={},p=new Set(r.subscription.channelGroups),h=new Set(r.subscription.channels);o&&r.subscription.objectsWithState.length&&r.subscription.objectsWithState.forEach((e=>{const t=o[e];t&&(l[e]=t)}));for(const e of u){const{subscription:t}=e;t.serviceRequestId||(t.channelGroups.forEach(p.add,p),t.channels.forEach(h.add,h),t.serviceRequestId=c,o&&t.objectsWithState.forEach((e=>{const t=o[e];t&&!l[e]&&(l[e]=t)})))}const b=null!==(n=f[c])&&void 0!==n?n:f[c]={requestId:c,timetoken:null!==(i=a.queryParameters.tt)&&void 0!==i?i:"0",channelGroups:[],channels:[]};if(h.size){b.channels=Array.from(h).sort();const e=a.path.split("/");e[4]=b.channels.join(","),a.path=e.join("/")}p.size&&(b.channelGroups=Array.from(p).sort(),a.queryParameters["channel-group"]=b.channelGroups.join(",")),Object.keys(l).length&&(a.queryParameters.state=JSON.stringify(l))}else f[c]={requestId:c,timetoken:null!==(s=a.queryParameters.tt)&&void 0!==s?s:"0",channelGroups:r.subscription.channelGroups,channels:r.subscription.channels};return r.subscription.serviceRequestId=c,a.identifier=c,a},O=e=>{const t=l[e.clientIdentifier],n=K(e);let i=G(e.request),s=$(e.request);const r=Object.assign({},e.request);if(t){const{subscription:e}=t;s.length&&(e.channels=e.channels.filter((e=>!s.includes(e)))),i.length&&(e.channelGroups=e.channelGroups.filter((e=>!i.includes(e))))}for(const t of n)t.clientIdentifier!==e.clientIdentifier&&(s.length&&(s=s.filter((e=>!t.subscription.channels.includes(e)))),i.length&&(i=i.filter((e=>!t.subscription.channelGroups.includes(e)))));if(0!==s.length||0!==i.length){if(s.length){const e=r.path.split("/");e[4]=s.join(","),r.path=e.join("/")}return i.length&&(r.queryParameters["channel-group"]=i.join(",")),r}},A=(e,t)=>{self.clients.get(e).then((e=>{e&&e.postMessage(t)}))},w=(e,t,n,i,s,r,o)=>{var c;if(0===t.length)return;const l=null!==(c=p[t[0].subscriptionKey])&&void 0!==c?c:{};let a;if("start"===e)a={type:"request-progress-start",clientIdentifier:"",url:"",timestamp:n};else{let e;s&&r&&(-1!==r.indexOf("text/javascript")||-1!==r.indexOf("application/json")||-1!==r.indexOf("text/plain")||-1!==r.indexOf("text/html"))&&(e=u.decode(s)),a={type:"request-progress-end",clientIdentifier:"",url:"",response:e,timestamp:n,duration:o}}t.forEach((e=>{const t=l[e.clientIdentifier],{request:n}=e.subscription,s=null!=n?n:i;e.logVerbosity&&t&&s&&A(t,Object.assign(Object.assign({},a),{clientIdentifier:e.clientIdentifier,url:`${s.origin}${s.path}`,query:s.queryParameters}))}))},E=(e,t,n,i)=>{var s;if(0===e.length)return;if(!i&&!t)return;const r=null!==(s=p[e[0].subscriptionKey])&&void 0!==s?s:{};!i&&t&&(i=t[0].status>=400?T(void 0,t):S(t)),e.forEach((e=>{const t=r[e.clientIdentifier],{request:s}=e.subscription,o=null!=s?s:n;t&&o&&A(t,Object.assign(Object.assign({},i),{clientIdentifier:e.clientIdentifier,identifier:o.identifier,url:`${o.origin}${o.path}`}))}))},S=e=>{var t;const[n,i]=e,s=i.byteLength>0?i:void 0,r=parseInt(null!==(t=n.headers.get("Content-Length"))&&void 0!==t?t:"0",10),o=n.headers.get("Content-Type"),u={};return n.headers.forEach(((e,t)=>u[t]=e.toLowerCase())),{type:"request-process-success",clientIdentifier:"",identifier:"",url:"",response:{contentLength:r,contentType:o,headers:u,status:n.status,body:s}}},T=(e,t)=>{if(t)return Object.assign(Object.assign({},S(t)),{type:"request-process-error"});let n="NETWORK_ISSUE",i="Unknown error",s="Error";return e&&e instanceof Error&&(i=e.message,s=e.name),"AbortError"===s?(i="Request aborted",n="ABORTED"):"Request timeout"===i&&(n="TIMEOUT"),{type:"request-process-error",clientIdentifier:"",identifier:"",url:"",error:{name:s,type:n,message:i}}},k=e=>{var t,n,i,s,r,o,u,c,f,h,b,v,g,y,q,m,I,j,O,A,w,E,S,T,k,x,F,R,K,P;const U=e.data,{clientIdentifier:W}=U,C=U.request.queryParameters;let D=l[W];if(D){const e=null!==(b=C["channel-group"])&&void 0!==b?b:"",t=null!==(v=C.state)&&void 0!==v?v:"";if(D.subscription.filterExpression=null!==(g=C["filter-expr"])&&void 0!==g?g:"",D.subscription.previousTimetoken=D.subscription.timetoken,D.subscription.timetoken=null!==(y=C.tt)&&void 0!==y?y:"0",D.lastAvailabilityCheck=(new Date).getTime(),D.subscription.request=U.request,D.authKey=null!==(q=C.auth)&&void 0!==q?q:"",D.userId=C.uuid,D.subscription.path!==U.request.path&&(D.subscription.path=U.request.path,D.subscription.channels=$(U.request)),D.subscription.channelGroupQuery!==e&&(D.subscription.channelGroupQuery=e,D.subscription.channelGroups=G(U.request)),t.length>0){const e=JSON.parse(t),n=null!==(I=(x=null!==(m=d[k=D.subscriptionKey])&&void 0!==m?m:d[k]={})[F=D.userId])&&void 0!==I?I:x[F]={};Object.entries(e).forEach((([e,t])=>n[e]=t));for(const t of D.subscription.objectsWithState)e[t]||delete n[t];D.subscription.objectsWithState=Object.keys(e)}else if(D.subscription.objectsWithState.length){const e=null!==(O=(K=null!==(j=d[R=D.subscriptionKey])&&void 0!==j?j:d[R]={})[P=D.userId])&&void 0!==O?O:K[P]={};for(const t of D.subscription.objectsWithState)delete e[t];D.subscription.objectsWithState=[]}}else{const b=!U.request.path.startsWith("/v2/subscribe"),v=b?"":null!==(t=C["channel-group"])&&void 0!==t?t:"",g=b?"":null!==(n=C.state)&&void 0!==n?n:"";if(D=l[W]={clientIdentifier:W,subscriptionKey:U.subscriptionKey,userId:C.uuid,authKey:null!==(i=C.auth)&&void 0!==i?i:"",logVerbosity:U.logVerbosity,lastAvailabilityCheck:(new Date).getTime(),subscription:{path:b?"":U.request.path,channelGroupQuery:b?"":v,channels:b?[]:$(U.request),channelGroups:b?[]:G(U.request),previousTimetoken:b?"0":null!==(s=C.tt)&&void 0!==s?s:"0",timetoken:b?"0":null!==(r=C.tt)&&void 0!==r?r:"0",request:b?void 0:U.request,objectsWithState:[],filterExpression:b?void 0:null!==(o=C["filter-expr"])&&void 0!==o?o:""}},!b&&g.length>0){const e=JSON.parse(g),t=null!==(c=(w=null!==(u=d[A=D.subscriptionKey])&&void 0!==u?u:d[A]={})[E=D.userId])&&void 0!==c?c:w[E]={};Object.entries(e).forEach((([e,n])=>t[e]=n)),D.subscription.objectsWithState=Object.keys(e)}const y=null!==(f=a[S=U.subscriptionKey])&&void 0!==f?f:a[S]=[];y.every((e=>e.clientIdentifier!==W))&&y.push(D),(null!==(h=p[T=U.subscriptionKey])&&void 0!==h?h:p[T]={})[W]=e.source.id}},x=e=>{if(!(e.source&&e.source instanceof Client))return!1;const t=e.data,{clientIdentifier:n,subscriptionKey:i,logVerbosity:s}=t;return void 0!==s&&"boolean"==typeof s&&(!(!n||"string"!=typeof n)&&!(!i||"string"!=typeof i))},F=(e,t)=>{var n;const i=null!==(n=t.request.queryParameters["channel-group"])&&void 0!==n?n:"",s=t.request.path;let r,o;for(const n of e){const{subscription:e}=n;if(e.serviceRequestId){if(e.path===s&&e.channelGroupQuery===i)return e.serviceRequestId;{const n=f[e.serviceRequestId];if(r||(r=G(t.request)),o||(o=$(t.request)),o.length&&!P(n.channels,o))continue;if(r.length&&!P(n.channelGroups,r))continue;return e.serviceRequestId}}}},R=(e,t)=>{var n,i,s;const r=t.request.queryParameters,o=null!==(n=r["filter-expr"])&&void 0!==n?n:"",u=null!==(i=r.auth)&&void 0!==i?i:"",c=r.uuid;return(null!==(s=a[t.subscriptionKey])&&void 0!==s?s:[]).filter((t=>t.userId===c&&t.authKey===u&&t.subscription.filterExpression===o&&("0"===e||"0"===t.subscription.previousTimetoken||t.subscription.previousTimetoken===e)))},K=e=>{var t,n;const i=e.request.queryParameters,s=null!==(t=i.auth)&&void 0!==t?t:"",r=i.uuid;return(null!==(n=a[e.subscriptionKey])&&void 0!==n?n:[]).filter((e=>e.userId===r&&e.authKey===s))},$=e=>{const t=e.path.split("/")[e.path.startsWith("/v2/subscribe/")?4:6];return","===t?[]:t.split(",").filter((e=>e.length>0))},G=e=>{var t;const n=null!==(t=e.queryParameters["channel-group"])&&void 0!==t?t:"";return 0===n.length?[]:n.split(",").filter((e=>e.length>0))},P=(e,t)=>{const n=new Set(e);return t.every(n.has,n)},U=e=>Object.keys(e).map((t=>{const n=e[t];return Array.isArray(n)?n.map((e=>`${t}=${W(e)}`)).join("&"):`${t}=${W(n)}`})).join("&"),W=e=>encodeURIComponent(e).replace(/[!~*'()]/g,(e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`))}));
+/*! lil-uuid - v0.1 - MIT License - https://github.com/lil-js/uuid */n=s,function(e){var t="0.1.0",n={3:/^[0-9A-F]{8}-[0-9A-F]{4}-3[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i,4:/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,5:/^[0-9A-F]{8}-[0-9A-F]{4}-5[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i,all:/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i};function i(){var e,t,n="";for(e=0;e<32;e++)t=16*Math.random()|0,8!==e&&12!==e&&16!==e&&20!==e||(n+="-"),n+=(12===e?4:16===e?3&t|8:t).toString(16);return n}function s(e,t){var i=n[t||"all"];return i&&i.test(e)||!1}i.isUUID=s,i.VERSION=t,e.uuid=i,e.isUUID=s}(i=s.exports),null!==n&&(n.exports=i.uuid);var r=t(s.exports),o={createUUID:()=>r.uuid?r.uuid():r()};const u=new TextDecoder,c=new Map,l={},a={},d={},p={},f={};self.addEventListener("activate",(e=>{e.waitUntil(self.clients.claim())})),self.addEventListener("message",(e=>{if(!K(e))return;const t=e.data;"send-request"===t.type?t.request.path.startsWith("/v2/subscribe")?(k(e),h(t)):(l[t.clientIdentifier]||k(e),b(e)):"cancel-request"===t.type&&v(t)}));const h=e=>{const t=j(e),n=l[e.clientIdentifier];n&&w("start",[n],(new Date).toISOString()),"string"!=typeof t?(e.request.cancellable&&c.set(t.identifier,new AbortController),g(t,(()=>q(t.identifier)),((e,n)=>{E(e,n),I(e,t.identifier)}),((e,n)=>{E(e,null,t,T(n)),I(e,t.identifier)}))):n&&(n.subscription.previousTimetoken=n.subscription.timetoken,n.subscription.timetoken=f[t].timetoken,n.subscription.serviceRequestId=t)},b=e=>{const t=e.data,n=O(t),i=l[t.clientIdentifier];if(i){if(!n){const n=(new TextEncoder).encode('{"status": 200, "action": "leave", "message": "OK", "service":"Presence"}'),s=new Headers({"Content-Type":'text/javascript; charset="UTF-8"',"Content-Length":"74"}),r=new Response(n,{status:200,headers:s}),o=S([r,n]);return o.url=`${t.request.origin}${t.request.path}`,o.clientIdentifier=t.clientIdentifier,o.identifier=t.request.identifier,void A(e.source.id,o).then((e=>{e&&x(i.subscriptionKey,i.clientIdentifier,i.userId)}))}g(n,(()=>[i]),((e,n)=>{E(e,n,t.request)}),((e,n)=>{E(e,null,t.request,T(n))}))}},v=e=>{const t=l[e.clientIdentifier],n=t?t.subscription.serviceRequestId:void 0;if(t&&n&&(delete t.subscription.serviceRequestId,delete t.subscription.request,0===q(n).length)){const e=c.get(n);c.delete(n),delete f[n],e&&e.abort()}},g=(t,n,i,s)=>{e(void 0,void 0,void 0,(function*(){var e;const r=(new Date).getTime();Promise.race([fetch(m(t),{signal:null===(e=c.get(t.identifier))||void 0===e?void 0:e.signal,keepalive:!0}),y(t.identifier,t.timeout)]).then((e=>e.arrayBuffer().then((t=>[e,t])))).then((e=>{const s=e[1].byteLength>0?e[1]:void 0,o=n();0!==o.length&&(w("end",o,(new Date).toISOString(),t,s,e[0].headers.get("Content-Type"),(new Date).getTime()-r),i(o,e))})).catch((e=>{const t=n();0!==t.length&&s(t,e)}))}))},y=(e,t)=>new Promise(((n,i)=>{const s=setTimeout((()=>{c.delete(e),clearTimeout(s),i(new Error("Request timeout"))}),1e3*t)})),q=e=>Object.values(l).filter((t=>void 0!==t&&t.subscription.serviceRequestId===e)),I=(e,t)=>{delete f[t],e.forEach((e=>{delete e.subscription.request,delete e.subscription.serviceRequestId}))},m=e=>{let t;const n=e.queryParameters;let i=e.path;if(e.headers){t={};for(const[n,i]of Object.entries(e.headers))t[n]=i}return n&&0!==Object.keys(n).length&&(i=`${i}?${W(n)}`),new Request(`${e.origin}${i}`,{method:e.method,headers:t,redirect:"follow"})},j=e=>{var t,n,i,s;const r=l[e.clientIdentifier],u=R(r.subscription.previousTimetoken,e),c=o.createUUID(),a=Object.assign({},e.request);if(u.length>1){const s=F(u,e);if(s)return s;const o=(null!==(t=d[r.subscriptionKey])&&void 0!==t?t:{})[r.userId],l={},p=new Set(r.subscription.channelGroups),h=new Set(r.subscription.channels);o&&r.subscription.objectsWithState.length&&r.subscription.objectsWithState.forEach((e=>{const t=o[e];t&&(l[e]=t)}));for(const e of u){const{subscription:t}=e;t.serviceRequestId||(t.channelGroups.forEach(p.add,p),t.channels.forEach(h.add,h),t.serviceRequestId=c,o&&t.objectsWithState.forEach((e=>{const t=o[e];t&&!l[e]&&(l[e]=t)})))}const b=null!==(n=f[c])&&void 0!==n?n:f[c]={requestId:c,timetoken:null!==(i=a.queryParameters.tt)&&void 0!==i?i:"0",channelGroups:[],channels:[]};if(h.size){b.channels=Array.from(h).sort();const e=a.path.split("/");e[4]=b.channels.join(","),a.path=e.join("/")}p.size&&(b.channelGroups=Array.from(p).sort(),a.queryParameters["channel-group"]=b.channelGroups.join(",")),Object.keys(l).length&&(a.queryParameters.state=JSON.stringify(l))}else f[c]={requestId:c,timetoken:null!==(s=a.queryParameters.tt)&&void 0!==s?s:"0",channelGroups:r.subscription.channelGroups,channels:r.subscription.channels};return r.subscription.serviceRequestId=c,a.identifier=c,a},O=e=>{const t=l[e.clientIdentifier],n=$(e);let i=P(e.request),s=G(e.request);const r=Object.assign({},e.request);if(t){const{subscription:e}=t;s.length&&(e.channels=e.channels.filter((e=>!s.includes(e)))),i.length&&(e.channelGroups=e.channelGroups.filter((e=>!i.includes(e))))}for(const t of n)t.clientIdentifier!==e.clientIdentifier&&(s.length&&(s=s.filter((e=>!t.subscription.channels.includes(e)))),i.length&&(i=i.filter((e=>!t.subscription.channelGroups.includes(e)))));if(0!==s.length||0!==i.length){if(s.length){const e=r.path.split("/");e[4]=s.join(","),r.path=e.join("/")}return i.length&&(r.queryParameters["channel-group"]=i.join(",")),r}},A=(e,t)=>self.clients.get(e).then((e=>!!e&&(e.postMessage(t),!0))),w=(e,t,n,i,s,r,o)=>{var c;if(0===t.length)return;const l=null!==(c=p[t[0].subscriptionKey])&&void 0!==c?c:{};let a;if("start"===e)a={type:"request-progress-start",clientIdentifier:"",url:"",timestamp:n};else{let e;s&&r&&(-1!==r.indexOf("text/javascript")||-1!==r.indexOf("application/json")||-1!==r.indexOf("text/plain")||-1!==r.indexOf("text/html"))&&(e=u.decode(s)),a={type:"request-progress-end",clientIdentifier:"",url:"",response:e,timestamp:n,duration:o}}t.forEach((e=>{const t=l[e.clientIdentifier],{request:n}=e.subscription,s=null!=n?n:i;e.logVerbosity&&t&&s&&A(t,Object.assign(Object.assign({},a),{clientIdentifier:e.clientIdentifier,url:`${s.origin}${s.path}`,query:s.queryParameters})).then((t=>{t&&x(e.subscriptionKey,e.clientIdentifier,e.userId)}))}))},E=(e,t,n,i)=>{var s;if(0===e.length)return;if(!i&&!t)return;const r=null!==(s=p[e[0].subscriptionKey])&&void 0!==s?s:{};!i&&t&&(i=t[0].status>=400?T(void 0,t):S(t)),e.forEach((e=>{const t=r[e.clientIdentifier],{request:s}=e.subscription,o=null!=s?s:n;t&&o&&A(t,Object.assign(Object.assign({},i),{clientIdentifier:e.clientIdentifier,identifier:o.identifier,url:`${o.origin}${o.path}`})).then((t=>{t&&x(e.subscriptionKey,e.clientIdentifier,e.userId)}))}))},S=e=>{var t;const[n,i]=e,s=i.byteLength>0?i:void 0,r=parseInt(null!==(t=n.headers.get("Content-Length"))&&void 0!==t?t:"0",10),o=n.headers.get("Content-Type"),u={};return n.headers.forEach(((e,t)=>u[t]=e.toLowerCase())),{type:"request-process-success",clientIdentifier:"",identifier:"",url:"",response:{contentLength:r,contentType:o,headers:u,status:n.status,body:s}}},T=(e,t)=>{if(t)return Object.assign(Object.assign({},S(t)),{type:"request-process-error"});let n="NETWORK_ISSUE",i="Unknown error",s="Error";return e&&e instanceof Error&&(i=e.message,s=e.name),"AbortError"===s?(i="Request aborted",n="ABORTED"):"Request timeout"===i&&(n="TIMEOUT"),{type:"request-process-error",clientIdentifier:"",identifier:"",url:"",error:{name:s,type:n,message:i}}},k=e=>{var t,n,i,s,r,o,u,c,f,h,b,v,g,y,q,I,m,j,O,A,w,E,S,T,k,x,K,F,R,$;const U=e.data,{clientIdentifier:W}=U,C=U.request.queryParameters;let D=l[W];if(D){const e=null!==(b=C["channel-group"])&&void 0!==b?b:"",t=null!==(v=C.state)&&void 0!==v?v:"";if(D.subscription.filterExpression=null!==(g=C["filter-expr"])&&void 0!==g?g:"",D.subscription.previousTimetoken=D.subscription.timetoken,D.subscription.timetoken=null!==(y=C.tt)&&void 0!==y?y:"0",D.subscription.request=U.request,D.authKey=null!==(q=C.auth)&&void 0!==q?q:"",D.userId=C.uuid,D.subscription.path!==U.request.path&&(D.subscription.path=U.request.path,D.subscription.channels=G(U.request)),D.subscription.channelGroupQuery!==e&&(D.subscription.channelGroupQuery=e,D.subscription.channelGroups=P(U.request)),t.length>0){const e=JSON.parse(t),n=null!==(m=(x=null!==(I=d[k=D.subscriptionKey])&&void 0!==I?I:d[k]={})[K=D.userId])&&void 0!==m?m:x[K]={};Object.entries(e).forEach((([e,t])=>n[e]=t));for(const t of D.subscription.objectsWithState)e[t]||delete n[t];D.subscription.objectsWithState=Object.keys(e)}else if(D.subscription.objectsWithState.length){const e=null!==(O=(R=null!==(j=d[F=D.subscriptionKey])&&void 0!==j?j:d[F]={})[$=D.userId])&&void 0!==O?O:R[$]={};for(const t of D.subscription.objectsWithState)delete e[t];D.subscription.objectsWithState=[]}}else{const b=!U.request.path.startsWith("/v2/subscribe"),v=b?"":null!==(t=C["channel-group"])&&void 0!==t?t:"",g=b?"":null!==(n=C.state)&&void 0!==n?n:"";if(D=l[W]={clientIdentifier:W,subscriptionKey:U.subscriptionKey,userId:C.uuid,authKey:null!==(i=C.auth)&&void 0!==i?i:"",logVerbosity:U.logVerbosity,subscription:{path:b?"":U.request.path,channelGroupQuery:b?"":v,channels:b?[]:G(U.request),channelGroups:b?[]:P(U.request),previousTimetoken:b?"0":null!==(s=C.tt)&&void 0!==s?s:"0",timetoken:b?"0":null!==(r=C.tt)&&void 0!==r?r:"0",request:b?void 0:U.request,objectsWithState:[],filterExpression:b?void 0:null!==(o=C["filter-expr"])&&void 0!==o?o:""}},!b&&g.length>0){const e=JSON.parse(g),t=null!==(c=(w=null!==(u=d[A=D.subscriptionKey])&&void 0!==u?u:d[A]={})[E=D.userId])&&void 0!==c?c:w[E]={};Object.entries(e).forEach((([e,n])=>t[e]=n)),D.subscription.objectsWithState=Object.keys(e)}const y=null!==(f=a[S=U.subscriptionKey])&&void 0!==f?f:a[S]=[];y.every((e=>e.clientIdentifier!==W))&&y.push(D),(null!==(h=p[T=U.subscriptionKey])&&void 0!==h?h:p[T]={})[W]=e.source.id}},x=(e,t,n)=>{delete l[t];let i=a[e];if(i)if(i=i.filter((e=>e.clientIdentifier!==t)),i.length>0?a[e]=i:delete a[e],0===i.length&&delete d[e],i.length>0){const n=p[e];n&&(delete n[t],0===Object.keys(n).length&&delete p[e])}else delete p[e]},K=e=>{if(!(e.source&&e.source instanceof Client))return!1;const t=e.data,{clientIdentifier:n,subscriptionKey:i,logVerbosity:s}=t;return void 0!==s&&"boolean"==typeof s&&(!(!n||"string"!=typeof n)&&!(!i||"string"!=typeof i))},F=(e,t)=>{var n;const i=null!==(n=t.request.queryParameters["channel-group"])&&void 0!==n?n:"",s=t.request.path;let r,o;for(const n of e){const{subscription:e}=n;if(e.serviceRequestId){if(e.path===s&&e.channelGroupQuery===i)return e.serviceRequestId;{const n=f[e.serviceRequestId];if(r||(r=P(t.request)),o||(o=G(t.request)),o.length&&!U(n.channels,o))continue;if(r.length&&!U(n.channelGroups,r))continue;return e.serviceRequestId}}}},R=(e,t)=>{var n,i,s;const r=t.request.queryParameters,o=null!==(n=r["filter-expr"])&&void 0!==n?n:"",u=null!==(i=r.auth)&&void 0!==i?i:"",c=r.uuid;return(null!==(s=a[t.subscriptionKey])&&void 0!==s?s:[]).filter((t=>t.userId===c&&t.authKey===u&&t.subscription.filterExpression===o&&("0"===e||"0"===t.subscription.previousTimetoken||t.subscription.previousTimetoken===e)))},$=e=>{var t,n;const i=e.request.queryParameters,s=null!==(t=i.auth)&&void 0!==t?t:"",r=i.uuid;return(null!==(n=a[e.subscriptionKey])&&void 0!==n?n:[]).filter((e=>e.userId===r&&e.authKey===s))},G=e=>{const t=e.path.split("/")[e.path.startsWith("/v2/subscribe/")?4:6];return","===t?[]:t.split(",").filter((e=>e.length>0))},P=e=>{var t;const n=null!==(t=e.queryParameters["channel-group"])&&void 0!==t?t:"";return 0===n.length?[]:n.split(",").filter((e=>e.length>0))},U=(e,t)=>{const n=new Set(e);return t.every(n.has,n)},W=e=>Object.keys(e).map((t=>{const n=e[t];return Array.isArray(n)?n.map((e=>`${t}=${C(e)}`)).join("&"):`${t}=${C(n)}`})).join("&"),C=e=>encodeURIComponent(e).replace(/[!~*'()]/g,(e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`))}));
diff --git a/src/core/components/uuid.js b/src/core/components/uuid.ts
similarity index 72%
rename from src/core/components/uuid.js
rename to src/core/components/uuid.ts
index a22f987f7..023de232a 100644
--- a/src/core/components/uuid.js
+++ b/src/core/components/uuid.ts
@@ -5,6 +5,7 @@ export default {
if (uuidGenerator.uuid) {
return uuidGenerator.uuid();
}
+ // @ts-expect-error Depending on module type it may be callable.
return uuidGenerator();
},
};
diff --git a/src/event-engine/core/retryPolicy.ts b/src/event-engine/core/retryPolicy.ts
index 9d85d5d65..ebc515ad9 100644
--- a/src/event-engine/core/retryPolicy.ts
+++ b/src/event-engine/core/retryPolicy.ts
@@ -7,7 +7,6 @@ export class RetryPolicy {
return {
delay: configuration.delay,
maximumRetry: configuration.maximumRetry,
- // TODO: Find out actual `error` type.
/* eslint-disable @typescript-eslint/no-explicit-any */
shouldRetry(error: any, attempt: number) {
if (error?.status?.statusCode === 403) {
@@ -19,7 +18,6 @@ export class RetryPolicy {
const delay = reason.retryAfter ?? this.delay;
return (delay + Math.random()) * 1000;
},
- // TODO: Find out actual `error` type.
/* eslint-disable @typescript-eslint/no-explicit-any */
getGiveupReason(error: any, attempt: number) {
if (this.maximumRetry <= attempt) {
diff --git a/src/event-engine/presence/dispatcher.ts b/src/event-engine/presence/dispatcher.ts
index 5366ae873..f2dfa0571 100644
--- a/src/event-engine/presence/dispatcher.ts
+++ b/src/event-engine/presence/dispatcher.ts
@@ -20,7 +20,6 @@ export type Dependencies = {
config: PrivateClientConfiguration;
presenceState: Record;
- // TODO: Find out actual `status` type.
/* eslint-disable @typescript-eslint/no-explicit-any */
emitStatus: (status: any) => void;
};
diff --git a/src/event-engine/presence/effects.ts b/src/event-engine/presence/effects.ts
index 3f7f21b1e..0eddde4d5 100644
--- a/src/event-engine/presence/effects.ts
+++ b/src/event-engine/presence/effects.ts
@@ -12,7 +12,6 @@ export const leave = createEffect('LEAVE', (channels: string[], groups: string[]
groups,
}));
-// TODO: Find out actual `status` type.
/* eslint-disable @typescript-eslint/no-explicit-any */
export const emitStatus = createEffect('EMIT_STATUS', (status: any) => status);
diff --git a/src/transport/service-worker/subscription-service-worker.ts b/src/transport/service-worker/subscription-service-worker.ts
index 397a9289b..970ded65a 100644
--- a/src/transport/service-worker/subscription-service-worker.ts
+++ b/src/transport/service-worker/subscription-service-worker.ts
@@ -284,11 +284,6 @@ type PubNubClientState = {
*/
logVerbosity: boolean;
- /**
- * Last time, PubNub client instance responded to the `ping` event.
- */
- lastAvailabilityCheck: number;
-
/**
* Current subscription session information.
*
@@ -518,6 +513,11 @@ const handleSendSubscribeRequestEvent = (event: SendRequestEvent) => {
);
};
+/**
+ * Handle client request to leave request.
+ *
+ * @param event - Leave event details.
+ */
const handleSendLeaveRequestEvent = (event: ExtendableMessageEvent) => {
const data = event.data as SendRequestEvent;
const request = leaveTransportRequestFromEvent(data);
@@ -533,7 +533,9 @@ const handleSendLeaveRequestEvent = (event: ExtendableMessageEvent) => {
result.clientIdentifier = data.clientIdentifier;
result.identifier = data.request.identifier;
- publishClientEvent((event.source! as Client).id, result);
+ publishClientEvent((event.source! as Client).id, result).then((sent) => {
+ if (sent) invalidateClient(client.subscriptionKey, client.clientIdentifier, client.userId);
+ });
return;
}
@@ -867,9 +869,11 @@ const leaveTransportRequestFromEvent = (event: SendRequestEvent): TransportReque
* @param event - Service worker event object.
*/
const publishClientEvent = (identifier: string, event: ServiceWorkerEvent) => {
- self.clients.get(identifier).then((client) => {
- if (!client) return;
+ return self.clients.get(identifier).then((client) => {
+ if (!client) return false;
+
client.postMessage(event);
+ return true;
});
};
@@ -939,6 +943,8 @@ const notifyRequestProcessing = (
clientIdentifier: client.clientIdentifier,
url: `${decidedRequest.origin}${decidedRequest.path}`,
query: decidedRequest.queryParameters,
+ }).then((sent) => {
+ if (sent) invalidateClient(client.subscriptionKey, client.clientIdentifier, client.userId);
});
}
});
@@ -982,6 +988,8 @@ const notifyRequestProcessingResult = (
clientIdentifier: client.clientIdentifier,
identifier: decidedRequest.identifier,
url: `${decidedRequest.origin}${decidedRequest.path}`,
+ }).then((sent) => {
+ if (sent) invalidateClient(client.subscriptionKey, client.clientIdentifier, client.userId);
});
}
});
@@ -1099,7 +1107,6 @@ const registerClientIfRequired = (event: ExtendableMessageEvent) => {
userId: query.uuid as string,
authKey: (query.auth ?? '') as string,
logVerbosity: information.logVerbosity,
- lastAvailabilityCheck: new Date().getTime(),
subscription: {
path: !isPresenceLeave ? information.request.path : '',
channelGroupQuery: !isPresenceLeave ? channelGroupQuery : '',
@@ -1134,7 +1141,6 @@ const registerClientIfRequired = (event: ExtendableMessageEvent) => {
client.subscription.filterExpression = (query['filter-expr'] ?? '') as string;
client.subscription.previousTimetoken = client.subscription.timetoken;
client.subscription.timetoken = (query.tt ?? '0') as string;
- client.lastAvailabilityCheck = new Date().getTime();
client.subscription.request = information.request;
client.authKey = (query.auth ?? '') as string;
client.userId = query.uuid as string;
@@ -1169,6 +1175,39 @@ const registerClientIfRequired = (event: ExtendableMessageEvent) => {
}
};
+/**
+ * Clean up resources used by registered PubNub client instance.
+ *
+ * @param subscriptionKey - Subscription key which has been used by the
+ * invalidated instance.
+ * @param clientId - Unique PubNub client identifier.
+ * @param userId - Unique identifier of the user used by PubNub client instance.
+ */
+const invalidateClient = (subscriptionKey: string, clientId: string, userId: string) => {
+ delete pubNubClients[clientId];
+ let clients = pubNubClientsBySubscriptionKey[subscriptionKey];
+
+ if (clients) {
+ // Clean up linkage between client and subscription key.
+ clients = clients.filter((client) => client.clientIdentifier !== clientId);
+ if (clients.length > 0) pubNubClientsBySubscriptionKey[subscriptionKey] = clients;
+ else delete pubNubClientsBySubscriptionKey[subscriptionKey];
+
+ // Clean up presence state information if not in use anymore.
+ if (clients.length === 0) delete presenceState[subscriptionKey];
+
+ // Clean up service workers client linkage to PubNub clients.
+ if (clients.length > 0) {
+ const workerClients = serviceWorkerClients[subscriptionKey];
+ if (workerClients) {
+ delete workerClients[clientId];
+
+ if (Object.keys(workerClients).length === 0) delete serviceWorkerClients[subscriptionKey];
+ }
+ } else delete serviceWorkerClients[subscriptionKey];
+ }
+};
+
/**
* Validate received event payload.
*/