@auxilium/datalynk-client 1.5.2 โ†’ 1.6.1

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.
@@ -2,8 +2,80 @@ import {Api} from 'https://datalynk-client.primary.auxilium.world/dist/index.mjs
2
2
 
3
3
  let api = null;
4
4
  const CACHE_NAME = 'datalynk';
5
+ const STATE_CACHE = 'datalynk-app-state';
5
6
  let ONLINE = true;
6
7
  const netChan = 'BroadcastChannel' in self ? new BroadcastChannel('sw-net') : null;
8
+ let updateQueue = Promise.resolve();
9
+
10
+ function safeAsset(url) {
11
+ return ['http:', 'https:'].includes(url.protocol)
12
+ && !/\/(api|login|logout|settings)(\/|$)/i.test(url.pathname)
13
+ && ![...url.searchParams.keys()].some(key => /token|auth|secret|key|session|password/i.test(key));
14
+ }
15
+
16
+ async function activeCacheName() {
17
+ const state = await caches.open(STATE_CACHE);
18
+ const response = await state.match(self.location.origin + '/datalynk-app-active');
19
+ return response ? response.text() : CACHE_NAME;
20
+ }
21
+
22
+ async function activeCache() {
23
+ return caches.open(await activeCacheName());
24
+ }
25
+
26
+ function updateAppAssets(values) {
27
+ updateQueue = updateQueue.catch(() => {}).then(async () => {
28
+ const urls = [...new Set(values.map(value => {
29
+ try {
30
+ const url = new URL(value, self.location.origin);
31
+ url.hash = '';
32
+ return safeAsset(url) && (url.origin === self.location.origin
33
+ || !/\.(?:png|jpe?g|gif|webp|svg)(?:$|\?)/i.test(url.href)) ? url.href : null;
34
+ } catch { return null; }
35
+ }).filter(Boolean))];
36
+ if(!urls.length) return;
37
+ const previousName = await activeCacheName();
38
+ const nextName = 'datalynk-app-' + Date.now() + '-' + Math.random().toString(36).slice(2);
39
+ const previous = await caches.open(previousName);
40
+ const next = await caches.open(nextName);
41
+ try {
42
+ for(const request of await previous.keys()) {
43
+ if(new URL(request.url).pathname === new URL(urls[0]).pathname
44
+ && new URL(request.url).origin === new URL(urls[0]).origin) continue;
45
+ const saved = await previous.match(request);
46
+ if(saved && safeAsset(new URL(request.url))) await next.put(request, saved);
47
+ }
48
+ for(const url of urls) {
49
+ const response = await fetch(url, {mode: new URL(url).origin === self.location.origin ? 'same-origin' : 'no-cors'});
50
+ if((!response.ok && response.type !== 'opaque') || response.headers.has('set-cookie')
51
+ || /private|no-store/i.test(response.headers.get('cache-control') || '')
52
+ || (/\.json$/i.test(new URL(url).pathname) && !/\bpublic\b/i.test(response.headers.get('cache-control') || ''))
53
+ || (/\.(?:m?js|css)(?:$|\?)/i.test(url) && /text\/html/i.test(response.headers.get('content-type') || '')))
54
+ throw new Error('Offline app update incomplete: ' + url);
55
+ if(response.type === 'opaque' && await previous.match(url)) continue;
56
+ await next.put(url, response);
57
+ }
58
+ for(const request of await previous.keys()) {
59
+ if(new URL(request.url).pathname === new URL(urls[0]).pathname
60
+ && new URL(request.url).origin === new URL(urls[0]).origin) continue;
61
+ if(await next.match(request)) continue;
62
+ const saved = await previous.match(request);
63
+ if(saved && safeAsset(new URL(request.url))) await next.put(request, saved);
64
+ }
65
+ const state = await caches.open(STATE_CACHE);
66
+ await state.put(self.location.origin + '/datalynk-app-active', new Response(nextName));
67
+ } catch(error) {
68
+ await caches.delete(nextName);
69
+ console.warn(error);
70
+ return;
71
+ }
72
+ if(previousName !== nextName) {
73
+ try { await caches.delete(previousName); }
74
+ catch(error) { console.warn('Unable to remove previous app cache', error); }
75
+ }
76
+ });
77
+ return updateQueue;
78
+ }
7
79
 
