@supabase/realtime-js 1.3.6 → 1.4.0

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.
@@ -0,0 +1,92 @@
1
+ import { PresenceOpts, PresenceOnJoinCallback, PresenceOnLeaveCallback } from 'phoenix';
2
+ import RealtimeSubscription from './RealtimeSubscription';
3
+ declare type Presence = {
4
+ presence_id: string;
5
+ [key: string]: any;
6
+ };
7
+ declare type PresenceState = {
8
+ [key: string]: Presence[];
9
+ };
10
+ declare type PresenceDiff = {
11
+ joins: PresenceState;
12
+ leaves: PresenceState;
13
+ };
14
+ declare type RawPresenceState = {
15
+ [key: string]: Record<'metas', {
16
+ phx_ref?: string;
17
+ phx_ref_prev?: string;
18
+ [key: string]: any;
19
+ }[]>;
20
+ };
21
+ declare type RawPresenceDiff = {
22
+ joins: RawPresenceState;
23
+ leaves: RawPresenceState;
24
+ };
25
+ declare type PresenceChooser<T> = (key: string, presences: any) => T;
26
+ export default class RealtimePresence {
27
+ channel: RealtimeSubscription;
28
+ state: PresenceState;
29
+ pendingDiffs: RawPresenceDiff[];
30
+ joinRef: string | null;
31
+ caller: {
32
+ onJoin: PresenceOnJoinCallback;
33
+ onLeave: PresenceOnLeaveCallback;
34
+ onSync: () => void;
35
+ };
36
+ /**
37
+ * Initializes the Presence
38
+ * @param channel - The RealtimeSubscription
39
+ * @param opts - The options,
40
+ * for example `{events: {state: 'state', diff: 'diff'}}`
41
+ */
42
+ constructor(channel: RealtimeSubscription, opts?: PresenceOpts);
43
+ /**
44
+ * Used to sync the list of presences on the server
45
+ * with the client's state. An optional `onJoin` and `onLeave` callback can
46
+ * be provided to react to changes in the client's local presences across
47
+ * disconnects and reconnects with the server.
48
+ */
49
+ static syncState(currentState: PresenceState, newState: RawPresenceState | PresenceState, onJoin: PresenceOnJoinCallback, onLeave: PresenceOnLeaveCallback): PresenceState;
50
+ /**
51
+ *
52
+ * Used to sync a diff of presence join and leave
53
+ * events from the server, as they happen. Like `syncState`, `syncDiff`
54
+ * accepts optional `onJoin` and `onLeave` callbacks to react to a user
55
+ * joining or leaving from a device.
56
+ */
57
+ static syncDiff(state: PresenceState, diff: RawPresenceDiff | PresenceDiff, onJoin: PresenceOnJoinCallback, onLeave: PresenceOnLeaveCallback): PresenceState;
58
+ /**
59
+ * Returns the array of presences, with selected metadata.
60
+ */
61
+ static list<T = any>(presences: PresenceState, chooser: PresenceChooser<T> | undefined): T[];
62
+ private static map;
63
+ /**
64
+ * Remove 'metas' key
65
+ * Change 'phx_ref' to 'presence_id'
66
+ * Remove 'phx_ref' and 'phx_ref_prev'
67
+ *
68
+ * @example
69
+ * // returns {
70
+ * abc123: [
71
+ * { presence_id: '2', user_id: 1 },
72
+ * { presence_id: '3', user_id: 2 }
73
+ * ]
74
+ * }
75
+ * RealtimePresence.transformState({
76
+ * abc123: {
77
+ * metas: [
78
+ * { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
79
+ * { phx_ref: '3', user_id: 2 }
80
+ * ]
81
+ * }
82
+ * })
83
+ */
84
+ private static transformState;
85
+ onJoin(callback: PresenceOnJoinCallback): void;
86
+ onLeave(callback: PresenceOnLeaveCallback): void;
87
+ onSync(callback: () => void): void;
88
+ list<T = any>(by?: PresenceChooser<T>): T[];
89
+ private inPendingSyncState;
90
+ }
91
+ export {};
92
+ //# sourceMappingURL=RealtimePresence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RealtimePresence.d.ts","sourceRoot":"","sources":["../../src/RealtimePresence.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,SAAS,CAAA;AAChB,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AAEzD,aAAK,QAAQ,GAAG;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,CAAA;AAED,aAAK,aAAa,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAA;CAAE,CAAA;AAElD,aAAK,YAAY,GAAG;IAClB,KAAK,EAAE,aAAa,CAAA;IACpB,MAAM,EAAE,aAAa,CAAA;CACtB,CAAA;AAED,aAAK,gBAAgB,GAAG;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CACnB,OAAO,EACP;QACE,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KACnB,EAAE,CACJ,CAAA;CACF,CAAA;AAED,aAAK,eAAe,GAAG;IACrB,KAAK,EAAE,gBAAgB,CAAA;IACvB,MAAM,EAAE,gBAAgB,CAAA;CACzB,CAAA;AAED,aAAK,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC,CAAA;AAE5D,MAAM,CAAC,OAAO,OAAO,gBAAgB;IAoBhB,OAAO,EAAE,oBAAoB;IAnBhD,KAAK,EAAE,aAAa,CAAK;IACzB,YAAY,EAAE,eAAe,EAAE,CAAK;IACpC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAO;IAC7B,MAAM,EAAE;QACN,MAAM,EAAE,sBAAsB,CAAA;QAC9B,OAAO,EAAE,uBAAuB,CAAA;QAChC,MAAM,EAAE,MAAM,IAAI,CAAA;KACnB,CAIA;IAED;;;;;OAKG;gBACgB,OAAO,EAAE,oBAAoB,EAAE,IAAI,CAAC,EAAE,YAAY;IAkDrE;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CACd,YAAY,EAAE,aAAa,EAC3B,QAAQ,EAAE,gBAAgB,GAAG,aAAa,EAC1C,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,uBAAuB,GAC/B,aAAa;IA0ChB;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,CACb,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,eAAe,GAAG,YAAY,EACpC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,uBAAuB,GAC/B,aAAa;IAoDhB;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,EACjB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,GACtC,CAAC,EAAE;IAUN,OAAO,CAAC,MAAM,CAAC,GAAG;IAOlB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAyB7B,MAAM,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAI9C,OAAO,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAIhD,MAAM,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAIlC,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;IAI3C,OAAO,CAAC,kBAAkB;CAG3B"}
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ /*
3
+ This file draws heavily from https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/assets/js/phoenix/presence.js
4
+ License: https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/LICENSE.md
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const lodash_clonedeep_1 = __importDefault(require("lodash.clonedeep"));
11
+ class RealtimePresence {
12
+ /**
13
+ * Initializes the Presence
14
+ * @param channel - The RealtimeSubscription
15
+ * @param opts - The options,
16
+ * for example `{events: {state: 'state', diff: 'diff'}}`
17
+ */
18
+ constructor(channel, opts) {
19
+ this.channel = channel;
20
+ this.state = {};
21
+ this.pendingDiffs = [];
22
+ this.joinRef = null;
23
+ this.caller = {
24
+ onJoin: () => { },
25
+ onLeave: () => { },
26
+ onSync: () => { },
27
+ };
28
+ const events = (opts === null || opts === void 0 ? void 0 : opts.events) || {
29
+ state: 'presence_state',
30
+ diff: 'presence_diff',
31
+ };
32
+ this.channel.on(events.state, (newState) => {
33
+ const { onJoin, onLeave, onSync } = this.caller;
34
+ this.joinRef = this.channel.joinRef();
35
+ this.state = RealtimePresence.syncState(this.state, newState, onJoin, onLeave);
36
+ this.pendingDiffs.forEach((diff) => {
37
+ this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
38
+ });
39
+ this.pendingDiffs = [];
40
+ onSync();
41
+ });
42
+ this.channel.on(events.diff, (diff) => {
43
+ const { onJoin, onLeave, onSync } = this.caller;
44
+ if (this.inPendingSyncState()) {
45
+ this.pendingDiffs.push(diff);
46
+ }
47
+ else {
48
+ this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
49
+ onSync();
50
+ }
51
+ });
52
+ }
53
+ /**
54
+ * Used to sync the list of presences on the server
55
+ * with the client's state. An optional `onJoin` and `onLeave` callback can
56
+ * be provided to react to changes in the client's local presences across
57
+ * disconnects and reconnects with the server.
58
+ */
59
+ static syncState(currentState, newState, onJoin, onLeave) {
60
+ const state = lodash_clonedeep_1.default(currentState);
61
+ const transformedState = this.transformState(newState);
62
+ const joins = {};
63
+ const leaves = {};
64
+ this.map(state, (key, presences) => {
65
+ if (!transformedState[key]) {
66
+ leaves[key] = presences;
67
+ }
68
+ });
69
+ this.map(transformedState, (key, newPresences) => {
70
+ const currentPresences = state[key];
71
+ if (currentPresences) {
72
+ const newPresenceIds = newPresences.map((m) => m.presence_id);
73
+ const curPresenceIds = currentPresences.map((m) => m.presence_id);
74
+ const joinedPresences = newPresences.filter((m) => curPresenceIds.indexOf(m.presence_id) < 0);
75
+ const leftPresences = currentPresences.filter((m) => newPresenceIds.indexOf(m.presence_id) < 0);
76
+ if (joinedPresences.length > 0) {
77
+ joins[key] = joinedPresences;
78
+ }
79
+ if (leftPresences.length > 0) {
80
+ leaves[key] = leftPresences;
81
+ }
82
+ }
83
+ else {
84
+ joins[key] = newPresences;
85
+ }
86
+ });
87
+ return this.syncDiff(state, { joins, leaves }, onJoin, onLeave);
88
+ }
89
+ /**
90
+ *
91
+ * Used to sync a diff of presence join and leave
92
+ * events from the server, as they happen. Like `syncState`, `syncDiff`
93
+ * accepts optional `onJoin` and `onLeave` callbacks to react to a user
94
+ * joining or leaving from a device.
95
+ */
96
+ static syncDiff(state, diff, onJoin, onLeave) {
97
+ const { joins, leaves } = {
98
+ joins: this.transformState(diff.joins),
99
+ leaves: this.transformState(diff.leaves),
100
+ };
101
+ if (!onJoin) {
102
+ onJoin = () => { };
103
+ }
104
+ if (!onLeave) {
105
+ onLeave = () => { };
106
+ }
107
+ this.map(joins, (key, newPresences) => {
108
+ const currentPresences = state[key];
109
+ state[key] = lodash_clonedeep_1.default(newPresences);
110
+ if (currentPresences) {
111
+ const joinedPresenceIds = state[key].map((m) => m.presence_id);
112
+ const curPresences = currentPresences.filter((m) => joinedPresenceIds.indexOf(m.presence_id) < 0);
113
+ state[key].unshift(...curPresences);
114
+ }
115
+ onJoin(key, currentPresences, newPresences);
116
+ });
117
+ this.map(leaves, (key, leftPresences) => {
118
+ let currentPresences = state[key];
119
+ if (!currentPresences)
120
+ return;
121
+ const presenceIdsToRemove = leftPresences.map((m) => m.presence_id);
122
+ currentPresences = currentPresences.filter((m) => presenceIdsToRemove.indexOf(m.presence_id) < 0);
123
+ state[key] = currentPresences;
124
+ onLeave(key, currentPresences, leftPresences);
125
+ if (currentPresences.length === 0)
126
+ delete state[key];
127
+ });
128
+ return state;
129
+ }
130
+ /**
131
+ * Returns the array of presences, with selected metadata.
132
+ */
133
+ static list(presences, chooser) {
134
+ if (!chooser) {
135
+ chooser = (_key, pres) => pres;
136
+ }
137
+ return this.map(presences, (key, presences) => chooser(key, presences));
138
+ }
139
+ static map(obj, func) {
140
+ return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));
141
+ }
142
+ /**
143
+ * Remove 'metas' key
144
+ * Change 'phx_ref' to 'presence_id'
145
+ * Remove 'phx_ref' and 'phx_ref_prev'
146
+ *
147
+ * @example
148
+ * // returns {
149
+ * abc123: [
150
+ * { presence_id: '2', user_id: 1 },
151
+ * { presence_id: '3', user_id: 2 }
152
+ * ]
153
+ * }
154
+ * RealtimePresence.transformState({
155
+ * abc123: {
156
+ * metas: [
157
+ * { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
158
+ * { phx_ref: '3', user_id: 2 }
159
+ * ]
160
+ * }
161
+ * })
162
+ */
163
+ static transformState(state) {
164
+ state = lodash_clonedeep_1.default(state);
165
+ return Object.getOwnPropertyNames(state).reduce((newState, key) => {
166
+ const presences = state[key];
167
+ if ('metas' in presences) {
168
+ newState[key] = presences.metas.map((presence) => {
169
+ presence['presence_id'] = presence['phx_ref'];
170
+ delete presence['phx_ref'];
171
+ delete presence['phx_ref_prev'];
172
+ return presence;
173
+ });
174
+ }
175
+ else {
176
+ newState[key] = presences;
177
+ }
178
+ return newState;
179
+ }, {});
180
+ }
181
+ onJoin(callback) {
182
+ this.caller.onJoin = callback;
183
+ }
184
+ onLeave(callback) {
185
+ this.caller.onLeave = callback;
186
+ }
187
+ onSync(callback) {
188
+ this.caller.onSync = callback;
189
+ }
190
+ list(by) {
191
+ return RealtimePresence.list(this.state, by);
192
+ }
193
+ inPendingSyncState() {
194
+ return !this.joinRef || this.joinRef !== this.channel.joinRef();
195
+ }
196
+ }
197
+ exports.default = RealtimePresence;
198
+ //# sourceMappingURL=RealtimePresence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RealtimePresence.js","sourceRoot":"","sources":["../../src/RealtimePresence.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import * as Transformers from './lib/transformers';
2
2
  import RealtimeClient, { Options as RealtimeClientOptions } from './RealtimeClient';
