@churchapps/apphelper 0.7.4 → 0.7.6
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/components/conversations/Conversations.d.ts +21 -0
- package/dist/components/conversations/Conversations.d.ts.map +1 -0
- package/dist/components/conversations/Conversations.js +92 -0
- package/dist/components/conversations/Conversations.js.map +1 -0
- package/dist/components/conversations/index.d.ts +2 -0
- package/dist/components/conversations/index.d.ts.map +1 -0
- package/dist/components/conversations/index.js +2 -0
- package/dist/components/conversations/index.js.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/notes/AddNote.d.ts.map +1 -1
- package/dist/components/notes/AddNote.js +19 -1
- package/dist/components/notes/AddNote.js.map +1 -1
- package/dist/components/notes/Notes.d.ts.map +1 -1
- package/dist/components/notes/Notes.js +62 -47
- package/dist/components/notes/Notes.js.map +1 -1
- package/dist/components/notes/SubscriptionToggle.d.ts +38 -0
- package/dist/components/notes/SubscriptionToggle.d.ts.map +1 -0
- package/dist/components/notes/SubscriptionToggle.js +76 -0
- package/dist/components/notes/SubscriptionToggle.js.map +1 -0
- package/dist/components/notes/index.d.ts +1 -0
- package/dist/components/notes/index.d.ts.map +1 -1
- package/dist/components/notes/index.js +1 -0
- package/dist/components/notes/index.js.map +1 -1
- package/dist/components/wrapper/PrivateMessages.d.ts.map +1 -1
- package/dist/components/wrapper/PrivateMessages.js +12 -0
- package/dist/components/wrapper/PrivateMessages.js.map +1 -1
- package/dist/helpers/ConversationStore.d.ts +36 -0
- package/dist/helpers/ConversationStore.d.ts.map +1 -0
- package/dist/helpers/ConversationStore.js +238 -0
- package/dist/helpers/ConversationStore.js.map +1 -0
- package/dist/helpers/NotificationService.d.ts +3 -0
- package/dist/helpers/NotificationService.d.ts.map +1 -1
- package/dist/helpers/NotificationService.js +52 -13
- package/dist/helpers/NotificationService.js.map +1 -1
- package/dist/helpers/PresenceStore.d.ts +34 -0
- package/dist/helpers/PresenceStore.d.ts.map +1 -0
- package/dist/helpers/PresenceStore.js +93 -0
- package/dist/helpers/PresenceStore.js.map +1 -0
- package/dist/helpers/SocketHelper.d.ts +17 -0
- package/dist/helpers/SocketHelper.d.ts.map +1 -1
- package/dist/helpers/SocketHelper.js +70 -3
- package/dist/helpers/SocketHelper.js.map +1 -1
- package/dist/helpers/SubscriptionManager.d.ts +23 -0
- package/dist/helpers/SubscriptionManager.d.ts.map +1 -0
- package/dist/helpers/SubscriptionManager.js +118 -0
- package/dist/helpers/SubscriptionManager.js.map +1 -0
- package/dist/helpers/WebPushHelper.d.ts +36 -0
- package/dist/helpers/WebPushHelper.d.ts.map +1 -0
- package/dist/helpers/WebPushHelper.js +180 -0
- package/dist/helpers/WebPushHelper.js.map +1 -0
- package/dist/helpers/__tests__/WebPushHelper.test.d.ts +13 -0
- package/dist/helpers/__tests__/WebPushHelper.test.d.ts.map +1 -0
- package/dist/helpers/__tests__/WebPushHelper.test.js +99 -0
- package/dist/helpers/__tests__/WebPushHelper.test.js.map +1 -0
- package/dist/helpers/index.d.ts +5 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +4 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useConversation.d.ts +36 -0
- package/dist/hooks/useConversation.d.ts.map +1 -0
- package/dist/hooks/useConversation.js +152 -0
- package/dist/hooks/useConversation.js.map +1 -0
- package/dist/website/components/elementTypes/CarouselElement.js +1 -1
- package/dist/website/components/elementTypes/CarouselElement.js.map +1 -1
- package/dist/website/styles/animations.css +11 -0
- package/package.json +18 -10
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { SocketHelper } from "./SocketHelper";
|
|
2
|
+
/**
|
|
3
|
+
* In-memory cache of presence (attendance) per conversationId. Mirrors ConversationStore's
|
|
4
|
+
* pattern: registers the "attendance" socket handler exactly once, applies broadcasts to
|
|
5
|
+
* whichever conversations are currently open, and notifies subscribers.
|
|
6
|
+
*
|
|
7
|
+
* Server-side, attendance is derived from the Connection rows joined to the conversation
|
|
8
|
+
* and rebroadcast on every join/leave, so consumers see updates without polling.
|
|
9
|
+
*/
|
|
10
|
+
export class PresenceStore {
|
|
11
|
+
}
|
|
12
|
+
PresenceStore.snapshots = new Map();
|
|
13
|
+
PresenceStore.listeners = new Map();
|
|
14
|
+
PresenceStore.handlersRegistered = false;
|
|
15
|
+
PresenceStore.ensureHandlers = () => {
|
|
16
|
+
if (PresenceStore.handlersRegistered)
|
|
17
|
+
return;
|
|
18
|
+
PresenceStore.handlersRegistered = true;
|
|
19
|
+
SocketHelper.addHandler("attendance", "PresenceStore-Attendance", (data) => {
|
|
20
|
+
const snapshot = PresenceStore.normalize(data);
|
|
21
|
+
if (!snapshot)
|
|
22
|
+
return;
|
|
23
|
+
const existing = PresenceStore.snapshots.get(snapshot.conversationId);
|
|
24
|
+
if (existing && PresenceStore.equals(existing, snapshot))
|
|
25
|
+
return;
|
|
26
|
+
PresenceStore.snapshots.set(snapshot.conversationId, snapshot);
|
|
27
|
+
PresenceStore.notify(snapshot.conversationId);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
PresenceStore.reset = () => {
|
|
31
|
+
PresenceStore.snapshots.clear();
|
|
32
|
+
PresenceStore.listeners.clear();
|
|
33
|
+
};
|
|
34
|
+
PresenceStore.get = (conversationId) => {
|
|
35
|
+
return PresenceStore.snapshots.get(conversationId) ?? null;
|
|
36
|
+
};
|
|
37
|
+
PresenceStore.subscribe = (conversationId, listener) => {
|
|
38
|
+
PresenceStore.ensureHandlers();
|
|
39
|
+
let set = PresenceStore.listeners.get(conversationId);
|
|
40
|
+
if (!set) {
|
|
41
|
+
set = new Set();
|
|
42
|
+
PresenceStore.listeners.set(conversationId, set);
|
|
43
|
+
}
|
|
44
|
+
set.add(listener);
|
|
45
|
+
const cached = PresenceStore.snapshots.get(conversationId);
|
|
46
|
+
if (cached)
|
|
47
|
+
listener(cached);
|
|
48
|
+
return () => {
|
|
49
|
+
const s = PresenceStore.listeners.get(conversationId);
|
|
50
|
+
if (!s)
|
|
51
|
+
return;
|
|
52
|
+
s.delete(listener);
|
|
53
|
+
if (s.size === 0)
|
|
54
|
+
PresenceStore.listeners.delete(conversationId);
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
PresenceStore.notify = (conversationId) => {
|
|
58
|
+
const snapshot = PresenceStore.snapshots.get(conversationId);
|
|
59
|
+
if (!snapshot)
|
|
60
|
+
return;
|
|
61
|
+
const set = PresenceStore.listeners.get(conversationId);
|
|
62
|
+
set?.forEach(listener => {
|
|
63
|
+
try {
|
|
64
|
+
listener(snapshot);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error("PresenceStore listener error:", err);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
PresenceStore.normalize = (data) => {
|
|
72
|
+
if (!data?.conversationId)
|
|
73
|
+
return null;
|
|
74
|
+
const viewers = Array.isArray(data.viewers) ? data.viewers : [];
|
|
75
|
+
const totalViewers = typeof data.totalViewers === "number" ? data.totalViewers : viewers.length;
|
|
76
|
+
return { conversationId: data.conversationId, totalViewers, viewers };
|
|
77
|
+
};
|
|
78
|
+
PresenceStore.equals = (a, b) => {
|
|
79
|
+
if (a.totalViewers !== b.totalViewers || a.viewers.length !== b.viewers.length)
|
|
80
|
+
return false;
|
|
81
|
+
for (let i = 0; i < a.viewers.length; i++) {
|
|
82
|
+
const av = a.viewers[i];
|
|
83
|
+
const bv = b.viewers[i];
|
|
84
|
+
if (av.id !== bv.id || av.displayName !== bv.displayName || av.personId !== bv.personId)
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
};
|
|
89
|
+
PresenceStore.forget = (conversationId) => {
|
|
90
|
+
PresenceStore.snapshots.delete(conversationId);
|
|
91
|
+
PresenceStore.listeners.delete(conversationId);
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=PresenceStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PresenceStore.js","sourceRoot":"","sources":["../../src/helpers/PresenceStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,23 @@ export declare class SocketHelper {
|
|
|
9
9
|
personId: string;
|
|
10
10
|
churchId: string;
|
|
11
11
|
}) => void;
|
|
12
|
+
static getPersonChurch: () => {
|
|
13
|
+
personId: string;
|
|
14
|
+
churchId: string;
|
|
15
|
+
};
|
|
16
|
+
static onPersonChurchChange: (listener: (info: {
|
|
17
|
+
previous: {
|
|
18
|
+
personId: string;
|
|
19
|
+
churchId: string;
|
|
20
|
+
};
|
|
21
|
+
next: {
|
|
22
|
+
personId: string;
|
|
23
|
+
churchId: string;
|
|
24
|
+
};
|
|
25
|
+
}) => void) => () => void;
|
|
26
|
+
private static changeListeners;
|
|
27
|
+
private static socketIdListeners;
|
|
28
|
+
static onSocketIdReady: (listener: (socketId: string) => void) => () => void;
|
|
12
29
|
static createAlertConnection: () => void;
|
|
13
30
|
static init: () => Promise<void>;
|
|
14
31
|
static addHandler: (action: string, id: string, handleMessage: (data: any) => void) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SocketHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/SocketHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,4BAA4B,EAAE,sBAAsB,EAAmD,MAAM,qBAAqB,CAAC;AAEjK,qBAAa,YAAY;IACvB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;IACzB,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,cAAc,EAAE,4BAA4B,EAAE,CAAM;IAC3D,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAA0E;IACzG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAkB;IAE5C,MAAM,CAAC,eAAe,GAAI,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,
|
|
1
|
+
{"version":3,"file":"SocketHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/SocketHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,4BAA4B,EAAE,sBAAsB,EAAmD,MAAM,qBAAqB,CAAC;AAEjK,qBAAa,YAAY;IACvB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;IACzB,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,cAAc,EAAE,4BAA4B,EAAE,CAAM;IAC3D,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAA0E;IACzG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAkB;IAE5C,MAAM,CAAC,eAAe,GAAI,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,UAiBlE;IAEF,MAAM,CAAC,eAAe,QAAO;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAEjE;IAEF,MAAM,CAAC,oBAAoB,GAAI,UAAU,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,IAAI,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,KAAK,IAAI,gBAKzJ;IAEF,OAAO,CAAC,MAAM,CAAC,eAAe,CAAiI;IAE/J,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAyC;IAEzE,MAAM,CAAC,eAAe,GAAI,UAAU,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,gBAK5D;IAEF,MAAM,CAAC,qBAAqB,aAY1B;IAEF,MAAM,CAAC,IAAI,sBA0DT;IAEF,MAAM,CAAC,UAAU,GAAI,QAAQ,MAAM,EAAE,IAAI,MAAM,EAAE,eAAe,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,UAOjF;IAEF,MAAM,CAAC,aAAa,GAAI,IAAI,MAAM,UAEhC;IAEF,MAAM,CAAC,sBAAsB,GAAI,QAAQ,MAAM,UAE7C;IAEF,MAAM,CAAC,gBAAgB,aAErB;IAEF,MAAM,CAAC,aAAa,GAAI,SAAS,sBAAsB,UA4BrD;IAEF,MAAM,CAAC,OAAO,aAcZ;IAEF,MAAM,CAAC,UAAU,aAEf;IAEF,MAAM,CAAC,WAAW,QAAO,OAAO,CAE9B;IAEF,MAAM,CAAC,kBAAkB,QAAO,MAAM,CAUpC;IAGF,MAAM,CAAC,kBAAkB,mBAmBvB;CAEH"}
|
|
@@ -7,10 +7,44 @@ SocketHelper.actionHandlers = [];
|
|
|
7
7
|
SocketHelper.personIdChurchId = { personId: "", churchId: "" };
|
|
8
8
|
SocketHelper.isCleanedUp = false;
|
|
9
9
|
SocketHelper.setPersonChurch = (pc) => {
|
|
10
|
-
if (pc?.personId
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
if (!pc?.personId || !pc?.churchId)
|
|
11
|
+
return;
|
|
12
|
+
const churchChanged = pc.churchId !== _a.personIdChurchId.churchId;
|
|
13
|
+
const personChanged = pc.personId !== _a.personIdChurchId.personId;
|
|
14
|
+
if (!churchChanged && !personChanged)
|
|
15
|
+
return;
|
|
16
|
+
// If we're already connected and the church/person changed, fire the changed callbacks
|
|
17
|
+
// so caches scoped to the previous tenant get cleared. Each subscriber decides whether
|
|
18
|
+
// it cares about church-only vs person-only changes.
|
|
19
|
+
if (_a.personIdChurchId.churchId || _a.personIdChurchId.personId) {
|
|
20
|
+
_a.changeListeners.forEach((cb) => {
|
|
21
|
+
try {
|
|
22
|
+
cb({ previous: _a.personIdChurchId, next: pc });
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error("SocketHelper change listener error:", err);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
13
28
|
}
|
|
29
|
+
_a.personIdChurchId = pc;
|
|
30
|
+
_a.createAlertConnection();
|
|
31
|
+
};
|
|
32
|
+
SocketHelper.getPersonChurch = () => {
|
|
33
|
+
return { ..._a.personIdChurchId };
|
|
34
|
+
};
|
|
35
|
+
SocketHelper.onPersonChurchChange = (listener) => {
|
|
36
|
+
_a.changeListeners.push(listener);
|
|
37
|
+
return () => {
|
|
38
|
+
_a.changeListeners = _a.changeListeners.filter(l => l !== listener);
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
SocketHelper.changeListeners = [];
|
|
42
|
+
SocketHelper.socketIdListeners = [];
|
|
43
|
+
SocketHelper.onSocketIdReady = (listener) => {
|
|
44
|
+
_a.socketIdListeners.push(listener);
|
|
45
|
+
return () => {
|
|
46
|
+
_a.socketIdListeners = _a.socketIdListeners.filter(l => l !== listener);
|
|
47
|
+
};
|
|
14
48
|
};
|
|
15
49
|
SocketHelper.createAlertConnection = () => {
|
|
16
50
|
if (_a.personIdChurchId.personId && _a.socketId) {
|
|
@@ -25,6 +59,26 @@ SocketHelper.createAlertConnection = () => {
|
|
|
25
59
|
}
|
|
26
60
|
};
|
|
27
61
|
SocketHelper.init = async () => {
|
|
62
|
+
// Idempotent: if a socket is already open or connecting, do not tear it down.
|
|
63
|
+
if (_a.socket && (_a.socket.readyState === _a.socket.OPEN || _a.socket.readyState === _a.socket.CONNECTING)) {
|
|
64
|
+
// If we already have a socketId, the connection is fully usable — return immediately.
|
|
65
|
+
if (_a.socketId)
|
|
66
|
+
return;
|
|
67
|
+
// Otherwise wait briefly for socketId to arrive instead of tearing down.
|
|
68
|
+
await new Promise((resolve) => {
|
|
69
|
+
const start = Date.now();
|
|
70
|
+
const tick = () => {
|
|
71
|
+
if (_a.socketId)
|
|
72
|
+
return resolve();
|
|
73
|
+
if (Date.now() - start > 3000)
|
|
74
|
+
return resolve();
|
|
75
|
+
setTimeout(tick, 50);
|
|
76
|
+
};
|
|
77
|
+
tick();
|
|
78
|
+
});
|
|
79
|
+
if (_a.socketId)
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
28
82
|
_a.cleanup();
|
|
29
83
|
_a.isCleanedUp = false;
|
|
30
84
|
if (_a.socket && _a.socket.readyState !== _a.socket.CLOSED) {
|
|
@@ -84,8 +138,21 @@ SocketHelper.handleMessage = (payload) => {
|
|
|
84
138
|
return;
|
|
85
139
|
try {
|
|
86
140
|
if (payload.action === "socketId") {
|
|
141
|
+
const previousId = _a.socketId;
|
|
87
142
|
_a.socketId = payload.data;
|
|
88
143
|
_a.createAlertConnection();
|
|
144
|
+
// Notify listeners that a socketId is available — used by SubscriptionManager
|
|
145
|
+
// to flush rooms that called joinRoom before the socket finished connecting.
|
|
146
|
+
if (!previousId) {
|
|
147
|
+
_a.socketIdListeners.forEach((cb) => {
|
|
148
|
+
try {
|
|
149
|
+
cb(payload.data);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
console.error("SocketHelper socketId listener error:", err);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
89
156
|
}
|
|
90
157
|
else {
|
|
91
158
|
const matchingHandlers = ArrayHelper.getAll(_a.actionHandlers, "action", payload.action);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SocketHelper.js","sourceRoot":"","sources":["../../src/helpers/SocketHelper.ts"],"names":[],"mappings":";AAAA,OAAO,EAA6E,SAAS,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAEjK,MAAM,OAAO,YAAY;;;AAGhB,2BAAc,GAAmC,EAAE,AAArC,CAAsC;AAC5C,6BAAgB,GAA2C,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,AAAzE,CAA0E;AAC1F,wBAAW,GAAY,KAAK,AAAjB,CAAkB;AAErC,4BAAe,GAAG,CAAC,EAA0C,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"SocketHelper.js","sourceRoot":"","sources":["../../src/helpers/SocketHelper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks which conversation rooms the current tab has joined.
|
|
3
|
+
* Backed by a single WebSocket (SocketHelper) and the server's /messaging/connections endpoint.
|
|
4
|
+
*
|
|
5
|
+
* - joinRoom: ref-counted POST /connections
|
|
6
|
+
* - leaveRoom: decrements ref; DELETE /connections/:churchId/:conversationId/:socketId at zero
|
|
7
|
+
* - rejoinAll: re-issues every active POST after a socket reconnect
|
|
8
|
+
*/
|
|
9
|
+
export declare class SubscriptionManager {
|
|
10
|
+
private static counts;
|
|
11
|
+
private static rejoinHandlerRegistered;
|
|
12
|
+
static setupRejoin: () => void;
|
|
13
|
+
static joinRoom: (conversationId: string, churchId: string, personId?: string, displayName?: string) => Promise<void>;
|
|
14
|
+
private static pendingLeaves;
|
|
15
|
+
private static LEAVE_DEBOUNCE_MS;
|
|
16
|
+
static leaveRoom: (conversationId: string, churchId: string) => Promise<void>;
|
|
17
|
+
static rejoinAll: () => Promise<void>;
|
|
18
|
+
static reset: () => void;
|
|
19
|
+
static isJoined: (churchId: string, conversationId: string) => boolean;
|
|
20
|
+
private static postConnection;
|
|
21
|
+
private static key;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=SubscriptionManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscriptionManager.d.ts","sourceRoot":"","sources":["../../src/helpers/SubscriptionManager.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAkC;IACvD,OAAO,CAAC,MAAM,CAAC,uBAAuB,CAAS;IAE/C,MAAM,CAAC,WAAW,aAkBhB;IAEF,MAAM,CAAC,QAAQ,GAAU,gBAAgB,MAAM,EAAE,UAAU,MAAM,EAAE,WAAW,MAAM,EAAE,cAAc,MAAM,mBAOxG;IAQF,OAAO,CAAC,MAAM,CAAC,aAAa,CAAyD;IACrF,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAO;IAEvC,MAAM,CAAC,SAAS,GAAU,gBAAgB,MAAM,EAAE,UAAU,MAAM,mBAyBhE;IAEF,MAAM,CAAC,SAAS,sBAOd;IAEF,MAAM,CAAC,KAAK,aAEV;IAEF,MAAM,CAAC,QAAQ,GAAI,UAAU,MAAM,EAAE,gBAAgB,MAAM,KAAG,OAAO,CAEnE;IAEF,OAAO,CAAC,MAAM,CAAC,cAAc,CAc3B;IAEF,OAAO,CAAC,MAAM,CAAC,GAAG,CAAiF;CACpG"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
var _a;
|
|
2
|
+
import { ApiHelper } from "@churchapps/helpers";
|
|
3
|
+
import { SocketHelper } from "./SocketHelper";
|
|
4
|
+
/**
|
|
5
|
+
* Tracks which conversation rooms the current tab has joined.
|
|
6
|
+
* Backed by a single WebSocket (SocketHelper) and the server's /messaging/connections endpoint.
|
|
7
|
+
*
|
|
8
|
+
* - joinRoom: ref-counted POST /connections
|
|
9
|
+
* - leaveRoom: decrements ref; DELETE /connections/:churchId/:conversationId/:socketId at zero
|
|
10
|
+
* - rejoinAll: re-issues every active POST after a socket reconnect
|
|
11
|
+
*/
|
|
12
|
+
export class SubscriptionManager {
|
|
13
|
+
}
|
|
14
|
+
_a = SubscriptionManager;
|
|
15
|
+
SubscriptionManager.counts = new Map();
|
|
16
|
+
SubscriptionManager.rejoinHandlerRegistered = false;
|
|
17
|
+
SubscriptionManager.setupRejoin = () => {
|
|
18
|
+
if (_a.rejoinHandlerRegistered)
|
|
19
|
+
return;
|
|
20
|
+
_a.rejoinHandlerRegistered = true;
|
|
21
|
+
SocketHelper.addHandler("reconnect", "SubscriptionManager-Rejoin", () => {
|
|
22
|
+
_a.rejoinAll();
|
|
23
|
+
});
|
|
24
|
+
// Components may call joinRoom while the socket is still mid-handshake (no socketId yet).
|
|
25
|
+
// We record the intent in `counts`; this listener flushes those pending joins to the server
|
|
26
|
+
// as soon as socketId arrives.
|
|
27
|
+
SocketHelper.onSocketIdReady(() => {
|
|
28
|
+
_a.rejoinAll();
|
|
29
|
+
});
|
|
30
|
+
// setupRejoin is called from NotificationService.runInitialize *after* SocketHelper.init
|
|
31
|
+
// completes — so socketId may already be present by the time we register the listener.
|
|
32
|
+
// In that case there's no future socketId event to wait for; flush any pending joins now.
|
|
33
|
+
if (SocketHelper.socketId) {
|
|
34
|
+
_a.rejoinAll().catch(() => { });
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
SubscriptionManager.joinRoom = async (conversationId, churchId, personId, displayName) => {
|
|
38
|
+
const key = _a.key(churchId, conversationId);
|
|
39
|
+
const next = (_a.counts.get(key) ?? 0) + 1;
|
|
40
|
+
_a.counts.set(key, next);
|
|
41
|
+
if (next > 1)
|
|
42
|
+
return; // already joined
|
|
43
|
+
_a.setupRejoin();
|
|
44
|
+
await _a.postConnection(conversationId, churchId, personId, displayName);
|
|
45
|
+
};
|
|
46
|
+
// Debounced DELETE: when count hits zero, schedule the server-side leave a short
|
|
47
|
+
// delay later. If joinRoom comes back within that window (React StrictMode dev
|
|
48
|
+
// mount/cleanup/mount, or a useEffect dep update that immediately re-mounts), the
|
|
49
|
+
// pending DELETE is canceled and the server-side row is preserved — preventing the
|
|
50
|
+
// race where the inbound broadcast lands between DELETE and the next POST and
|
|
51
|
+
// finds an empty room.
|
|
52
|
+
SubscriptionManager.pendingLeaves = new Map();
|
|
53
|
+
SubscriptionManager.LEAVE_DEBOUNCE_MS = 300;
|
|
54
|
+
SubscriptionManager.leaveRoom = async (conversationId, churchId) => {
|
|
55
|
+
const key = _a.key(churchId, conversationId);
|
|
56
|
+
const current = _a.counts.get(key) ?? 0;
|
|
57
|
+
if (current <= 0)
|
|
58
|
+
return;
|
|
59
|
+
const next = current - 1;
|
|
60
|
+
if (next > 0) {
|
|
61
|
+
_a.counts.set(key, next);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
_a.counts.delete(key);
|
|
65
|
+
// Debounce the actual DELETE — see comment above.
|
|
66
|
+
const existing = _a.pendingLeaves.get(key);
|
|
67
|
+
if (existing)
|
|
68
|
+
clearTimeout(existing);
|
|
69
|
+
const timer = setTimeout(async () => {
|
|
70
|
+
_a.pendingLeaves.delete(key);
|
|
71
|
+
// If something rejoined in the meantime, abort.
|
|
72
|
+
if ((_a.counts.get(key) ?? 0) > 0)
|
|
73
|
+
return;
|
|
74
|
+
if (!SocketHelper.socketId)
|
|
75
|
+
return;
|
|
76
|
+
try {
|
|
77
|
+
await ApiHelper.delete(`/connections/${churchId}/${conversationId}/${SocketHelper.socketId}`, "MessagingApi");
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.warn("SubscriptionManager.leaveRoom failed:", err);
|
|
81
|
+
}
|
|
82
|
+
}, _a.LEAVE_DEBOUNCE_MS);
|
|
83
|
+
_a.pendingLeaves.set(key, timer);
|
|
84
|
+
};
|
|
85
|
+
SubscriptionManager.rejoinAll = async () => {
|
|
86
|
+
if (!SocketHelper.socketId)
|
|
87
|
+
return;
|
|
88
|
+
const entries = Array.from(_a.counts.keys());
|
|
89
|
+
await Promise.all(entries.map((key) => {
|
|
90
|
+
const [churchId, conversationId] = key.split("|");
|
|
91
|
+
return _a.postConnection(conversationId, churchId);
|
|
92
|
+
}));
|
|
93
|
+
};
|
|
94
|
+
SubscriptionManager.reset = () => {
|
|
95
|
+
_a.counts.clear();
|
|
96
|
+
};
|
|
97
|
+
SubscriptionManager.isJoined = (churchId, conversationId) => {
|
|
98
|
+
return (_a.counts.get(_a.key(churchId, conversationId)) ?? 0) > 0;
|
|
99
|
+
};
|
|
100
|
+
SubscriptionManager.postConnection = async (conversationId, churchId, personId, displayName) => {
|
|
101
|
+
if (!SocketHelper.socketId)
|
|
102
|
+
return; // will retry on socketIdReady via setupRejoin
|
|
103
|
+
const connection = {
|
|
104
|
+
conversationId,
|
|
105
|
+
churchId,
|
|
106
|
+
socketId: SocketHelper.socketId,
|
|
107
|
+
personId: personId ?? null,
|
|
108
|
+
displayName: displayName ?? ""
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
await ApiHelper.postAnonymous("/connections", [connection], "MessagingApi");
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
console.warn(`SubscriptionManager.postConnection(${conversationId}) failed:`, err);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
SubscriptionManager.key = (churchId, conversationId) => `${churchId}|${conversationId}`;
|
|
118
|
+
//# sourceMappingURL=SubscriptionManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubscriptionManager.js","sourceRoot":"","sources":["../../src/helpers/SubscriptionManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-app helper for registering a Web Push subscription with the MessagingApi.
|
|
3
|
+
* Hosts call WebPushHelper.configure({ scope, appName }) once at boot, then
|
|
4
|
+
* WebPushHelper.subscribe() at login (and on userChurch changes).
|
|
5
|
+
*
|
|
6
|
+
* The server stores the subscription under the active userChurch's churchId, so
|
|
7
|
+
* switching churches without re-enrolling would leave the device tied to the wrong
|
|
8
|
+
* tenant — call subscribe() again after each church switch.
|
|
9
|
+
*/
|
|
10
|
+
export declare const WebPushHelper: {
|
|
11
|
+
configure(options: {
|
|
12
|
+
scope?: string;
|
|
13
|
+
appName?: string;
|
|
14
|
+
}): void;
|
|
15
|
+
isSupported: () => boolean;
|
|
16
|
+
isOptedOut(): boolean;
|
|
17
|
+
setOptedOut(value: boolean): void;
|
|
18
|
+
canPromptNow(): boolean;
|
|
19
|
+
markPrompted(): void;
|
|
20
|
+
isStandalone(): boolean;
|
|
21
|
+
getRegistration(): Promise<ServiceWorkerRegistration | null>;
|
|
22
|
+
getExistingSubscription(): Promise<PushSubscription | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Subscribe (or re-enroll) the active user. Safe to call multiple times — the server
|
|
25
|
+
* upserts based on (churchId, fcmToken). Call after login and after any userChurch change.
|
|
26
|
+
*/
|
|
27
|
+
subscribe(): Promise<PushSubscription | null>;
|
|
28
|
+
/**
|
|
29
|
+
* Re-enroll the existing subscription (no permission prompt). Use this on userChurch
|
|
30
|
+
* change so the device record updates to the new church without re-prompting the user.
|
|
31
|
+
*/
|
|
32
|
+
refreshEnrollment(): Promise<void>;
|
|
33
|
+
unsubscribe(): Promise<void>;
|
|
34
|
+
postSubscription(sub: PushSubscription): Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=WebPushHelper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebPushHelper.d.ts","sourceRoot":"","sources":["../../src/helpers/WebPushHelper.ts"],"names":[],"mappings":"AAwBA;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa;uBACL;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;uBAhBjC,OAAO;kBAuBf,OAAO;uBAIF,OAAO;oBAOV,OAAO;;oBAeP,OAAO;uBAOE,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC;+BAMjC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAOjE;;;OAGG;iBACgB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IA6BnD;;;OAGG;yBACwB,OAAO,CAAC,IAAI,CAAC;mBAOnB,OAAO,CAAC,IAAI,CAAC;0BAUN,gBAAgB;CAc7C,CAAC"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { ApiHelper, UserHelper } from "@churchapps/helpers";
|
|
2
|
+
const OPT_OUT_KEY = "ca-webpush-opt-out";
|
|
3
|
+
const LAST_PROMPT_KEY = "ca-webpush-last-prompt";
|
|
4
|
+
const PROMPT_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000;
|
|
5
|
+
let scope = "/";
|
|
6
|
+
let appName = "ChurchAppsPwa";
|
|
7
|
+
const urlBase64ToUint8Array = (base64) => {
|
|
8
|
+
const padding = "=".repeat((4 - (base64.length % 4)) % 4);
|
|
9
|
+
const normalized = (base64 + padding).replace(/-/g, "+").replace(/_/g, "/");
|
|
10
|
+
const raw = atob(normalized);
|
|
11
|
+
const out = new Uint8Array(raw.length);
|
|
12
|
+
for (let i = 0; i < raw.length; i++)
|
|
13
|
+
out[i] = raw.charCodeAt(i);
|
|
14
|
+
return out;
|
|
15
|
+
};
|
|
16
|
+
const isSupported = () => typeof window !== "undefined" &&
|
|
17
|
+
"serviceWorker" in navigator &&
|
|
18
|
+
"PushManager" in window &&
|
|
19
|
+
"Notification" in window;
|
|
20
|
+
/**
|
|
21
|
+
* Cross-app helper for registering a Web Push subscription with the MessagingApi.
|
|
22
|
+
* Hosts call WebPushHelper.configure({ scope, appName }) once at boot, then
|
|
23
|
+
* WebPushHelper.subscribe() at login (and on userChurch changes).
|
|
24
|
+
*
|
|
25
|
+
* The server stores the subscription under the active userChurch's churchId, so
|
|
26
|
+
* switching churches without re-enrolling would leave the device tied to the wrong
|
|
27
|
+
* tenant — call subscribe() again after each church switch.
|
|
28
|
+
*/
|
|
29
|
+
export const WebPushHelper = {
|
|
30
|
+
configure(options) {
|
|
31
|
+
if (options.scope)
|
|
32
|
+
scope = options.scope;
|
|
33
|
+
if (options.appName)
|
|
34
|
+
appName = options.appName;
|
|
35
|
+
},
|
|
36
|
+
isSupported,
|
|
37
|
+
isOptedOut() {
|
|
38
|
+
try {
|
|
39
|
+
return localStorage.getItem(OPT_OUT_KEY) === "1";
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
setOptedOut(value) {
|
|
46
|
+
try {
|
|
47
|
+
if (value)
|
|
48
|
+
localStorage.setItem(OPT_OUT_KEY, "1");
|
|
49
|
+
else
|
|
50
|
+
localStorage.removeItem(OPT_OUT_KEY);
|
|
51
|
+
}
|
|
52
|
+
catch { /* ignore */ }
|
|
53
|
+
},
|
|
54
|
+
canPromptNow() {
|
|
55
|
+
if (!isSupported())
|
|
56
|
+
return false;
|
|
57
|
+
if (Notification.permission !== "default")
|
|
58
|
+
return false;
|
|
59
|
+
if (WebPushHelper.isOptedOut())
|
|
60
|
+
return false;
|
|
61
|
+
try {
|
|
62
|
+
const last = Number(localStorage.getItem(LAST_PROMPT_KEY) || 0);
|
|
63
|
+
if (last && Date.now() - last < PROMPT_COOLDOWN_MS)
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
catch { /* ignore */ }
|
|
67
|
+
return true;
|
|
68
|
+
},
|
|
69
|
+
markPrompted() {
|
|
70
|
+
try {
|
|
71
|
+
localStorage.setItem(LAST_PROMPT_KEY, String(Date.now()));
|
|
72
|
+
}
|
|
73
|
+
catch { /* ignore */ }
|
|
74
|
+
},
|
|
75
|
+
isStandalone() {
|
|
76
|
+
if (typeof window === "undefined")
|
|
77
|
+
return false;
|
|
78
|
+
const mq = window.matchMedia?.("(display-mode: standalone)").matches;
|
|
79
|
+
const iosStandalone = window.navigator.standalone === true;
|
|
80
|
+
return !!(mq || iosStandalone);
|
|
81
|
+
},
|
|
82
|
+
async getRegistration() {
|
|
83
|
+
if (!isSupported())
|
|
84
|
+
return null;
|
|
85
|
+
try {
|
|
86
|
+
return (await navigator.serviceWorker.getRegistration(scope)) || null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
async getExistingSubscription() {
|
|
93
|
+
const reg = await WebPushHelper.getRegistration();
|
|
94
|
+
if (!reg)
|
|
95
|
+
return null;
|
|
96
|
+
try {
|
|
97
|
+
return await reg.pushManager.getSubscription();
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* Subscribe (or re-enroll) the active user. Safe to call multiple times — the server
|
|
105
|
+
* upserts based on (churchId, fcmToken). Call after login and after any userChurch change.
|
|
106
|
+
*/
|
|
107
|
+
async subscribe() {
|
|
108
|
+
if (!isSupported() || !UserHelper.user?.id)
|
|
109
|
+
return null;
|
|
110
|
+
const reg = await WebPushHelper.getRegistration();
|
|
111
|
+
if (!reg)
|
|
112
|
+
return null;
|
|
113
|
+
const permission = await Notification.requestPermission();
|
|
114
|
+
WebPushHelper.markPrompted();
|
|
115
|
+
if (permission !== "granted")
|
|
116
|
+
return null;
|
|
117
|
+
const config = await ApiHelper.get("/webpush/publicKey", "MessagingApi");
|
|
118
|
+
const publicKey = config?.publicKey;
|
|
119
|
+
if (!publicKey || !config?.enabled)
|
|
120
|
+
return null;
|
|
121
|
+
const existing = await reg.pushManager.getSubscription();
|
|
122
|
+
if (existing) {
|
|
123
|
+
await WebPushHelper.postSubscription(existing);
|
|
124
|
+
WebPushHelper.setOptedOut(false);
|
|
125
|
+
return existing;
|
|
126
|
+
}
|
|
127
|
+
const subscription = await reg.pushManager.subscribe({
|
|
128
|
+
userVisibleOnly: true,
|
|
129
|
+
applicationServerKey: urlBase64ToUint8Array(publicKey)
|
|
130
|
+
});
|
|
131
|
+
await WebPushHelper.postSubscription(subscription);
|
|
132
|
+
WebPushHelper.setOptedOut(false);
|
|
133
|
+
return subscription;
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* Re-enroll the existing subscription (no permission prompt). Use this on userChurch
|
|
137
|
+
* change so the device record updates to the new church without re-prompting the user.
|
|
138
|
+
*/
|
|
139
|
+
async refreshEnrollment() {
|
|
140
|
+
if (!isSupported() || !UserHelper.user?.id)
|
|
141
|
+
return;
|
|
142
|
+
const sub = await WebPushHelper.getExistingSubscription();
|
|
143
|
+
if (!sub)
|
|
144
|
+
return;
|
|
145
|
+
await WebPushHelper.postSubscription(sub);
|
|
146
|
+
},
|
|
147
|
+
async unsubscribe() {
|
|
148
|
+
WebPushHelper.setOptedOut(true);
|
|
149
|
+
const sub = await WebPushHelper.getExistingSubscription();
|
|
150
|
+
if (!sub)
|
|
151
|
+
return;
|
|
152
|
+
const endpoint = sub.endpoint;
|
|
153
|
+
try {
|
|
154
|
+
await sub.unsubscribe();
|
|
155
|
+
}
|
|
156
|
+
catch { /* ignore */ }
|
|
157
|
+
try {
|
|
158
|
+
await ApiHelper.post("/webpush/unsubscribe", { endpoint }, "MessagingApi");
|
|
159
|
+
}
|
|
160
|
+
catch { /* ignore */ }
|
|
161
|
+
},
|
|
162
|
+
async postSubscription(sub) {
|
|
163
|
+
const json = sub.toJSON();
|
|
164
|
+
if (!json.endpoint || !json.keys?.p256dh || !json.keys?.auth)
|
|
165
|
+
return;
|
|
166
|
+
const body = {
|
|
167
|
+
subscription: {
|
|
168
|
+
endpoint: json.endpoint,
|
|
169
|
+
keys: { p256dh: json.keys.p256dh, auth: json.keys.auth }
|
|
170
|
+
},
|
|
171
|
+
appName,
|
|
172
|
+
deviceInfo: typeof navigator !== "undefined" ? navigator.userAgent : undefined
|
|
173
|
+
};
|
|
174
|
+
try {
|
|
175
|
+
await ApiHelper.post("/webpush/subscribe", body, "MessagingApi");
|
|
176
|
+
}
|
|
177
|
+
catch { /* ignore */ }
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=WebPushHelper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebPushHelper.js","sourceRoot":"","sources":["../../src/helpers/WebPushHelper.ts"],"names":[],"mappings":"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"}
|