8
80
  function broadcastStatus(online) {
9
81
  if(ONLINE === online) return;
@@ -40,7 +112,17 @@ self.addEventListener('activate', (event) => {
40
112
  event.waitUntil((async () => {
41
113
  await self.clients.claim();
42
114
 
115
+ const appCache = await activeCache();
116
+ for(const request of await appCache.keys())
117
+ if(!safeAsset(new URL(request.url))
118
+ || (/\.json$/i.test(new URL(request.url).pathname)
119
+ && !/\bpublic\b/i.test((await appCache.match(request))?.headers.get('cache-control') || '')))
120
+ await appCache.delete(request);
121
+
43
122
  const keys = await caches.keys();
123
+ const activeName = await activeCacheName();
124
+ await Promise.all(keys.filter(k => k.startsWith('datalynk-app-')
125
+ && k !== STATE_CACHE && k !== activeName).map(k => caches.delete(k)));
44
126
  await Promise.all(
45
127
  keys.filter((k) => k.startsWith('app-cache-') && k !== CACHE_NAME)
46
128
  .map((k) => caches.delete(k))
@@ -76,32 +158,45 @@ self.addEventListener('fetch', (event) => {
76
158
  }
77
159
 
78
160
  event.respondWith((async () => {
79
- const cache = await caches.open(CACHE_NAME);
80
- if(!ONLINE) {
81
- const cachedEarly = await cache.match(request);
82
- if(cachedEarly) return cachedEarly;
83
- }
84
-
161
+ const cache = await activeCache();
162
+ const url = new URL(request.url);
163
+ const appFile = request.mode === 'navigate' || ['script', 'style', 'image', 'font', 'worker'].includes(request.destination);
164
+ const staticJson = url.origin === self.location.origin && /\.json$/i.test(url.pathname) && !request.destination;
165
+ const cacheable = (appFile || staticJson) && safeAsset(url) && !request.headers.has('authorization')
166
+ && !request.headers.has('range') && (url.origin === self.location.origin || request.destination !== 'image');
85
167
  try {
86
168
  const networkResponse = await fetch(request);
87
169
  broadcastStatus(true);
88
- if(networkResponse.status === 404) { // purge stale
89
- await cache.delete(request);
90
- return networkResponse;
170
+ if((networkResponse.status === 404 || networkResponse.status >= 500) && cacheable) {
171
+ const cached = await (await activeCache()).match(request, {ignoreSearch: request.mode === 'navigate'});
172
+ if(cached) { broadcastStatus(false); return cached; }
91
173
  }
92
174
  const isCacheable = networkResponse.ok || networkResponse.type === 'opaque';
93
- if(isCacheable) {
175
+ const saved = cacheable && await cache.match(request, {ignoreSearch: request.mode === 'navigate'});
176
+ if(isCacheable && cacheable && (networkResponse.ok || !saved)
177
+ && (!staticJson || /\bpublic\b/i.test(networkResponse.headers.get('cache-control') || ''))
178
+ && !networkResponse.headers.has('set-cookie')
179
+ && !/private|no-store/i.test(networkResponse.headers.get('cache-control') || '')
180
+ && !(/\.(?:m?js|css)(?:$|\?)/i.test(url.href) && /text\/html/i.test(networkResponse.headers.get('content-type') || ''))) {
94
181
  const isOpaqueNav = networkResponse.type === 'opaque' && request.mode === 'navigate';
95
182
  if (!isOpaqueNav) {
96
- event.waitUntil(cache.put(request, networkResponse.clone()));
183
+ event.waitUntil((async () => {
184
+ const current = await activeCache();
185
+ const key = request.mode === 'navigate' ? url.origin + url.pathname : request;
186
+ await current.put(key, networkResponse.clone());
187
+ if(request.mode === 'navigate')
188
+ for(const old of await current.keys())
189
+ if(old.url !== key && new URL(old.url).origin === url.origin && new URL(old.url).pathname === url.pathname)
190
+ await current.delete(old);
191
+ })());
97
192
  }
98
193
  }
99
194
  return networkResponse;
100
195
  } catch {
101
196
  broadcastStatus(false);
102
- const cached = await cache.match(request);
197
+ const cached = cacheable && await (await activeCache()).match(request, {ignoreSearch: request.mode === 'navigate'});
103
198
  if(cached) return cached;
104
- if(request.mode === 'navigate') {
199
+ if(cacheable && request.mode === 'navigate') {
105
200
  const fallback = (await cache.match('/')) || (await cache.match('/index.html'));
106
201
  if(fallback) return fallback;
107
202
  }
@@ -116,6 +211,9 @@ self.addEventListener('fetch', (event) => {
116
211
  });
117
212
 
118
213
  self.addEventListener('message', async (event) => {
214
+ if(Array.isArray(event.data?.offlineAssets)) {
215
+ event.waitUntil(updateAppAssets(event.data.offlineAssets));
216
+ }
119
217
  if(event.data?.options) { // ๐Ÿ”ง Update settings
120
218
  api = new Api(event.data.options.url, { ...event.data.options, serviceWorker: false, replayOfflineQueue: false });
121
219
  await setSettings(event.data.options);
@@ -128,6 +226,7 @@ self.addEventListener('message', async (event) => {
128
226
  await setSettings({token});
129
227
  }
130
228
  if(event.data?.clearCache) { // ๐Ÿงน Clear cache
131
- await caches.delete(CACHE_NAME);
229
+ await caches.delete(await activeCacheName());
230
+ await caches.delete(STATE_CACHE);
132
231
  }
133
232
  });
package/dist/socket.d.ts CHANGED
@@ -43,6 +43,10 @@ export type WebRTCEvent = {
43
43
  export type SocketOptions = {
44
44
  /** Set connection URL or disable with false */
45
45
  url?: string | false;
46
+ /** Use one-time socket tickets only when explicitly enabled by a chat-capable host. */
47
+ authentication?: 'token' | 'ticket';
48
+ /** Optional realm used by backward-compatible token authentication. */
49
+ realm?: string;
46
50
  };
47
51
  /** Callback shape */
48
52
  export type SocketListener<T = SocketEvent<any>> = (event: T) => any;
@@ -55,6 +59,7 @@ export declare class Socket {
55
59
  private listeners;
56
60
  private retry?;
57
61
  private socket?;
62
+ private connectGeneration;
58
63
  open: boolean;
59
64
  constructor(api: Api, options?: SocketOptions);
60
65
  /**
@@ -76,7 +81,14 @@ export declare class Socket {
76
81
  * Connect socket client to socket server
77
82
  * @param {number} timeout Retry to connect every x seconds
78
83
  */
79
- connect(timeout?: number): void;
84
+ connect(timeout?: number): void | Promise<void>;
85
+ /**
86
+ * Ticket-authenticated connection path, used only once a chat-capable host switches
87
+ * this instance to `authentication: 'ticket'` (see Api.openChat). Entirely separate
88
+ * from the token path above so existing non-chat consumers are never affected.
89
+ */
90
+ private connectWithTicket;
91
+ private scheduleReconnect;
80
92
  /**
81
93
  * Send data to socket server
82
94
  *
@@ -91,5 +103,6 @@ export declare class Socket {
91
103
  * @return {Unsubscribe} Run returned function to unsubscribe callback
92
104
  */
93
105
  sliceEvents(slice: number | number[] | string | string[], callback: SocketListener<SocketEventSlice>): Unsubscribe;
106
+ private normalizeUrl;
94
107
  }
95
108
  //# sourceMappingURL=socket.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,OAAO,CAAC;AAE1B,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,CAAA;CACP,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,GACvC,CAAC;IAAC,SAAS,EAAE;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAA;CAAC,GAC7C;IAAC,YAAY,EAAE;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAA;CAAC,GAC/C;IAAC,KAAK,EAAE;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,yBAAyB,CAAA;KAAC,CAAA;CAAC,GACtF;IAAC,MAAM,EAAE;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,yBAAyB,CAAA;KAAC,CAAA;CAAC,CAAC,CAAC;AAE1F,yBAAyB;AACzB,MAAM,MAAM,aAAa,GAAG;IAC3B,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;CACrB,CAAA;AAED,qBAAqB;AACrB,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC;AACrE,8CAA8C;AAC9C,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC,iCAAiC;AACjC,qBAAa,MAAM;IAON,OAAO,CAAC,QAAQ,CAAC,GAAG;aAAuB,OAAO,EAAE,aAAa;IAN7E,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,KAAK,CAAC,CAAM;IACpB,OAAO,CAAC,MAAM,CAAC,CAAY;IAE3B,IAAI,UAAS;gBAEgB,GAAG,EAAE,GAAG,EAAkB,OAAO,GAAE,aAAkB;IAWlF;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,IAAI,GAAG,WAAW;IAOnE;;OAEG;IACH,KAAK;IASL;;;OAGG;IACH,OAAO,CAAC,OAAO,GAAE,MAAW;IAgC5B;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,GAAG;IAKjB;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,gBAAgB,CAAC,GAAG,WAAW;CAUlH"}
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,GAAG,EAAC,MAAM,OAAO,CAAC;AAE1B,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,CAAA;CACP,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,WAAW,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,GACvC,CAAC;IAAC,SAAS,EAAE;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAA;CAAC,GAC7C;IAAC,YAAY,EAAE;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAC,CAAA;CAAC,GAC/C;IAAC,KAAK,EAAE;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,yBAAyB,CAAA;KAAC,CAAA;CAAC,GACtF;IAAC,MAAM,EAAE;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,yBAAyB,CAAA;KAAC,CAAA;CAAC,CAAC,CAAC;AAE1F,yBAAyB;AACzB,MAAM,MAAM,aAAa,GAAG;IAC3B,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACrB,uFAAuF;IACvF,cAAc,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACpC,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,CAAA;AAED,qBAAqB;AACrB,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC;AACrE,8CAA8C;AAC9C,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC,iCAAiC;AACjC,qBAAa,MAAM;IAQN,OAAO,CAAC,QAAQ,CAAC,GAAG;aAAuB,OAAO,EAAE,aAAa;IAP7E,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,KAAK,CAAC,CAAM;IACpB,OAAO,CAAC,MAAM,CAAC,CAAY;IAC3B,OAAO,CAAC,iBAAiB,CAAK;IAE9B,IAAI,UAAS;gBAEgB,GAAG,EAAE,GAAG,EAAkB,OAAO,GAAE,aAAkB;IAWlF;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,IAAI,GAAG,WAAW;IAOnE;;OAEG;IACH,KAAK;IAUL;;;OAGG;IACH,OAAO,CAAC,OAAO,GAAE,MAAW;IAiC5B;;;;OAIG;YACW,iBAAiB;IA6D/B,OAAO,CAAC,iBAAiB;IASzB;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,GAAG;IAKjB;;;;;;OAMG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC,gBAAgB,CAAC,GAAG,WAAW;IAWlH,OAAO,CAAC,YAAY;CAUpB"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@auxilium/datalynk-client",
3
3
  "description": "Datalynk client library",
4
4
  "repository": "https://gitlab.auxiliumgroup.com/auxilium/datalynk/datalynk-client",
5
- "version": "1.5.2",
5
+ "version": "1.6.1",
6
6
  "author": "Auxilium Group <devops@auxiliumgroup.com>",
7
7
  "private": false,
8
8
  "main": "./dist/index.cjs",
@@ -13,13 +13,18 @@
13
13
  "types": "./dist/index.d.ts",
14
14
  "import": "./dist/index.mjs",
15
15
  "require": "./dist/index.cjs"
16
+ },
17
+ "./chat": {
18
+ "types": "./dist/chat-entry.d.ts",
19
+ "import": "./dist/chat.mjs",
20
+ "require": "./dist/chat.cjs"
16
21
  }
17
22
  },
18
23
  "bin": {
19
24
  "datalynk-models": "./bin/datalynk-models.mjs"
20
25
  },
21
26
  "scripts": {
22
- "build": "tsc && npx vite build",
27
+ "build": "tsc && npx vite build && npx vite build --config vite.chat.config.ts",
23
28
  "postbuild": "node -e \"const fs=require('fs');fs.cpSync('src/service.worker.mjs','dist/service.worker.mjs')\"",
24
29
  "test": "npm run build && node --test test/*.test.mjs",
25
30
  "datalynk-models": "node --no-warnings ./bin/datalynk-models.mjs",