3
3
  import RealtimeSubscription from './RealtimeSubscription';
4
- export { RealtimeClient, RealtimeClientOptions, RealtimeSubscription, Transformers, };
4
+ import RealtimePresence from './RealtimePresence';
5
+ export { RealtimeClient, RealtimeClientOptions, RealtimeSubscription, RealtimePresence, Transformers, };
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,cAAc,EAAE,EACrB,OAAO,IAAI,qBAAqB,EACjC,MAAM,kBAAkB,CAAA;AACzB,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AAEzD,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,GACb,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,cAAc,EAAE,EACrB,OAAO,IAAI,qBAAqB,EACjC,MAAM,kBAAkB,CAAA;AACzB,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AACzD,OAAO,gBAAgB,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,GACb,CAAA"}
@@ -22,11 +22,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.Transformers = exports.RealtimeSubscription = exports.RealtimeClient = void 0;
25
+ exports.Transformers = exports.RealtimePresence = exports.RealtimeSubscription = exports.RealtimeClient = void 0;
26
26
  const Transformers = __importStar(require("./lib/transformers"));
27
27
  exports.Transformers = Transformers;
28
28
  const RealtimeClient_1 = __importDefault(require("./RealtimeClient"));
29
29
  exports.RealtimeClient = RealtimeClient_1.default;
