@qikdev/sdk 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +796 -759
- package/package.json +1 -1
- package/src/api/qik.access.js +177 -201
- package/src/api/qik.api.js +55 -28
- package/src/api/qik.auth.js +181 -113
- package/src/api/qik.cache.js +29 -7
- package/src/api/qik.content.js +275 -147
- package/src/api/qik.core.js +19 -28
- package/src/api/qik.files.js +48 -38
- package/src/api/qik.filter.js +148 -207
- package/src/api/qik.geo.js +59 -42
- package/src/api/qik.socket.js +117 -43
- package/src/api/qik.storage.js +17 -8
- package/src/api/qik.system.js +6 -5
- package/src/api/qik.utils.js +244 -199
- package/src/version.js +1 -1
package/src/api/qik.socket.js
CHANGED
|
@@ -3,12 +3,37 @@ import { EventDispatcher } from './qik.utils.js';
|
|
|
3
3
|
///////////////////////////////////////////////////
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Realtime updates over a WebSocket. Subscribe to a channel to be told when something changes, then
|
|
7
|
+
* re-read what you need with sdk.content.get / list: content messages say THAT a record changed, not what it
|
|
8
|
+
* now contains (the read applies the viewer's field permissions). Needs a signed-in user or application
|
|
9
|
+
* token; the socket connects on first use, reconnects automatically with back-off, resubscribes its channels
|
|
10
|
+
* after reconnecting, and reconnects when the user switches organisation. Changes made from this same
|
|
11
|
+
* browser window are not echoed back to it. Subscriptions are permission-checked by the server: a channel
|
|
12
|
+
* you may not use is simply never delivered.
|
|
13
|
+
* Channels (use these names; don't invent others):
|
|
14
|
+
* '<recordID>:edit' and '<recordID>:delete' — that record saved or deleted (needs view access to it);
|
|
15
|
+
* '<type or definition key>:create' / ':edit' / ':delete' — any record of that type, e.g. 'profile:edit' (needs viewany or editany on it);
|
|
16
|
+
* '<recordID>:restore' and '<type or definition key>:restore' — restored from the trash;
|
|
17
|
+
* '<eventID>:checkins', '<profileID>:checkins', '<eventID>:assignments' — check-ins and roster assignments;
|
|
18
|
+
* '<recordID>:comment' — comments posted on a record (the full comment);
|
|
19
|
+
* '<type or definition key>:definition' — that type's fields changed;
|
|
20
|
+
* 'notifications:<your persona, profile or user id>' and 'presence:<your organisation id>'.
|
|
21
|
+
* Each message is { channel, message }. For create and edit, message is
|
|
22
|
+
* { user: { name, _id, persona }, item: { _id, meta: { type, definition, uhash, updated, deleted } }, truncated: true }:
|
|
23
|
+
* compare item.meta.uhash with your copy and refetch when it differs. For delete and restore, message.item is just the id.
|
|
24
|
+
* Connection events: sdk.socket.addEventListener('connected' | 'disconnected' | 'error' | 'message', handler);
|
|
25
|
+
* 'message' receives every incoming message on every channel.
|
|
8
26
|
* @name socket
|
|
9
27
|
* @constructor
|
|
10
28
|
* @hideconstructor
|
|
11
29
|
* @param {QikAPI} qik A reference to the parent instance of the QikCore module. This module is usually created by a QikCore instance that passes itself in as the first argument.
|
|
30
|
+
* @example
|
|
31
|
+
* const channel = await sdk.socket.channel(`${recordID}:edit`);
|
|
32
|
+
* channel.addEventListener('message', async ({ message }) => {
|
|
33
|
+
* record = await sdk.content.get(recordID);
|
|
34
|
+
* });
|
|
35
|
+
* // when finished
|
|
36
|
+
* channel.destroy();
|
|
12
37
|
*/
|
|
13
38
|
|
|
14
39
|
const QikSocket = function(qik, mode) {
|
|
@@ -63,19 +88,27 @@ const QikSocket = function(qik, mode) {
|
|
|
63
88
|
|
|
64
89
|
|
|
65
90
|
/**
|
|
66
|
-
* @description
|
|
67
|
-
* This
|
|
91
|
+
* @description Subscribes to a channel and returns a listener object for it, connecting first if needed.
|
|
92
|
+
* This is the easiest way to use the socket. Calling channel() again with the same key returns the same
|
|
93
|
+
* object, so it is shared by everything on the page that uses that key.
|
|
94
|
+
* The returned object has addEventListener('message', handler), removeEventListener('message', handler),
|
|
95
|
+
* removeAllListeners() and destroy(). Handlers receive { channel, message }. destroy() unsubscribes the
|
|
96
|
+
* channel and removes every handler on it — including other components' — so when the key may be shared,
|
|
97
|
+
* remove just your own handler instead.
|
|
68
98
|
* @alias socket.channel
|
|
69
|
-
* @param
|
|
99
|
+
* @param {String} key The channel name, e.g. `${recordID}:edit` or 'profile:create' (see the channel list above).
|
|
100
|
+
* @returns {Promise<Object>} The channel's listener object. If nobody is signed in the socket can't connect, and the returned object never receives messages.
|
|
70
101
|
* @example
|
|
71
|
-
* const
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* }
|
|
102
|
+
* const channel = await sdk.socket.channel('profile:create');
|
|
103
|
+
* async function onCreated({ message }) {
|
|
104
|
+
* const profile = await sdk.content.get(message.item._id);
|
|
105
|
+
* }
|
|
106
|
+
* channel.addEventListener('message', onCreated);
|
|
75
107
|
*
|
|
76
|
-
* //
|
|
77
|
-
*
|
|
78
|
-
*
|
|
108
|
+
* // Later: stop listening (keeps the channel for anyone else using it)
|
|
109
|
+
* channel.removeEventListener('message', onCreated);
|
|
110
|
+
* // Or: unsubscribe and remove all listeners on this channel
|
|
111
|
+
* channel.destroy();
|
|
79
112
|
*/
|
|
80
113
|
service.channel = async function(key) {
|
|
81
114
|
if (buckets[key]) {
|
|
@@ -195,7 +228,7 @@ const QikSocket = function(qik, mode) {
|
|
|
195
228
|
}
|
|
196
229
|
|
|
197
230
|
function socketError(error) {
|
|
198
|
-
console.log('[event] socketError',
|
|
231
|
+
console.log('[event] socketError', error);
|
|
199
232
|
service.debug ? console.log("[socket] Error", error) : null;
|
|
200
233
|
dispatcher.dispatch('error', error);
|
|
201
234
|
}
|
|
@@ -210,6 +243,14 @@ const QikSocket = function(qik, mode) {
|
|
|
210
243
|
if (eventData.channel) {
|
|
211
244
|
dispatcher.dispatch(eventData.channel, eventData);
|
|
212
245
|
}
|
|
246
|
+
|
|
247
|
+
// The server says this person's access changed (a role or access pass
|
|
248
|
+
// they hold was edited). Reload the session so what they can see
|
|
249
|
+
// follows; a failed reload keeps them signed in as they were.
|
|
250
|
+
const message = eventData.message;
|
|
251
|
+
if (message && message.type === 'session' && message.action === 'changed') {
|
|
252
|
+
Promise.resolve(qik.auth.sync({ keepOnError: true })).catch(function() {});
|
|
253
|
+
}
|
|
213
254
|
}
|
|
214
255
|
|
|
215
256
|
async function broadcast(data) {
|
|
@@ -247,11 +288,13 @@ const QikSocket = function(qik, mode) {
|
|
|
247
288
|
///////////////////////////////////////////////////
|
|
248
289
|
|
|
249
290
|
/**
|
|
250
|
-
* @description
|
|
291
|
+
* @description Opens the socket connection, first refreshing the access token if it has expired. Resolves once
|
|
292
|
+
* connected. Calls made while a connection is being opened share it. channel() connects for you, so this is
|
|
293
|
+
* rarely needed. Resolves with nothing (and doesn't connect) when there's no access token.
|
|
251
294
|
* @alias socket.connect
|
|
295
|
+
* @returns {Promise<WebSocket|undefined>} The underlying WebSocket once open, or undefined without a token.
|
|
252
296
|
* @example
|
|
253
|
-
*
|
|
254
|
-
*
|
|
297
|
+
* await sdk.socket.connect();
|
|
255
298
|
*/
|
|
256
299
|
service.connect = async function() {
|
|
257
300
|
if (socket) {
|
|
@@ -308,11 +351,12 @@ const QikSocket = function(qik, mode) {
|
|
|
308
351
|
|
|
309
352
|
|
|
310
353
|
/**
|
|
311
|
-
* @description
|
|
354
|
+
* @description Connects again after the connection was lost or closed; does nothing if already connected.
|
|
355
|
+
* Channels that were subscribed are subscribed again once the connection opens.
|
|
312
356
|
* @alias socket.reconnect
|
|
357
|
+
* @returns {Promise<WebSocket|undefined>} As for connect(); undefined when already connected.
|
|
313
358
|
* @example
|
|
314
|
-
*
|
|
315
|
-
*
|
|
359
|
+
* await sdk.socket.reconnect();
|
|
316
360
|
*/
|
|
317
361
|
service.reconnect = async function() {
|
|
318
362
|
if(service.connected) {
|
|
@@ -330,11 +374,12 @@ const QikSocket = function(qik, mode) {
|
|
|
330
374
|
///////////////////////////////////////////////////
|
|
331
375
|
|
|
332
376
|
/**
|
|
333
|
-
* @description
|
|
377
|
+
* @description Closes the connection without reconnecting automatically. Channel subscriptions are
|
|
378
|
+
* remembered and resent by a later connect() or reconnect(). Also available as sdk.socket.disconnect().
|
|
334
379
|
* @alias socket.close
|
|
380
|
+
* @returns {Promise<undefined>}
|
|
335
381
|
* @example
|
|
336
|
-
*
|
|
337
|
-
*
|
|
382
|
+
* await sdk.socket.close();
|
|
338
383
|
*/
|
|
339
384
|
service.close = async function() {
|
|
340
385
|
if (!socket) {
|
|
@@ -359,6 +404,13 @@ const QikSocket = function(qik, mode) {
|
|
|
359
404
|
service.autoreconnect = previousSetting;
|
|
360
405
|
}
|
|
361
406
|
|
|
407
|
+
/**
|
|
408
|
+
* @description Same as close(): closes the connection without reconnecting automatically.
|
|
409
|
+
* @alias socket.disconnect
|
|
410
|
+
* @returns {Promise<undefined>}
|
|
411
|
+
* @example
|
|
412
|
+
* await sdk.socket.disconnect();
|
|
413
|
+
*/
|
|
362
414
|
service.disconnect = service.close;
|
|
363
415
|
|
|
364
416
|
///////////////////////////////////////////////////
|
|
@@ -403,12 +455,17 @@ const QikSocket = function(qik, mode) {
|
|
|
403
455
|
///////////////////////////////////////////////////
|
|
404
456
|
|
|
405
457
|
/**
|
|
406
|
-
* @description
|
|
458
|
+
* @description Asks the server to send this channel's messages to this connection. Takes no callback: listen with
|
|
459
|
+
* sdk.socket.addEventListener(channel, handler), which receives { channel, message }. Only works while the
|
|
460
|
+
* socket is connected (otherwise it logs and does nothing) — channel() handles connecting, subscribing and
|
|
461
|
+
* listening in one step and is usually the better choice. Subscriptions made in the same moment are sent together.
|
|
407
462
|
* @alias socket.subscribe
|
|
408
|
-
* @param
|
|
463
|
+
* @param {String} channel The channel name.
|
|
464
|
+
* @returns {Promise<undefined>}
|
|
409
465
|
* @example
|
|
410
|
-
*
|
|
411
|
-
*
|
|
466
|
+
* await sdk.socket.connect();
|
|
467
|
+
* sdk.socket.subscribe(`${eventID}:checkins`);
|
|
468
|
+
* sdk.socket.addEventListener(`${eventID}:checkins`, ({ message }) => refresh());
|
|
412
469
|
*/
|
|
413
470
|
service.subscribe = async function(channel) {
|
|
414
471
|
if (!socket) {
|
|
@@ -426,12 +483,13 @@ const QikSocket = function(qik, mode) {
|
|
|
426
483
|
///////////////////////////////////////////////////
|
|
427
484
|
|
|
428
485
|
/**
|
|
429
|
-
* @description
|
|
486
|
+
* @description Tells the server to stop sending this channel's messages. Listeners added with addEventListener
|
|
487
|
+
* stay attached (remove them yourself). Only works while connected.
|
|
430
488
|
* @alias socket.unsubscribe
|
|
431
|
-
* @param
|
|
489
|
+
* @param {String} channel The channel name.
|
|
490
|
+
* @returns {Promise<undefined>}
|
|
432
491
|
* @example
|
|
433
|
-
*
|
|
434
|
-
*
|
|
492
|
+
* sdk.socket.unsubscribe(`${eventID}:checkins`);
|
|
435
493
|
*/
|
|
436
494
|
service.unsubscribe = async function(channel) {
|
|
437
495
|
if (!socket) {
|
|
@@ -452,31 +510,47 @@ const QikSocket = function(qik, mode) {
|
|
|
452
510
|
|
|
453
511
|
///////////////////////////////////////////////////
|
|
454
512
|
|
|
455
|
-
// Reconnect the socket whenever
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
// organisation's events. Reconnecting re-authenticates with
|
|
461
|
-
|
|
513
|
+
// Reconnect the socket whenever who is signed in changes: signing in,
|
|
514
|
+
// signing out, switching organisation or switching person. The WebSocket
|
|
515
|
+
// authenticates with an access token at connect time, so the connection
|
|
516
|
+
// stays bound to whoever was signed in when it opened — channels opened
|
|
517
|
+
// before sign-in stay refused, and an organisation switch keeps streaming
|
|
518
|
+
// the previous organisation's events. Reconnecting re-authenticates with
|
|
519
|
+
// the new token and replays every channel.
|
|
520
|
+
//
|
|
521
|
+
// The stored user is { session, token }; who someone is lives on the
|
|
522
|
+
// session. Only these ids are read — sessions and tokens are untouched.
|
|
523
|
+
function identityOf(user) {
|
|
524
|
+
const session = user && user.session;
|
|
525
|
+
if (!session) {
|
|
526
|
+
return '';
|
|
527
|
+
}
|
|
528
|
+
return [
|
|
529
|
+
qik.utils.id(session.organisation),
|
|
530
|
+
qik.utils.id(session.persona),
|
|
531
|
+
qik.utils.id(session._id),
|
|
532
|
+
].join('|');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let currentIdentity = identityOf(qik.auth.getCurrentUser());
|
|
462
536
|
|
|
463
537
|
qik.auth.addEventListener('change', async function(user) {
|
|
464
|
-
const
|
|
538
|
+
const identity = identityOf(user);
|
|
465
539
|
|
|
466
|
-
// Ignore auth changes that
|
|
467
|
-
// access token refreshes) so we don't churn the connection
|
|
468
|
-
if (
|
|
540
|
+
// Ignore auth changes that leave the same person signed in (e.g.
|
|
541
|
+
// routine access token refreshes) so we don't churn the connection.
|
|
542
|
+
if (identity === currentIdentity) {
|
|
469
543
|
return;
|
|
470
544
|
}
|
|
471
545
|
|
|
472
|
-
|
|
546
|
+
currentIdentity = identity;
|
|
473
547
|
|
|
474
548
|
// Nothing to reconnect if the socket was never opened.
|
|
475
549
|
if (!socket) {
|
|
476
550
|
return;
|
|
477
551
|
}
|
|
478
552
|
|
|
479
|
-
service.debug ? console.log('[socket] -
|
|
553
|
+
service.debug ? console.log('[socket] - signed-in identity changed, reconnecting') : null;
|
|
480
554
|
await service.close();
|
|
481
555
|
await service.reconnect();
|
|
482
556
|
});
|
package/src/api/qik.storage.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Where the SDK keeps the signed-in session. Not a namespace of its own: sdk.auth creates one adapter,
|
|
3
|
+
* available as sdk.auth.storageAdapter, and uses it for getCurrentUser, set and logout.
|
|
4
|
+
* LocalStorageAdapter (the default, despite its name) keeps the session in memory only, so it is lost on page
|
|
5
|
+
* reload unless your app saves and restores it with sdk.auth.set. CookieStorageAdapter (useHttpOnlyCookies)
|
|
6
|
+
* keeps the session details in memory and leaves the tokens to httpOnly cookies set by the server.
|
|
3
7
|
* @module QikStorage
|
|
4
8
|
*/
|
|
5
9
|
|
|
@@ -70,7 +74,7 @@ class StorageAdapter {
|
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
/**
|
|
73
|
-
*
|
|
77
|
+
* The default adapter. Despite the name it does not use window.localStorage: the session is held in memory.
|
|
74
78
|
*/
|
|
75
79
|
class LocalStorageAdapter extends StorageAdapter {
|
|
76
80
|
constructor() {
|
|
@@ -111,7 +115,8 @@ class LocalStorageAdapter extends StorageAdapter {
|
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
/**
|
|
114
|
-
*
|
|
118
|
+
* Adapter used with useHttpOnlyCookies: session details in memory, tokens in httpOnly cookies the server sets
|
|
119
|
+
* (so getAccessToken, getRefreshToken and getTokenExpiry always return null).
|
|
115
120
|
*/
|
|
116
121
|
class CookieStorageAdapter extends StorageAdapter {
|
|
117
122
|
constructor(options = {}) {
|
|
@@ -281,11 +286,15 @@ class CookieStorageAdapter extends StorageAdapter {
|
|
|
281
286
|
}
|
|
282
287
|
|
|
283
288
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* @param {
|
|
287
|
-
* @param {
|
|
288
|
-
* @
|
|
289
|
+
* Creates the adapter sdk.auth uses: a CookieStorageAdapter when useHttpOnlyCookies is set and the browser
|
|
290
|
+
* accepts cookies, otherwise a LocalStorageAdapter (in memory).
|
|
291
|
+
* @param {Object} [options] Configuration.
|
|
292
|
+
* @param {Boolean} [options.useHttpOnlyCookies] Use cookie mode.
|
|
293
|
+
* @param {Object} [options.cookieConfig] Cookie settings: { domain, secure, sameSite }.
|
|
294
|
+
* @returns {StorageAdapter} The adapter.
|
|
295
|
+
* @example
|
|
296
|
+
* const adapter = createStorageAdapter({ useHttpOnlyCookies: false });
|
|
297
|
+
* adapter.setUser(session);
|
|
289
298
|
*/
|
|
290
299
|
export function createStorageAdapter(options = {}) {
|
|
291
300
|
if (options.useHttpOnlyCookies) {
|
package/src/api/qik.system.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
///////////////////////////////////////////////////
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* This module provides a number of helper functions for the system
|
|
4
|
+
* Platform reference data that isn't specific to an organisation.
|
|
6
5
|
* @alias system
|
|
7
6
|
* @constructor
|
|
8
7
|
* @hideconstructor
|
|
@@ -21,10 +20,12 @@ export default function(qik) {
|
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* @alias system.countries
|
|
24
|
-
* @description
|
|
25
|
-
* and
|
|
23
|
+
* @description Retrieves every country (GET /system/countries) with its ISO codes, name, phone calling
|
|
24
|
+
* codes and currencies, e.g. to fill a country picker. Loaded once and shared by later calls.
|
|
25
|
+
* @returns {Promise<Array<Object>>} Countries, each with alpha2 (e.g. 'AU'), alpha3, name, countryCallingCodes (e.g. ['+61']) and currencies.
|
|
26
26
|
* @example
|
|
27
|
-
* const countries = await sdk.system.countries()
|
|
27
|
+
* const countries = await sdk.system.countries();
|
|
28
|
+
* const options = countries.map((country) => ({ title: country.name, value: country.alpha2 }));
|
|
28
29
|
*/
|
|
29
30
|
service.countries = async function() {
|
|
30
31
|
|