30
30
  const RealtimeSubscription_1 = __importDefault(require("./RealtimeSubscription"));
31
31
  exports.RealtimeSubscription = RealtimeSubscription_1.default;
32
+ const RealtimePresence_1 = __importDefault(require("./RealtimePresence"));
33
+ exports.RealtimePresence = RealtimePresence_1.default;
32
34
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iEAAkD;AAUhD,oCAAY;AATd,sEAEyB;AAIvB,yBANK,wBAAc,CAML;AAHhB,kFAAyD;AAKvD,+BALK,8BAAoB,CAKL"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iEAAkD;AAYhD,oCAAY;AAXd,sEAEyB;AAKvB,yBAPK,wBAAc,CAOL;AAJhB,kFAAyD;AAMvD,+BANK,8BAAoB,CAML;AALtB,0EAAiD;AAM/C,2BANK,0BAAgB,CAML"}
@@ -1,2 +1,2 @@
1
- export declare const version = "1.3.6";
1
+ export declare const version = "1.4.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.version = void 0;
4
4
  // generated by genversion
5
- exports.version = '1.3.6';
5
+ exports.version = '1.4.0';
6
6
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,92 @@
1
+ import { PresenceOpts, PresenceOnJoinCallback, PresenceOnLeaveCallback } from 'phoenix';
2
+ import RealtimeSubscription from './RealtimeSubscription';
3
+ declare type Presence = {
4
+ presence_id: string;
5
+ [key: string]: any;
6
+ };
7
+ declare type PresenceState = {
8
+ [key: string]: Presence[];
9
+ };
10
+ declare type PresenceDiff = {
11
+ joins: PresenceState;
12
+ leaves: PresenceState;
13
+ };
14
+ declare type RawPresenceState = {
15
+ [key: string]: Record<'metas', {
16
+ phx_ref?: string;
17
+ phx_ref_prev?: string;
18
+ [key: string]: any;
19
+ }[]>;
20
+ };
21
+ declare type RawPresenceDiff = {
22
+ joins: RawPresenceState;
23
+ leaves: RawPresenceState;
24
+ };
25
+ declare type PresenceChooser<T> = (key: string, presences: any) => T;
26
+ export default class RealtimePresence {
27
+ channel: RealtimeSubscription;
28
+ state: PresenceState;
29
+ pendingDiffs: RawPresenceDiff[];
30
+ joinRef: string | null;
31
+ caller: {
32
+ onJoin: PresenceOnJoinCallback;
33
+ onLeave: PresenceOnLeaveCallback;
34
+ onSync: () => void;
35
+ };
36
+ /**
37
+ * Initializes the Presence
38
+ * @param channel - The RealtimeSubscription
39
+ * @param opts - The options,
40
+ * for example `{events: {state: 'state', diff: 'diff'}}`
41
+ */
42
+ constructor(channel: RealtimeSubscription, opts?: PresenceOpts);
43
+ /**
44
+ * Used to sync the list of presences on the server
45
+ * with the client's state. An optional `onJoin` and `onLeave` callback can
46
+ * be provided to react to changes in the client's local presences across
47
+ * disconnects and reconnects with the server.
48
+ */
49
+ static syncState(currentState: PresenceState, newState: RawPresenceState | PresenceState, onJoin: PresenceOnJoinCallback, onLeave: PresenceOnLeaveCallback): PresenceState;
50
+ /**
51
+ *
52
+ * Used to sync a diff of presence join and leave
53
+ * events from the server, as they happen. Like `syncState`, `syncDiff`
54
+ * accepts optional `onJoin` and `onLeave` callbacks to react to a user
55
+ * joining or leaving from a device.
56
+ */
57
+ static syncDiff(state: PresenceState, diff: RawPresenceDiff | PresenceDiff, onJoin: PresenceOnJoinCallback, onLeave: PresenceOnLeaveCallback): PresenceState;
58
+ /**
59
+ * Returns the array of presences, with selected metadata.
60
+ */
61
+ static list<T = any>(presences: PresenceState, chooser: PresenceChooser<T> | undefined): T[];
62
+ private static map;
63
+ /**
64
+ * Remove 'metas' key
65
+ * Change 'phx_ref' to 'presence_id'
66
+ * Remove 'phx_ref' and 'phx_ref_prev'
67
+ *
68
+ * @example
69
+ * // returns {
70
+ * abc123: [
71
+ * { presence_id: '2', user_id: 1 },
72
+ * { presence_id: '3', user_id: 2 }
73
+ * ]
74
+ * }
75
+ * RealtimePresence.transformState({
76
+ * abc123: {
77
+ * metas: [
78
+ * { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
79
+ * { phx_ref: '3', user_id: 2 }
80
+ * ]
81
+ * }
82
+ * })
83
+ */
84
+ private static transformState;
85
+ onJoin(callback: PresenceOnJoinCallback): void;
86
+ onLeave(callback: PresenceOnLeaveCallback): void;
87
+ onSync(callback: () => void): void;
88
+ list<T = any>(by?: PresenceChooser<T>): T[];
89
+ private inPendingSyncState;
90
+ }
91
+ export {};
92
+ //# sourceMappingURL=RealtimePresence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RealtimePresence.d.ts","sourceRoot":"","sources":["../../src/RealtimePresence.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,SAAS,CAAA;AAChB,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AAEzD,aAAK,QAAQ,GAAG;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACnB,CAAA;AAED,aAAK,aAAa,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAA;CAAE,CAAA;AAElD,aAAK,YAAY,GAAG;IAClB,KAAK,EAAE,aAAa,CAAA;IACpB,MAAM,EAAE,aAAa,CAAA;CACtB,CAAA;AAED,aAAK,gBAAgB,GAAG;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CACnB,OAAO,EACP;QACE,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KACnB,EAAE,CACJ,CAAA;CACF,CAAA;AAED,aAAK,eAAe,GAAG;IACrB,KAAK,EAAE,gBAAgB,CAAA;IACvB,MAAM,EAAE,gBAAgB,CAAA;CACzB,CAAA;AAED,aAAK,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC,CAAA;AAE5D,MAAM,CAAC,OAAO,OAAO,gBAAgB;IAoBhB,OAAO,EAAE,oBAAoB;IAnBhD,KAAK,EAAE,aAAa,CAAK;IACzB,YAAY,EAAE,eAAe,EAAE,CAAK;IACpC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAO;IAC7B,MAAM,EAAE;QACN,MAAM,EAAE,sBAAsB,CAAA;QAC9B,OAAO,EAAE,uBAAuB,CAAA;QAChC,MAAM,EAAE,MAAM,IAAI,CAAA;KACnB,CAIA;IAED;;;;;OAKG;gBACgB,OAAO,EAAE,oBAAoB,EAAE,IAAI,CAAC,EAAE,YAAY;IAkDrE;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CACd,YAAY,EAAE,aAAa,EAC3B,QAAQ,EAAE,gBAAgB,GAAG,aAAa,EAC1C,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,uBAAuB,GAC/B,aAAa;IA0ChB;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,CACb,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,eAAe,GAAG,YAAY,EACpC,MAAM,EAAE,sBAAsB,EAC9B,OAAO,EAAE,uBAAuB,GAC/B,aAAa;IAoDhB;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,EACjB,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,GACtC,CAAC,EAAE;IAUN,OAAO,CAAC,MAAM,CAAC,GAAG;IAOlB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAyB7B,MAAM,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI;IAI9C,OAAO,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI;IAIhD,MAAM,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAIlC,IAAI,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;IAI3C,OAAO,CAAC,kBAAkB;CAG3B"}
@@ -0,0 +1,192 @@
1
+ /*
2
+ This file draws heavily from https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/assets/js/phoenix/presence.js
3
+ License: https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/LICENSE.md
4
+ */
5
+ import cloneDeep from 'lodash.clonedeep';
6
+ export default class RealtimePresence {
7
+ /**
8
+ * Initializes the Presence
9
+ * @param channel - The RealtimeSubscription
10
+ * @param opts - The options,
11
+ * for example `{events: {state: 'state', diff: 'diff'}}`
12
+ */
13
+ constructor(channel, opts) {
14
+ this.channel = channel;
15
+ this.state = {};
16
+ this.pendingDiffs = [];
17
+ this.joinRef = null;
18
+ this.caller = {
19
+ onJoin: () => { },
20
+ onLeave: () => { },
21
+ onSync: () => { },
22
+ };
23
+ const events = (opts === null || opts === void 0 ? void 0 : opts.events) || {
24
+ state: 'presence_state',
25
+ diff: 'presence_diff',
26
+ };
27
+ this.channel.on(events.state, (newState) => {
28
+ const { onJoin, onLeave, onSync } = this.caller;
29
+ this.joinRef = this.channel.joinRef();
30
+ this.state = RealtimePresence.syncState(this.state, newState, onJoin, onLeave);
31
+ this.pendingDiffs.forEach((diff) => {
32
+ this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
33
+ });
34
+ this.pendingDiffs = [];
35
+ onSync();
36
+ });
37
+ this.channel.on(events.diff, (diff) => {
38
+ const { onJoin, onLeave, onSync } = this.caller;
39
+ if (this.inPendingSyncState()) {
40
+ this.pendingDiffs.push(diff);
41
+ }
42
+ else {
43
+ this.state = RealtimePresence.syncDiff(this.state, diff, onJoin, onLeave);
44
+ onSync();
45
+ }
46
+ });
47
+ }
48
+ /**
49
+ * Used to sync the list of presences on the server
50
+ * with the client's state. An optional `onJoin` and `onLeave` callback can
51
+ * be provided to react to changes in the client's local presences across
52
+ * disconnects and reconnects with the server.
53
+ */
54
+ static syncState(currentState, newState, onJoin, onLeave) {
55
+ const state = cloneDeep(currentState);
56
+ const transformedState = this.transformState(newState);
57
+ const joins = {};
58
+ const leaves = {};
59
+ this.map(state, (key, presences) => {
60
+ if (!transformedState[key]) {
61
+ leaves[key] = presences;
62
+ }
63
+ });
64
+ this.map(transformedState, (key, newPresences) => {
65
+ const currentPresences = state[key];
66
+ if (currentPresences) {
67
+ const newPresenceIds = newPresences.map((m) => m.presence_id);
68
+ const curPresenceIds = currentPresences.map((m) => m.presence_id);
69
+ const joinedPresences = newPresences.filter((m) => curPresenceIds.indexOf(m.presence_id) < 0);
70
+ const leftPresences = currentPresences.filter((m) => newPresenceIds.indexOf(m.presence_id) < 0);
71
+ if (joinedPresences.length > 0) {
72
+ joins[key] = joinedPresences;
73
+ }
74
+ if (leftPresences.length > 0) {
75
+ leaves[key] = leftPresences;
76
+ }
77
+ }
78
+ else {
79
+ joins[key] = newPresences;
80
+ }
81
+ });
82
+ return this.syncDiff(state, { joins, leaves }, onJoin, onLeave);
83
+ }
84
+ /**
85
+ *
86
+ * Used to sync a diff of presence join and leave
87
+ * events from the server, as they happen. Like `syncState`, `syncDiff`
88
+ * accepts optional `onJoin` and `onLeave` callbacks to react to a user
89
+ * joining or leaving from a device.
90
+ */
91
+ static syncDiff(state, diff, onJoin, onLeave) {
92
+ const { joins, leaves } = {
93
+ joins: this.transformState(diff.joins),
94
+ leaves: this.transformState(diff.leaves),
95
+ };
96
+ if (!onJoin) {
97
+ onJoin = () => { };
98
+ }
99
+ if (!onLeave) {
100
+ onLeave = () => { };
101
+ }
102
+ this.map(joins, (key, newPresences) => {
103
+ const currentPresences = state[key];
104
+ state[key] = cloneDeep(newPresences);
105
+ if (currentPresences) {
106
+ const joinedPresenceIds = state[key].map((m) => m.presence_id);
107
+ const curPresences = currentPresences.filter((m) => joinedPresenceIds.indexOf(m.presence_id) < 0);
108
+ state[key].unshift(...curPresences);
109
+ }
110
+ onJoin(key, currentPresences, newPresences);
111
+ });
112
+ this.map(leaves, (key, leftPresences) => {
113
+ let currentPresences = state[key];
114
+ if (!currentPresences)
115
+ return;
116
+ const presenceIdsToRemove = leftPresences.map((m) => m.presence_id);
117
+ currentPresences = currentPresences.filter((m) => presenceIdsToRemove.indexOf(m.presence_id) < 0);
118
+ state[key] = currentPresences;
119
+ onLeave(key, currentPresences, leftPresences);
120
+ if (currentPresences.length === 0)
121
+ delete state[key];
122
+ });
123
+ return state;
124
+ }
125
+ /**
126
+ * Returns the array of presences, with selected metadata.
127
+ */
128
+ static list(presences, chooser) {
129
+ if (!chooser) {
130
+ chooser = (_key, pres) => pres;
131
+ }
132
+ return this.map(presences, (key, presences) => chooser(key, presences));
133
+ }
134
+ static map(obj, func) {
135
+ return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));
136
+ }
137
+ /**
138
+ * Remove 'metas' key
139
+ * Change 'phx_ref' to 'presence_id'
140
+ * Remove 'phx_ref' and 'phx_ref_prev'
141
+ *
142
+ * @example
143
+ * // returns {
144
+ * abc123: [
145
+ * { presence_id: '2', user_id: 1 },
146
+ * { presence_id: '3', user_id: 2 }
147
+ * ]
148
+ * }
149
+ * RealtimePresence.transformState({
150
+ * abc123: {
151
+ * metas: [
152
+ * { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
153
+ * { phx_ref: '3', user_id: 2 }
154
+ * ]
155
+ * }
156
+ * })
157
+ */
158
+ static transformState(state) {
159
+ state = cloneDeep(state);
160
+ return Object.getOwnPropertyNames(state).reduce((newState, key) => {
161
+ const presences = state[key];
162
+ if ('metas' in presences) {
163
+ newState[key] = presences.metas.map((presence) => {
164
+ presence['presence_id'] = presence['phx_ref'];
165
+ delete presence['phx_ref'];
166
+ delete presence['phx_ref_prev'];
167
+ return presence;
168
+ });
169
+ }
170
+ else {
171
+ newState[key] = presences;
172
+ }
173
+ return newState;
174
+ }, {});
175
+ }
176
+ onJoin(callback) {
177
+ this.caller.onJoin = callback;
178
+ }
179
+ onLeave(callback) {
180
+ this.caller.onLeave = callback;
181
+ }
182
+ onSync(callback) {
183
+ this.caller.onSync = callback;
184
+ }
185
+ list(by) {
186
+ return RealtimePresence.list(this.state, by);
187
+ }
188
+ inPendingSyncState() {
189
+ return !this.joinRef || this.joinRef !== this.channel.joinRef();
190
+ }
191
+ }
192
+ //# sourceMappingURL=RealtimePresence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RealtimePresence.js","sourceRoot":"","sources":["../../src/RealtimePresence.ts"],"names":[],"mappings":"AAAA;;;EAGE;AAEF,OAAO,SAAS,MAAM,kBAAkB,CAAA;AAsCxC,MAAM,CAAC,OAAO,OAAO,gBAAgB;IAcnC;;;;;OAKG;IACH,YAAmB,OAA6B,EAAE,IAAmB;QAAlD,YAAO,GAAP,OAAO,CAAsB;QAnBhD,UAAK,GAAkB,EAAE,CAAA;QACzB,iBAAY,GAAsB,EAAE,CAAA;QACpC,YAAO,GAAkB,IAAI,CAAA;QAC7B,WAAM,GAIF;YACF,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;YAChB,OAAO,EAAE,GAAG,EAAE,GAAE,CAAC;YACjB,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;SACjB,CAAA;QASC,MAAM,MAAM,GAAG,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,MAAM,KAAI;YAC7B,KAAK,EAAE,gBAAgB;YACvB,IAAI,EAAE,eAAe;SACtB,CAAA;QAED,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,QAA0B,EAAE,EAAE;YAC3D,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAA;YAE/C,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;YAErC,IAAI,CAAC,KAAK,GAAG,gBAAgB,CAAC,SAAS,CACrC,IAAI,CAAC,KAAK,EACV,QAAQ,EACR,MAAM,EACN,OAAO,CACR,CAAA;YAED,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;gBACjC,IAAI,CAAC,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CACpC,IAAI,CAAC,KAAK,EACV,IAAI,EACJ,MAAM,EACN,OAAO,CACR,CAAA;YACH,CAAC,CAAC,CAAA;YAEF,IAAI,CAAC,YAAY,GAAG,EAAE,CAAA;YAEtB,MAAM,EAAE,CAAA;QACV,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,IAAqB,EAAE,EAAE;YACrD,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAA;YAE/C,IAAI,IAAI,CAAC,kBAAkB,EAAE,EAAE;gBAC7B,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;aAC7B;iBAAM;gBACL,IAAI,CAAC,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CACpC,IAAI,CAAC,KAAK,EACV,IAAI,EACJ,MAAM,EACN,OAAO,CACR,CAAA;gBAED,MAAM,EAAE,CAAA;aACT;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,SAAS,CACd,YAA2B,EAC3B,QAA0C,EAC1C,MAA8B,EAC9B,OAAgC;QAEhC,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,CAAC,CAAA;QACrC,MAAM,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAA;QACtD,MAAM,KAAK,GAAkB,EAAE,CAAA;QAC/B,MAAM,MAAM,GAAkB,EAAE,CAAA;QAEhC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAW,EAAE,SAAqB,EAAE,EAAE;YACrD,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE;gBAC1B,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;aACxB;QACH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,GAAG,EAAE,YAAwB,EAAE,EAAE;YAC3D,MAAM,gBAAgB,GAAe,KAAK,CAAC,GAAG,CAAC,CAAA;YAE/C,IAAI,gBAAgB,EAAE;gBACpB,MAAM,cAAc,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;gBACvE,MAAM,cAAc,GAAG,gBAAgB,CAAC,GAAG,CACzC,CAAC,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAC/B,CAAA;gBACD,MAAM,eAAe,GAAe,YAAY,CAAC,MAAM,CACrD,CAAC,CAAW,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAC3D,CAAA;gBACD,MAAM,aAAa,GAAe,gBAAgB,CAAC,MAAM,CACvD,CAAC,CAAW,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAC3D,CAAA;gBAED,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE;oBAC9B,KAAK,CAAC,GAAG,CAAC,GAAG,eAAe,CAAA;iBAC7B;gBAED,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE;oBAC5B,MAAM,CAAC,GAAG,CAAC,GAAG,aAAa,CAAA;iBAC5B;aACF;iBAAM;gBACL,KAAK,CAAC,GAAG,CAAC,GAAG,YAAY,CAAA;aAC1B;QACH,CAAC,CAAC,CAAA;QAEF,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,CAAA;IACjE,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,CACb,KAAoB,EACpB,IAAoC,EACpC,MAA8B,EAC9B,OAAgC;QAEhC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG;YACxB,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC;YACtC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC;SACzC,CAAA;QAED,IAAI,CAAC,MAAM,EAAE;YACX,MAAM,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;SAClB;QAED,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;SACnB;QAED,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,YAAwB,EAAE,EAAE;YAChD,MAAM,gBAAgB,GAAe,KAAK,CAAC,GAAG,CAAC,CAAA;YAC/C,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,YAAY,CAAC,CAAA;YAEpC,IAAI,gBAAgB,EAAE;gBACpB,MAAM,iBAAiB,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;gBACxE,MAAM,YAAY,GAAe,gBAAgB,CAAC,MAAM,CACtD,CAAC,CAAW,EAAE,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAC9D,CAAA;gBAED,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,YAAY,CAAC,CAAA;aACpC;YAED,MAAM,CAAC,GAAG,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAA;QAC7C,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,aAAyB,EAAE,EAAE;YAClD,IAAI,gBAAgB,GAAe,KAAK,CAAC,GAAG,CAAC,CAAA;YAE7C,IAAI,CAAC,gBAAgB;gBAAE,OAAM;YAE7B,MAAM,mBAAmB,GAAG,aAAa,CAAC,GAAG,CAC3C,CAAC,CAAW,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,CAC/B,CAAA;YACD,gBAAgB,GAAG,gBAAgB,CAAC,MAAM,CACxC,CAAC,CAAW,EAAE,EAAE,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAChE,CAAA;YAED,KAAK,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAA;YAE7B,OAAO,CAAC,GAAG,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAA;YAE7C,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAA;QACtD,CAAC,CAAC,CAAA;QAEF,OAAO,KAAK,CAAA;IACd,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,IAAI,CACT,SAAwB,EACxB,OAAuC;QAEvC,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAA;SAC/B;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,SAAqB,EAAE,EAAE,CACxD,OAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,CACzB,CAAA;IACH,CAAC;IAEO,MAAM,CAAC,GAAG,CAChB,GAAkB,EAClB,IAAwB;QAExB,OAAO,MAAM,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAC1E,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACK,MAAM,CAAC,cAAc,CAC3B,KAAuC;QAEvC,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAA;QAExB,OAAO,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE;YAChE,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;YAE5B,IAAI,OAAO,IAAI,SAAS,EAAE;gBACxB,QAAQ,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;oBAC/C,QAAQ,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAA;oBAE7C,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAA;oBAC1B,OAAO,QAAQ,CAAC,cAAc,CAAC,CAAA;oBAE/B,OAAO,QAAQ,CAAA;gBACjB,CAAC,CAAe,CAAA;aACjB;iBAAM;gBACL,QAAQ,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;aAC1B;YAED,OAAO,QAAQ,CAAA;QACjB,CAAC,EAAE,EAAmB,CAAC,CAAA;IACzB,CAAC;IAED,MAAM,CAAC,QAAgC;QACrC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAA;IAC/B,CAAC;IAED,OAAO,CAAC,QAAiC;QACvC,IAAI,CAAC,MAAM,CAAC,OAAO,GAAG,QAAQ,CAAA;IAChC,CAAC;IAED,MAAM,CAAC,QAAoB;QACzB,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAA;IAC/B,CAAC;IAED,IAAI,CAAU,EAAuB;QACnC,OAAO,gBAAgB,CAAC,IAAI,CAAI,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IACjD,CAAC;IAEO,kBAAkB;QACxB,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAA;IACjE,CAAC;CACF"}
@@ -1,5 +1,6 @@
1
1
  import * as Transformers from './lib/transformers';
2
2
  import RealtimeClient, { Options as RealtimeClientOptions } from './RealtimeClient';
3
3
  import RealtimeSubscription from './RealtimeSubscription';
4
- export { RealtimeClient, RealtimeClientOptions, RealtimeSubscription, Transformers, };
4
+ import RealtimePresence from './RealtimePresence';
5
+ export { RealtimeClient, RealtimeClientOptions, RealtimeSubscription, RealtimePresence, Transformers, };
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,cAAc,EAAE,EACrB,OAAO,IAAI,qBAAqB,EACjC,MAAM,kBAAkB,CAAA;AACzB,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AAEzD,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,YAAY,GACb,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,cAAc,EAAE,EACrB,OAAO,IAAI,qBAAqB,EACjC,MAAM,kBAAkB,CAAA;AACzB,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AACzD,OAAO,gBAAgB,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,GACb,CAAA"}
@@ -1,5 +1,6 @@
1
1
  import * as Transformers from './lib/transformers';
2
2
  import RealtimeClient from './RealtimeClient';
3
3
  import RealtimeSubscription from './RealtimeSubscription';
4
- export { RealtimeClient, RealtimeSubscription, Transformers, };
4
+ import RealtimePresence from './RealtimePresence';
5
+ export { RealtimeClient, RealtimeSubscription, RealtimePresence, Transformers, };
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,cAEN,MAAM,kBAAkB,CAAA;AACzB,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AAEzD,OAAO,EACL,cAAc,EAEd,oBAAoB,EACpB,YAAY,GACb,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,oBAAoB,CAAA;AAClD,OAAO,cAEN,MAAM,kBAAkB,CAAA;AACzB,OAAO,oBAAoB,MAAM,wBAAwB,CAAA;AACzD,OAAO,gBAAgB,MAAM,oBAAoB,CAAA;AAEjD,OAAO,EACL,cAAc,EAEd,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,GACb,CAAA"}
@@ -1,2 +1,2 @@
1
- export declare const version = "1.3.6";
1
+ export declare const version = "1.4.0";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // generated by genversion
2
- export const version = '1.3.6';
2
+ export const version = '1.4.0';
3
3
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supabase/realtime-js",
3
- "version": "1.3.6",
3
+ "version": "1.4.0",
4
4
  "description": "Listen to realtime updates to your PostgreSQL database",
5
5
  "keywords": [
6
6
  "realtime",
@@ -34,7 +34,10 @@
34
34
  "docs:json": "typedoc --json docs/spec.json --mode modules --includeDeclarations --excludeExternals"
35
35
  },
36
36
  "dependencies": {
37
+ "@types/lodash.clonedeep": "^4.5.6",
38
+ "@types/phoenix": "^1.5.4",
37
39
  "@types/websocket": "^1.0.3",
40
+ "lodash.clonedeep": "^4.5.0",
38
41
  "websocket": "^1.0.34"
39
42
  },
40
43
  "devDependencies": {
@@ -0,0 +1,319 @@
1
+ /*
2
+ This file draws heavily from https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/assets/js/phoenix/presence.js
3
+ License: https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/LICENSE.md
4
+ */
5
+
6
+ import cloneDeep from 'lodash.clonedeep'
7
+ import {
8
+ PresenceOpts,
9
+ PresenceOnJoinCallback,
10
+ PresenceOnLeaveCallback,
11
+ } from 'phoenix'
12
+ import RealtimeSubscription from './RealtimeSubscription'
13
+
14
+ type Presence = {
15
+ presence_id: string
16
+ [key: string]: any
17
+ }
18
+
19
+ type PresenceState = { [key: string]: Presence[] }
20
+
21
+ type PresenceDiff = {
22
+ joins: PresenceState
23
+ leaves: PresenceState
24
+ }
25
+
26
+ type RawPresenceState = {
27
+ [key: string]: Record<
28
+ 'metas',
29
+ {
30
+ phx_ref?: string
31
+ phx_ref_prev?: string
32
+ [key: string]: any
33
+ }[]
34
+ >
35
+ }
36
+
37
+ type RawPresenceDiff = {
38
+ joins: RawPresenceState
39
+ leaves: RawPresenceState
40
+ }
41
+
42
+ type PresenceChooser<T> = (key: string, presences: any) => T
43
+
44
+ export default class RealtimePresence {
45
+ state: PresenceState = {}
46
+ pendingDiffs: RawPresenceDiff[] = []
47
+ joinRef: string | null = null
48
+ caller: {
49
+ onJoin: PresenceOnJoinCallback
50
+ onLeave: PresenceOnLeaveCallback
51
+ onSync: () => void
52
+ } = {
53
+ onJoin: () => {},
54
+ onLeave: () => {},
55
+ onSync: () => {},
56
+ }
57
+
58
+ /**
59
+ * Initializes the Presence
60
+ * @param channel - The RealtimeSubscription
61
+ * @param opts - The options,
62
+ * for example `{events: {state: 'state', diff: 'diff'}}`
63
+ */
64
+ constructor(public channel: RealtimeSubscription, opts?: PresenceOpts) {
65
+ const events = opts?.events || {
66
+ state: 'presence_state',
67
+ diff: 'presence_diff',
68
+ }
69
+
70
+ this.channel.on(events.state, (newState: RawPresenceState) => {
71
+ const { onJoin, onLeave, onSync } = this.caller
72
+
73
+ this.joinRef = this.channel.joinRef()
74
+
75
+ this.state = RealtimePresence.syncState(
76
+ this.state,
77
+ newState,
78
+ onJoin,
79
+ onLeave
80
+ )
81
+
82
+ this.pendingDiffs.forEach((diff) => {
83
+ this.state = RealtimePresence.syncDiff(
84
+ this.state,
85
+ diff,
86
+ onJoin,
87
+ onLeave
88
+ )
89
+ })
90
+
91
+ this.pendingDiffs = []
92
+
93
+ onSync()
94
+ })
95
+
96
+ this.channel.on(events.diff, (diff: RawPresenceDiff) => {
97
+ const { onJoin, onLeave, onSync } = this.caller
98
+
99
+ if (this.inPendingSyncState()) {
100
+ this.pendingDiffs.push(diff)
101
+ } else {
102
+ this.state = RealtimePresence.syncDiff(
103
+ this.state,
104
+ diff,
105
+ onJoin,
106
+ onLeave
107
+ )
108
+
109
+ onSync()
110
+ }
111
+ })
112
+ }
113
+
114
+ /**
115
+ * Used to sync the list of presences on the server
116
+ * with the client's state. An optional `onJoin` and `onLeave` callback can
117
+ * be provided to react to changes in the client's local presences across
118
+ * disconnects and reconnects with the server.
119
+ */
120
+ static syncState(
121
+ currentState: PresenceState,
122
+ newState: RawPresenceState | PresenceState,
123
+ onJoin: PresenceOnJoinCallback,
124
+ onLeave: PresenceOnLeaveCallback
125
+ ): PresenceState {
126
+ const state = cloneDeep(currentState)
127
+ const transformedState = this.transformState(newState)
128
+ const joins: PresenceState = {}
129
+ const leaves: PresenceState = {}
130
+
131
+ this.map(state, (key: string, presences: Presence[]) => {
132
+ if (!transformedState[key]) {
133
+ leaves[key] = presences
134
+ }
135
+ })
136
+
137
+ this.map(transformedState, (key, newPresences: Presence[]) => {
138
+ const currentPresences: Presence[] = state[key]
139
+
140
+ if (currentPresences) {
141
+ const newPresenceIds = newPresences.map((m: Presence) => m.presence_id)
142
+ const curPresenceIds = currentPresences.map(
143
+ (m: Presence) => m.presence_id
144
+ )
145
+ const joinedPresences: Presence[] = newPresences.filter(
146
+ (m: Presence) => curPresenceIds.indexOf(m.presence_id) < 0
147
+ )
148
+ const leftPresences: Presence[] = currentPresences.filter(
149
+ (m: Presence) => newPresenceIds.indexOf(m.presence_id) < 0
150
+ )
151
+
152
+ if (joinedPresences.length > 0) {
153
+ joins[key] = joinedPresences
154
+ }
155
+
156
+ if (leftPresences.length > 0) {
157
+ leaves[key] = leftPresences
158
+ }
159
+ } else {
160
+ joins[key] = newPresences
161
+ }
162
+ })
163
+
164
+ return this.syncDiff(state, { joins, leaves }, onJoin, onLeave)
165
+ }
166
+
167
+ /**
168
+ *
169
+ * Used to sync a diff of presence join and leave
170
+ * events from the server, as they happen. Like `syncState`, `syncDiff`
171
+ * accepts optional `onJoin` and `onLeave` callbacks to react to a user
172
+ * joining or leaving from a device.
173
+ */
174
+ static syncDiff(
175
+ state: PresenceState,
176
+ diff: RawPresenceDiff | PresenceDiff,
177
+ onJoin: PresenceOnJoinCallback,
178
+ onLeave: PresenceOnLeaveCallback
179
+ ): PresenceState {
180
+ const { joins, leaves } = {
181
+ joins: this.transformState(diff.joins),
182
+ leaves: this.transformState(diff.leaves),
183
+ }
184
+
185
+ if (!onJoin) {
186
+ onJoin = () => {}
187
+ }
188
+
189
+ if (!onLeave) {
190
+ onLeave = () => {}
191
+ }
192
+
193
+ this.map(joins, (key, newPresences: Presence[]) => {
194
+ const currentPresences: Presence[] = state[key]
195
+ state[key] = cloneDeep(newPresences)
196
+
197
+ if (currentPresences) {
198
+ const joinedPresenceIds = state[key].map((m: Presence) => m.presence_id)
199
+ const curPresences: Presence[] = currentPresences.filter(
200
+ (m: Presence) => joinedPresenceIds.indexOf(m.presence_id) < 0
201
+ )
202
+
203
+ state[key].unshift(...curPresences)
204
+ }
205
+
206
+ onJoin(key, currentPresences, newPresences)
207
+ })
208
+
209
+ this.map(leaves, (key, leftPresences: Presence[]) => {
210
+ let currentPresences: Presence[] = state[key]
211
+
212
+ if (!currentPresences) return
213
+
214
+ const presenceIdsToRemove = leftPresences.map(
215
+ (m: Presence) => m.presence_id
216
+ )
217
+ currentPresences = currentPresences.filter(
218
+ (m: Presence) => presenceIdsToRemove.indexOf(m.presence_id) < 0
219
+ )
220
+
221
+ state[key] = currentPresences
222
+
223
+ onLeave(key, currentPresences, leftPresences)
224
+
225
+ if (currentPresences.length === 0) delete state[key]
226
+ })
227
+
228
+ return state
229
+ }
230
+
231
+ /**
232
+ * Returns the array of presences, with selected metadata.
233
+ */
234
+ static list<T = any>(
235
+ presences: PresenceState,
236
+ chooser: PresenceChooser<T> | undefined
237
+ ): T[] {
238
+ if (!chooser) {
239
+ chooser = (_key, pres) => pres
240
+ }
241
+
242
+ return this.map(presences, (key, presences: Presence[]) =>
243
+ chooser!(key, presences)
244
+ )
245
+ }
246
+
247
+ private static map<T = any>(
248
+ obj: PresenceState,
249
+ func: PresenceChooser<T>
250
+ ): T[] {
251
+ return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]))
252
+ }
253
+
254
+ /**
255
+ * Remove 'metas' key
256
+ * Change 'phx_ref' to 'presence_id'
257
+ * Remove 'phx_ref' and 'phx_ref_prev'
258
+ *
259
+ * @example
260
+ * // returns {
261
+ * abc123: [
262
+ * { presence_id: '2', user_id: 1 },
263
+ * { presence_id: '3', user_id: 2 }
264
+ * ]
265
+ * }
266
+ * RealtimePresence.transformState({
267
+ * abc123: {
268
+ * metas: [
269
+ * { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
270
+ * { phx_ref: '3', user_id: 2 }
271
+ * ]
272
+ * }
273
+ * })
274
+ */
275
+ private static transformState(
276
+ state: RawPresenceState | PresenceState
277
+ ): PresenceState {
278
+ state = cloneDeep(state)
279
+
280
+ return Object.getOwnPropertyNames(state).reduce((newState, key) => {
281
+ const presences = state[key]
282
+
283
+ if ('metas' in presences) {
284
+ newState[key] = presences.metas.map((presence) => {
285
+ presence['presence_id'] = presence['phx_ref']
286
+
287
+ delete presence['phx_ref']
288
+ delete presence['phx_ref_prev']
289
+
290
+ return presence
291
+ }) as Presence[]
292
+ } else {
293
+ newState[key] = presences
294
+ }
295
+
296
+ return newState
297
+ }, {} as PresenceState)
298
+ }
299
+
300
+ onJoin(callback: PresenceOnJoinCallback): void {
301
+ this.caller.onJoin = callback
302
+ }
303
+
304
+ onLeave(callback: PresenceOnLeaveCallback): void {
305
+ this.caller.onLeave = callback
306
+ }
307
+
308
+ onSync(callback: () => void): void {
309
+ this.caller.onSync = callback
310
+ }
311
+
312
+ list<T = any>(by?: PresenceChooser<T>): T[] {
313
+ return RealtimePresence.list<T>(this.state, by)
314
+ }
315
+
316
+ private inPendingSyncState(): boolean {
317
+ return !this.joinRef || this.joinRef !== this.channel.joinRef()
318
+ }
319
+ }
package/src/index.ts CHANGED
@@ -3,10 +3,12 @@ import RealtimeClient, {
3
3
  Options as RealtimeClientOptions,
4
4
  } from './RealtimeClient'
5
5
  import RealtimeSubscription from './RealtimeSubscription'
6
+ import RealtimePresence from './RealtimePresence'
6
7
 
7
8
  export {
8
9
  RealtimeClient,
9
10
  RealtimeClientOptions,
10
11
  RealtimeSubscription,
12
+ RealtimePresence,
11
13
  Transformers,
12
14
  }
@@ -1,2 +1,2 @@
1
1
  // generated by genversion
2
- export const version = '1.3.6'
2
+ export const version = '1.4.0'