shared-features 0.0.4 → 0.0.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/README.md +249 -96
- package/dist/{AdBanner-CZz8trdz.cjs → AnnouncementModal-Bqy0pn3V.cjs} +428 -3
- package/dist/AnnouncementModal-Bqy0pn3V.cjs.map +1 -0
- package/dist/{AdBanner-DF1QuMxR.js → AnnouncementModal-sxH4K5gy.js} +430 -5
- package/dist/AnnouncementModal-sxH4K5gy.js.map +1 -0
- package/dist/admin-notifications-D9n9h-eY.cjs +362 -0
- package/dist/admin-notifications-D9n9h-eY.cjs.map +1 -0
- package/dist/admin-notifications-p1dy3zIP.js +363 -0
- package/dist/admin-notifications-p1dy3zIP.js.map +1 -0
- package/dist/{analytics-CdpCtTpu.js → analytics-40-S_fHC.js} +3 -2
- package/dist/{analytics-CdpCtTpu.js.map → analytics-40-S_fHC.js.map} +1 -1
- package/dist/{analytics--ZSO9ova.cjs → analytics-lEzOx2vl.cjs} +2 -1
- package/dist/{analytics--ZSO9ova.cjs.map → analytics-lEzOx2vl.cjs.map} +1 -1
- package/dist/broadcasts-3_WfQMNL.cjs +278 -0
- package/dist/broadcasts-3_WfQMNL.cjs.map +1 -0
- package/dist/broadcasts-DgZUzqMf.js +257 -0
- package/dist/broadcasts-DgZUzqMf.js.map +1 -0
- package/dist/components/index.cjs +23 -20
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -1
- package/dist/components/notifications/AnnouncementModal.d.ts +21 -0
- package/dist/components/notifications/AnnouncementModal.d.ts.map +1 -0
- package/dist/components/notifications/BroadcastBanner.d.ts +55 -0
- package/dist/components/notifications/BroadcastBanner.d.ts.map +1 -0
- package/dist/components/notifications/index.d.ts +11 -0
- package/dist/components/notifications/index.d.ts.map +1 -0
- package/dist/hooks/index.cjs +9 -1
- package/dist/hooks/index.cjs.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 +9 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useBroadcasts.d.ts +122 -0
- package/dist/hooks/useBroadcasts.d.ts.map +1 -0
- package/dist/index.cjs +99 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -17
- package/dist/index.js.map +1 -1
- package/dist/notifications/events/index.d.ts +14 -0
- package/dist/notifications/events/index.d.ts.map +1 -0
- package/dist/notifications/events/registry.d.ts +75 -0
- package/dist/notifications/events/registry.d.ts.map +1 -0
- package/dist/notifications/events/templates/engine.d.ts +57 -0
- package/dist/notifications/events/templates/engine.d.ts.map +1 -0
- package/dist/notifications/events/templates/standard.d.ts +20 -0
- package/dist/notifications/events/templates/standard.d.ts.map +1 -0
- package/dist/notifications/events/useNotificationEvents.d.ts +36 -0
- package/dist/notifications/events/useNotificationEvents.d.ts.map +1 -0
- package/dist/notifications/index.cjs +25 -0
- package/dist/notifications/index.cjs.map +1 -0
- package/dist/notifications/index.d.ts +9 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +25 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/services/admin-notifications.d.ts +95 -0
- package/dist/services/admin-notifications.d.ts.map +1 -0
- package/dist/services/broadcasts.d.ts +55 -0
- package/dist/services/broadcasts.d.ts.map +1 -0
- package/dist/services/index.cjs +34 -1
- package/dist/services/index.cjs.map +1 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +35 -2
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.cjs +108 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +109 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/notifications.d.ts +634 -0
- package/dist/types/notifications.d.ts.map +1 -0
- package/dist/useBroadcasts-DzpCcbC8.js +161 -0
- package/dist/useBroadcasts-DzpCcbC8.js.map +1 -0
- package/dist/useBroadcasts-FP6ZrcY_.cjs +160 -0
- package/dist/useBroadcasts-FP6ZrcY_.cjs.map +1 -0
- package/dist/{useCampaigns-Dltisb9N.cjs → useCampaigns-BOZ9dDsG.cjs} +2 -2
- package/dist/{useCampaigns-Dltisb9N.cjs.map → useCampaigns-BOZ9dDsG.cjs.map} +1 -1
- package/dist/{useCampaigns-nwfsALsN.js → useCampaigns-D46b9zuf.js} +2 -2
- package/dist/{useCampaigns-nwfsALsN.js.map → useCampaigns-D46b9zuf.js.map} +1 -1
- package/dist/useNotificationEvents-BXeMqdak.cjs +954 -0
- package/dist/useNotificationEvents-BXeMqdak.cjs.map +1 -0
- package/dist/useNotificationEvents-D8DVxah1.js +955 -0
- package/dist/useNotificationEvents-D8DVxah1.js.map +1 -0
- package/package.json +11 -3
- package/dist/AdBanner-CZz8trdz.cjs.map +0 -1
- package/dist/AdBanner-DF1QuMxR.js.map +0 -1
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { query, collection, where, Timestamp, getDocs, getDoc, doc, orderBy, deleteDoc, updateDoc, serverTimestamp, addDoc } from "firebase/firestore";
|
|
2
|
+
import { a as getSharedFeaturesDb } from "./analytics-40-S_fHC.js";
|
|
3
|
+
const COLLECTION_BROADCASTS = "zaions_broadcasts";
|
|
4
|
+
const COLLECTION_TEMPLATES = "zaions_notification_templates";
|
|
5
|
+
const COLLECTION_BROADCAST_EVENTS = "zaions_broadcast_events";
|
|
6
|
+
function docToBroadcast(docId, data) {
|
|
7
|
+
return {
|
|
8
|
+
id: docId,
|
|
9
|
+
title: data.title,
|
|
10
|
+
message: data.message,
|
|
11
|
+
type: data.type,
|
|
12
|
+
category: data.category,
|
|
13
|
+
isRead: false,
|
|
14
|
+
isImportant: data.isImportant,
|
|
15
|
+
actionUrl: data.actionUrl,
|
|
16
|
+
actionText: data.actionText,
|
|
17
|
+
createdAt: data.createdAt,
|
|
18
|
+
metadata: data.metadata,
|
|
19
|
+
targetProjects: data.targetProjects || [],
|
|
20
|
+
targetPlatforms: data.targetPlatforms || [],
|
|
21
|
+
targetAudience: data.targetAudience || "all",
|
|
22
|
+
status: data.status,
|
|
23
|
+
startDate: data.startDate,
|
|
24
|
+
endDate: data.endDate,
|
|
25
|
+
priority: data.priority || 50,
|
|
26
|
+
dismissible: data.dismissible !== false,
|
|
27
|
+
variant: data.variant || "banner",
|
|
28
|
+
impressions: data.impressions || 0,
|
|
29
|
+
clicks: data.clicks || 0,
|
|
30
|
+
createdBy: data.createdBy,
|
|
31
|
+
updatedBy: data.updatedBy
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function docToTemplate(docId, data) {
|
|
35
|
+
return {
|
|
36
|
+
id: docId,
|
|
37
|
+
name: data.name,
|
|
38
|
+
eventType: data.eventType,
|
|
39
|
+
category: data.category,
|
|
40
|
+
title: data.title,
|
|
41
|
+
message: data.message,
|
|
42
|
+
variables: data.variables || [],
|
|
43
|
+
type: data.type,
|
|
44
|
+
isImportant: data.isImportant,
|
|
45
|
+
actionUrl: data.actionUrl,
|
|
46
|
+
actionText: data.actionText,
|
|
47
|
+
enabled: data.enabled !== false,
|
|
48
|
+
createdAt: data.createdAt?.toDate() || /* @__PURE__ */ new Date(),
|
|
49
|
+
updatedAt: data.updatedAt?.toDate() || /* @__PURE__ */ new Date()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function createBroadcast(input, adminUserId) {
|
|
53
|
+
const db = getSharedFeaturesDb();
|
|
54
|
+
const broadcastData = {
|
|
55
|
+
...input,
|
|
56
|
+
status: "draft",
|
|
57
|
+
isRead: false,
|
|
58
|
+
impressions: 0,
|
|
59
|
+
clicks: 0,
|
|
60
|
+
createdBy: adminUserId,
|
|
61
|
+
createdAt: serverTimestamp(),
|
|
62
|
+
startDate: Timestamp.fromDate(input.startDate),
|
|
63
|
+
endDate: input.endDate ? Timestamp.fromDate(input.endDate) : null
|
|
64
|
+
};
|
|
65
|
+
const docRef = await addDoc(
|
|
66
|
+
collection(db, COLLECTION_BROADCASTS),
|
|
67
|
+
broadcastData
|
|
68
|
+
);
|
|
69
|
+
return docRef.id;
|
|
70
|
+
}
|
|
71
|
+
async function updateBroadcast(input, adminUserId) {
|
|
72
|
+
const db = getSharedFeaturesDb();
|
|
73
|
+
const updateData = {
|
|
74
|
+
...input,
|
|
75
|
+
updatedBy: adminUserId,
|
|
76
|
+
updatedAt: serverTimestamp()
|
|
77
|
+
};
|
|
78
|
+
if (input.startDate) {
|
|
79
|
+
updateData.startDate = Timestamp.fromDate(input.startDate);
|
|
80
|
+
}
|
|
81
|
+
if (input.endDate !== void 0) {
|
|
82
|
+
updateData.endDate = input.endDate ? Timestamp.fromDate(input.endDate) : null;
|
|
83
|
+
}
|
|
84
|
+
delete updateData.id;
|
|
85
|
+
await updateDoc(doc(db, COLLECTION_BROADCASTS, input.id), updateData);
|
|
86
|
+
}
|
|
87
|
+
async function deleteBroadcast(broadcastId) {
|
|
88
|
+
const db = getSharedFeaturesDb();
|
|
89
|
+
await deleteDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));
|
|
90
|
+
}
|
|
91
|
+
async function getAllBroadcasts() {
|
|
92
|
+
const db = getSharedFeaturesDb();
|
|
93
|
+
const q = query(
|
|
94
|
+
collection(db, COLLECTION_BROADCASTS),
|
|
95
|
+
orderBy("createdAt", "desc")
|
|
96
|
+
);
|
|
97
|
+
const snapshot = await getDocs(q);
|
|
98
|
+
return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));
|
|
99
|
+
}
|
|
100
|
+
async function getBroadcastsByStatus(status) {
|
|
101
|
+
const db = getSharedFeaturesDb();
|
|
102
|
+
const q = query(
|
|
103
|
+
collection(db, COLLECTION_BROADCASTS),
|
|
104
|
+
where("status", "==", status),
|
|
105
|
+
orderBy("createdAt", "desc")
|
|
106
|
+
);
|
|
107
|
+
const snapshot = await getDocs(q);
|
|
108
|
+
return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));
|
|
109
|
+
}
|
|
110
|
+
async function getBroadcastById(broadcastId) {
|
|
111
|
+
const db = getSharedFeaturesDb();
|
|
112
|
+
const docSnap = await getDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));
|
|
113
|
+
if (!docSnap.exists()) return null;
|
|
114
|
+
return docToBroadcast(docSnap.id, docSnap.data());
|
|
115
|
+
}
|
|
116
|
+
async function publishBroadcast(broadcastId, adminUserId) {
|
|
117
|
+
const db = getSharedFeaturesDb();
|
|
118
|
+
await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {
|
|
119
|
+
status: "active",
|
|
120
|
+
updatedBy: adminUserId,
|
|
121
|
+
updatedAt: serverTimestamp()
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async function scheduleBroadcast(broadcastId, scheduledDate, adminUserId) {
|
|
125
|
+
const db = getSharedFeaturesDb();
|
|
126
|
+
await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {
|
|
127
|
+
status: "scheduled",
|
|
128
|
+
startDate: Timestamp.fromDate(scheduledDate),
|
|
129
|
+
updatedBy: adminUserId,
|
|
130
|
+
updatedAt: serverTimestamp()
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async function pauseBroadcast(broadcastId, adminUserId) {
|
|
134
|
+
const db = getSharedFeaturesDb();
|
|
135
|
+
await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {
|
|
136
|
+
status: "draft",
|
|
137
|
+
// Move back to draft
|
|
138
|
+
updatedBy: adminUserId,
|
|
139
|
+
updatedAt: serverTimestamp()
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async function endBroadcast(broadcastId, adminUserId) {
|
|
143
|
+
const db = getSharedFeaturesDb();
|
|
144
|
+
await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {
|
|
145
|
+
status: "ended",
|
|
146
|
+
endDate: serverTimestamp(),
|
|
147
|
+
updatedBy: adminUserId,
|
|
148
|
+
updatedAt: serverTimestamp()
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async function createTemplate(input) {
|
|
152
|
+
const db = getSharedFeaturesDb();
|
|
153
|
+
const templateData = {
|
|
154
|
+
...input,
|
|
155
|
+
createdAt: serverTimestamp(),
|
|
156
|
+
updatedAt: serverTimestamp()
|
|
157
|
+
};
|
|
158
|
+
const docRef = await addDoc(
|
|
159
|
+
collection(db, COLLECTION_TEMPLATES),
|
|
160
|
+
templateData
|
|
161
|
+
);
|
|
162
|
+
return docRef.id;
|
|
163
|
+
}
|
|
164
|
+
async function updateTemplate(templateId, input) {
|
|
165
|
+
const db = getSharedFeaturesDb();
|
|
166
|
+
await updateDoc(doc(db, COLLECTION_TEMPLATES, templateId), {
|
|
167
|
+
...input,
|
|
168
|
+
updatedAt: serverTimestamp()
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async function deleteTemplate(templateId) {
|
|
172
|
+
const db = getSharedFeaturesDb();
|
|
173
|
+
await deleteDoc(doc(db, COLLECTION_TEMPLATES, templateId));
|
|
174
|
+
}
|
|
175
|
+
async function getAllTemplates() {
|
|
176
|
+
const db = getSharedFeaturesDb();
|
|
177
|
+
const q = query(
|
|
178
|
+
collection(db, COLLECTION_TEMPLATES),
|
|
179
|
+
orderBy("name", "asc")
|
|
180
|
+
);
|
|
181
|
+
const snapshot = await getDocs(q);
|
|
182
|
+
return snapshot.docs.map((d) => docToTemplate(d.id, d.data()));
|
|
183
|
+
}
|
|
184
|
+
async function getTemplateById(templateId) {
|
|
185
|
+
const db = getSharedFeaturesDb();
|
|
186
|
+
const docSnap = await getDoc(doc(db, COLLECTION_TEMPLATES, templateId));
|
|
187
|
+
if (!docSnap.exists()) return null;
|
|
188
|
+
return docToTemplate(docSnap.id, docSnap.data());
|
|
189
|
+
}
|
|
190
|
+
async function getFirestoreTemplateByEventType(eventType) {
|
|
191
|
+
const db = getSharedFeaturesDb();
|
|
192
|
+
const q = query(
|
|
193
|
+
collection(db, COLLECTION_TEMPLATES),
|
|
194
|
+
where("eventType", "==", eventType)
|
|
195
|
+
);
|
|
196
|
+
const snapshot = await getDocs(q);
|
|
197
|
+
if (snapshot.empty) return null;
|
|
198
|
+
const docSnap = snapshot.docs[0];
|
|
199
|
+
if (!docSnap) return null;
|
|
200
|
+
return docToTemplate(docSnap.id, docSnap.data());
|
|
201
|
+
}
|
|
202
|
+
async function getBroadcastAnalytics(broadcastId) {
|
|
203
|
+
const db = getSharedFeaturesDb();
|
|
204
|
+
const broadcast = await getBroadcastById(broadcastId);
|
|
205
|
+
if (!broadcast) return null;
|
|
206
|
+
const eventsQuery = query(
|
|
207
|
+
collection(db, COLLECTION_BROADCAST_EVENTS),
|
|
208
|
+
where("broadcastId", "==", broadcastId)
|
|
209
|
+
);
|
|
210
|
+
const eventsSnapshot = await getDocs(eventsQuery);
|
|
211
|
+
const events = eventsSnapshot.docs.map((d) => d.data());
|
|
212
|
+
const impressions = events.filter((e) => e.action === "impression").length;
|
|
213
|
+
const clicks = events.filter((e) => e.action === "click").length;
|
|
214
|
+
const dismissals = events.filter((e) => e.action === "dismiss").length;
|
|
215
|
+
const byPlatform = {
|
|
216
|
+
web: { impressions: 0, clicks: 0 },
|
|
217
|
+
android: { impressions: 0, clicks: 0 },
|
|
218
|
+
ios: { impressions: 0, clicks: 0 }
|
|
219
|
+
};
|
|
220
|
+
events.forEach((e) => {
|
|
221
|
+
const platform = e.platform;
|
|
222
|
+
if (byPlatform[platform]) {
|
|
223
|
+
if (e.action === "impression") byPlatform[platform].impressions++;
|
|
224
|
+
if (e.action === "click") byPlatform[platform].clicks++;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
const byProject = {};
|
|
228
|
+
events.forEach((e) => {
|
|
229
|
+
const projectId = e.projectId;
|
|
230
|
+
if (!byProject[projectId]) {
|
|
231
|
+
byProject[projectId] = { impressions: 0, clicks: 0 };
|
|
232
|
+
}
|
|
233
|
+
if (e.action === "impression") byProject[projectId].impressions++;
|
|
234
|
+
if (e.action === "click") byProject[projectId].clicks++;
|
|
235
|
+
});
|
|
236
|
+
const byDateMap = {};
|
|
237
|
+
events.forEach((e) => {
|
|
238
|
+
const dateStr = e.timestamp.toDate().toISOString().split("T")[0];
|
|
239
|
+
if (dateStr) {
|
|
240
|
+
if (!byDateMap[dateStr]) {
|
|
241
|
+
byDateMap[dateStr] = { impressions: 0, clicks: 0 };
|
|
242
|
+
}
|
|
243
|
+
if (e.action === "impression") byDateMap[dateStr].impressions++;
|
|
244
|
+
if (e.action === "click") byDateMap[dateStr].clicks++;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
const byDate = Object.entries(byDateMap).map(([date, data]) => ({
|
|
248
|
+
date,
|
|
249
|
+
impressions: data.impressions,
|
|
250
|
+
clicks: data.clicks
|
|
251
|
+
}));
|
|
252
|
+
return {
|
|
253
|
+
broadcastId,
|
|
254
|
+
title: broadcast.title,
|
|
255
|
+
status: broadcast.status,
|
|
256
|
+
impressions,
|
|
257
|
+
clicks,
|
|
258
|
+
dismissals,
|
|
259
|
+
ctr: impressions > 0 ? clicks / impressions : 0,
|
|
260
|
+
byPlatform,
|
|
261
|
+
byProject,
|
|
262
|
+
byDate
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async function getOverallAnalytics(startDate, endDate) {
|
|
266
|
+
const db = getSharedFeaturesDb();
|
|
267
|
+
const eventsQuery = query(
|
|
268
|
+
collection(db, COLLECTION_BROADCAST_EVENTS),
|
|
269
|
+
where("timestamp", ">=", Timestamp.fromDate(startDate)),
|
|
270
|
+
where("timestamp", "<=", Timestamp.fromDate(endDate))
|
|
271
|
+
);
|
|
272
|
+
const eventsSnapshot = await getDocs(eventsQuery);
|
|
273
|
+
const events = eventsSnapshot.docs.map((d) => d.data());
|
|
274
|
+
const totalSent = events.filter((e) => e.action === "impression").length;
|
|
275
|
+
const totalRead = events.filter((e) => e.action === "impression").length;
|
|
276
|
+
const totalClicked = events.filter((e) => e.action === "click").length;
|
|
277
|
+
const byCategory = {
|
|
278
|
+
system: { sent: 0, read: 0, clicked: 0 },
|
|
279
|
+
account: { sent: 0, read: 0, clicked: 0 },
|
|
280
|
+
activity: { sent: 0, read: 0, clicked: 0 },
|
|
281
|
+
report: { sent: 0, read: 0, clicked: 0 },
|
|
282
|
+
promotional: { sent: 0, read: 0, clicked: 0 },
|
|
283
|
+
social: { sent: 0, read: 0, clicked: 0 }
|
|
284
|
+
};
|
|
285
|
+
const byType = {
|
|
286
|
+
info: { sent: 0, read: 0, clicked: 0 },
|
|
287
|
+
success: { sent: 0, read: 0, clicked: 0 },
|
|
288
|
+
warning: { sent: 0, read: 0, clicked: 0 },
|
|
289
|
+
error: { sent: 0, read: 0, clicked: 0 },
|
|
290
|
+
reminder: { sent: 0, read: 0, clicked: 0 },
|
|
291
|
+
milestone: { sent: 0, read: 0, clicked: 0 },
|
|
292
|
+
announcement: { sent: 0, read: 0, clicked: 0 }
|
|
293
|
+
};
|
|
294
|
+
const byDateMap = {};
|
|
295
|
+
events.forEach((e) => {
|
|
296
|
+
const dateStr = e.timestamp.toDate().toISOString().split("T")[0];
|
|
297
|
+
if (dateStr) {
|
|
298
|
+
if (!byDateMap[dateStr]) {
|
|
299
|
+
byDateMap[dateStr] = { sent: 0, read: 0, clicked: 0 };
|
|
300
|
+
}
|
|
301
|
+
if (e.action === "impression") {
|
|
302
|
+
byDateMap[dateStr].sent++;
|
|
303
|
+
byDateMap[dateStr].read++;
|
|
304
|
+
}
|
|
305
|
+
if (e.action === "click") byDateMap[dateStr].clicked++;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
const byDate = Object.entries(byDateMap).map(([date, data]) => ({ date, ...data })).sort((a, b) => a.date.localeCompare(b.date));
|
|
309
|
+
return {
|
|
310
|
+
totalSent,
|
|
311
|
+
totalRead,
|
|
312
|
+
totalClicked,
|
|
313
|
+
readRate: totalSent > 0 ? totalRead / totalSent : 0,
|
|
314
|
+
clickRate: totalSent > 0 ? totalClicked / totalSent : 0,
|
|
315
|
+
byCategory,
|
|
316
|
+
byType,
|
|
317
|
+
byDate
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const adminNotificationService = {
|
|
321
|
+
// Broadcasts
|
|
322
|
+
createBroadcast,
|
|
323
|
+
updateBroadcast,
|
|
324
|
+
deleteBroadcast,
|
|
325
|
+
getAllBroadcasts,
|
|
326
|
+
getBroadcastsByStatus,
|
|
327
|
+
getBroadcastById,
|
|
328
|
+
publishBroadcast,
|
|
329
|
+
scheduleBroadcast,
|
|
330
|
+
pauseBroadcast,
|
|
331
|
+
endBroadcast,
|
|
332
|
+
// Templates
|
|
333
|
+
createTemplate,
|
|
334
|
+
updateTemplate,
|
|
335
|
+
deleteTemplate,
|
|
336
|
+
getAllTemplates,
|
|
337
|
+
getTemplateById,
|
|
338
|
+
getFirestoreTemplateByEventType,
|
|
339
|
+
// Analytics
|
|
340
|
+
getBroadcastAnalytics,
|
|
341
|
+
getOverallAnalytics
|
|
342
|
+
};
|
|
343
|
+
export {
|
|
344
|
+
adminNotificationService as a,
|
|
345
|
+
getBroadcastsByStatus as b,
|
|
346
|
+
createBroadcast as c,
|
|
347
|
+
deleteBroadcast as d,
|
|
348
|
+
pauseBroadcast as e,
|
|
349
|
+
endBroadcast as f,
|
|
350
|
+
getAllBroadcasts as g,
|
|
351
|
+
createTemplate as h,
|
|
352
|
+
updateTemplate as i,
|
|
353
|
+
deleteTemplate as j,
|
|
354
|
+
getAllTemplates as k,
|
|
355
|
+
getTemplateById as l,
|
|
356
|
+
getFirestoreTemplateByEventType as m,
|
|
357
|
+
getBroadcastAnalytics as n,
|
|
358
|
+
getOverallAnalytics as o,
|
|
359
|
+
publishBroadcast as p,
|
|
360
|
+
scheduleBroadcast as s,
|
|
361
|
+
updateBroadcast as u
|
|
362
|
+
};
|
|
363
|
+
//# sourceMappingURL=admin-notifications-p1dy3zIP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-notifications-p1dy3zIP.js","sources":["../src/services/admin-notifications.ts"],"sourcesContent":["/**\n * Admin Notifications Service\n *\n * Service for managing broadcasts and templates from the admin panel.\n * Used in aoneahsan.com admin panel to manage cross-project notifications.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDocs,\n getDoc,\n updateDoc,\n deleteDoc,\n addDoc,\n query,\n where,\n orderBy,\n serverTimestamp,\n Timestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport type {\n BroadcastNotification,\n BroadcastStatus,\n CreateBroadcastInput,\n UpdateBroadcastInput,\n NotificationTemplate,\n CreateTemplateInput,\n BroadcastAnalytics,\n NotificationAnalytics,\n} from '../types/notifications';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_BROADCASTS = 'zaions_broadcasts';\nconst COLLECTION_TEMPLATES = 'zaions_notification_templates';\nconst COLLECTION_BROADCAST_EVENTS = 'zaions_broadcast_events';\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Convert Firestore document to BroadcastNotification\n */\nfunction docToBroadcast(\n docId: string,\n data: Record<string, unknown>\n): BroadcastNotification {\n return {\n id: docId,\n title: data.title as string,\n message: data.message as string,\n type: data.type as BroadcastNotification['type'],\n category: data.category as BroadcastNotification['category'],\n isRead: false,\n isImportant: data.isImportant as boolean | undefined,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n createdAt: data.createdAt as Timestamp,\n metadata: data.metadata as Record<string, unknown> | undefined,\n targetProjects: (data.targetProjects as string[]) || [],\n targetPlatforms: (data.targetPlatforms as BroadcastNotification['targetPlatforms']) || [],\n targetAudience: (data.targetAudience as BroadcastNotification['targetAudience']) || 'all',\n status: data.status as BroadcastStatus,\n startDate: data.startDate as Timestamp,\n endDate: data.endDate as Timestamp | null | undefined,\n priority: (data.priority as number) || 50,\n dismissible: data.dismissible !== false,\n variant: (data.variant as BroadcastNotification['variant']) || 'banner',\n impressions: (data.impressions as number) || 0,\n clicks: (data.clicks as number) || 0,\n createdBy: data.createdBy as string,\n updatedBy: data.updatedBy as string | undefined,\n };\n}\n\n/**\n * Convert Firestore document to NotificationTemplate\n */\nfunction docToTemplate(\n docId: string,\n data: Record<string, unknown>\n): NotificationTemplate {\n return {\n id: docId,\n name: data.name as string,\n eventType: data.eventType as string,\n category: data.category as NotificationTemplate['category'],\n title: data.title as string,\n message: data.message as string,\n variables: (data.variables as string[]) || [],\n type: data.type as NotificationTemplate['type'],\n isImportant: data.isImportant as boolean,\n actionUrl: data.actionUrl as string | undefined,\n actionText: data.actionText as string | undefined,\n enabled: data.enabled !== false,\n createdAt: (data.createdAt as Timestamp)?.toDate() || new Date(),\n updatedAt: (data.updatedAt as Timestamp)?.toDate() || new Date(),\n };\n}\n\n// ============================================================================\n// BROADCAST CRUD\n// ============================================================================\n\n/**\n * Create a new broadcast\n */\nexport async function createBroadcast(\n input: CreateBroadcastInput,\n adminUserId: string\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const broadcastData = {\n ...input,\n status: 'draft' as BroadcastStatus,\n isRead: false,\n impressions: 0,\n clicks: 0,\n createdBy: adminUserId,\n createdAt: serverTimestamp(),\n startDate: Timestamp.fromDate(input.startDate),\n endDate: input.endDate ? Timestamp.fromDate(input.endDate) : null,\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_BROADCASTS),\n broadcastData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing broadcast\n */\nexport async function updateBroadcast(\n input: UpdateBroadcastInput,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n const updateData: Record<string, unknown> = {\n ...input,\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n };\n\n // Convert dates if provided\n if (input.startDate) {\n updateData.startDate = Timestamp.fromDate(input.startDate);\n }\n if (input.endDate !== undefined) {\n updateData.endDate = input.endDate ? Timestamp.fromDate(input.endDate) : null;\n }\n\n // Remove id from update data\n delete updateData.id;\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, input.id), updateData);\n}\n\n/**\n * Delete a broadcast\n */\nexport async function deleteBroadcast(broadcastId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n}\n\n/**\n * Get all broadcasts (for admin listing)\n */\nexport async function getAllBroadcasts(): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n}\n\n/**\n * Get broadcasts by status\n */\nexport async function getBroadcastsByStatus(\n status: BroadcastStatus\n): Promise<BroadcastNotification[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_BROADCASTS),\n where('status', '==', status),\n orderBy('createdAt', 'desc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToBroadcast(d.id, d.data()));\n}\n\n/**\n * Get a single broadcast by ID\n */\nexport async function getBroadcastById(\n broadcastId: string\n): Promise<BroadcastNotification | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_BROADCASTS, broadcastId));\n\n if (!docSnap.exists()) return null;\n return docToBroadcast(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// BROADCAST STATUS MANAGEMENT\n// ============================================================================\n\n/**\n * Publish a draft broadcast (set to active)\n */\nexport async function publishBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'active',\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Schedule a broadcast for later\n */\nexport async function scheduleBroadcast(\n broadcastId: string,\n scheduledDate: Date,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'scheduled',\n startDate: Timestamp.fromDate(scheduledDate),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Pause an active broadcast\n */\nexport async function pauseBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'draft', // Move back to draft\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * End a broadcast\n */\nexport async function endBroadcast(\n broadcastId: string,\n adminUserId: string\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_BROADCASTS, broadcastId), {\n status: 'ended',\n endDate: serverTimestamp(),\n updatedBy: adminUserId,\n updatedAt: serverTimestamp(),\n });\n}\n\n// ============================================================================\n// TEMPLATE CRUD\n// ============================================================================\n\n/**\n * Create a new template\n */\nexport async function createTemplate(\n input: CreateTemplateInput\n): Promise<string> {\n const db = getSharedFeaturesDb();\n\n const templateData = {\n ...input,\n createdAt: serverTimestamp(),\n updatedAt: serverTimestamp(),\n };\n\n const docRef = await addDoc(\n collection(db, COLLECTION_TEMPLATES),\n templateData\n );\n\n return docRef.id;\n}\n\n/**\n * Update an existing template\n */\nexport async function updateTemplate(\n templateId: string,\n input: Partial<CreateTemplateInput>\n): Promise<void> {\n const db = getSharedFeaturesDb();\n\n await updateDoc(doc(db, COLLECTION_TEMPLATES, templateId), {\n ...input,\n updatedAt: serverTimestamp(),\n });\n}\n\n/**\n * Delete a template\n */\nexport async function deleteTemplate(templateId: string): Promise<void> {\n const db = getSharedFeaturesDb();\n await deleteDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n}\n\n/**\n * Get all templates\n */\nexport async function getAllTemplates(): Promise<NotificationTemplate[]> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n orderBy('name', 'asc')\n );\n\n const snapshot = await getDocs(q);\n return snapshot.docs.map((d) => docToTemplate(d.id, d.data()));\n}\n\n/**\n * Get template by ID\n */\nexport async function getTemplateById(\n templateId: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_TEMPLATES, templateId));\n\n if (!docSnap.exists()) return null;\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n/**\n * Get template by event type from Firestore\n */\nexport async function getFirestoreTemplateByEventType(\n eventType: string\n): Promise<NotificationTemplate | null> {\n const db = getSharedFeaturesDb();\n\n const q = query(\n collection(db, COLLECTION_TEMPLATES),\n where('eventType', '==', eventType)\n );\n\n const snapshot = await getDocs(q);\n if (snapshot.empty) return null;\n\n const docSnap = snapshot.docs[0];\n if (!docSnap) return null;\n\n return docToTemplate(docSnap.id, docSnap.data());\n}\n\n// ============================================================================\n// ANALYTICS\n// ============================================================================\n\n/**\n * Get analytics for a specific broadcast\n */\nexport async function getBroadcastAnalytics(\n broadcastId: string\n): Promise<BroadcastAnalytics | null> {\n const db = getSharedFeaturesDb();\n\n // Get broadcast\n const broadcast = await getBroadcastById(broadcastId);\n if (!broadcast) return null;\n\n // Get events\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('broadcastId', '==', broadcastId)\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate analytics\n const impressions = events.filter((e) => e.action === 'impression').length;\n const clicks = events.filter((e) => e.action === 'click').length;\n const dismissals = events.filter((e) => e.action === 'dismiss').length;\n\n // Group by platform\n const byPlatform: BroadcastAnalytics['byPlatform'] = {\n web: { impressions: 0, clicks: 0 },\n android: { impressions: 0, clicks: 0 },\n ios: { impressions: 0, clicks: 0 },\n };\n\n events.forEach((e) => {\n const platform = e.platform as keyof typeof byPlatform;\n if (byPlatform[platform]) {\n if (e.action === 'impression') byPlatform[platform].impressions++;\n if (e.action === 'click') byPlatform[platform].clicks++;\n }\n });\n\n // Group by project\n const byProject: BroadcastAnalytics['byProject'] = {};\n events.forEach((e) => {\n const projectId = e.projectId as string;\n if (!byProject[projectId]) {\n byProject[projectId] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byProject[projectId].impressions++;\n if (e.action === 'click') byProject[projectId].clicks++;\n });\n\n // Group by date\n const byDateMap: Record<string, { impressions: number; clicks: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { impressions: 0, clicks: 0 };\n }\n if (e.action === 'impression') byDateMap[dateStr].impressions++;\n if (e.action === 'click') byDateMap[dateStr].clicks++;\n }\n });\n\n const byDate = Object.entries(byDateMap).map(([date, data]) => ({\n date,\n impressions: data.impressions,\n clicks: data.clicks,\n }));\n\n return {\n broadcastId,\n title: broadcast.title,\n status: broadcast.status,\n impressions,\n clicks,\n dismissals,\n ctr: impressions > 0 ? clicks / impressions : 0,\n byPlatform,\n byProject,\n byDate,\n };\n}\n\n/**\n * Get overall notification analytics\n */\nexport async function getOverallAnalytics(\n startDate: Date,\n endDate: Date\n): Promise<NotificationAnalytics> {\n const db = getSharedFeaturesDb();\n\n // Get all broadcast events in date range\n const eventsQuery = query(\n collection(db, COLLECTION_BROADCAST_EVENTS),\n where('timestamp', '>=', Timestamp.fromDate(startDate)),\n where('timestamp', '<=', Timestamp.fromDate(endDate))\n );\n\n const eventsSnapshot = await getDocs(eventsQuery);\n const events = eventsSnapshot.docs.map((d) => d.data());\n\n // Calculate totals\n const totalSent = events.filter((e) => e.action === 'impression').length;\n const totalRead = events.filter((e) => e.action === 'impression').length; // Impressions = read for broadcasts\n const totalClicked = events.filter((e) => e.action === 'click').length;\n\n // Initialize category and type breakdowns\n const byCategory: NotificationAnalytics['byCategory'] = {\n system: { sent: 0, read: 0, clicked: 0 },\n account: { sent: 0, read: 0, clicked: 0 },\n activity: { sent: 0, read: 0, clicked: 0 },\n report: { sent: 0, read: 0, clicked: 0 },\n promotional: { sent: 0, read: 0, clicked: 0 },\n social: { sent: 0, read: 0, clicked: 0 },\n };\n\n const byType: NotificationAnalytics['byType'] = {\n info: { sent: 0, read: 0, clicked: 0 },\n success: { sent: 0, read: 0, clicked: 0 },\n warning: { sent: 0, read: 0, clicked: 0 },\n error: { sent: 0, read: 0, clicked: 0 },\n reminder: { sent: 0, read: 0, clicked: 0 },\n milestone: { sent: 0, read: 0, clicked: 0 },\n announcement: { sent: 0, read: 0, clicked: 0 },\n };\n\n // Group by date\n const byDateMap: Record<string, { sent: number; read: number; clicked: number }> = {};\n events.forEach((e) => {\n const dateStr = (e.timestamp as Timestamp).toDate().toISOString().split('T')[0];\n if (dateStr) {\n if (!byDateMap[dateStr]) {\n byDateMap[dateStr] = { sent: 0, read: 0, clicked: 0 };\n }\n if (e.action === 'impression') {\n byDateMap[dateStr].sent++;\n byDateMap[dateStr].read++;\n }\n if (e.action === 'click') byDateMap[dateStr].clicked++;\n }\n });\n\n const byDate = Object.entries(byDateMap)\n .map(([date, data]) => ({ date, ...data }))\n .sort((a, b) => a.date.localeCompare(b.date));\n\n return {\n totalSent,\n totalRead,\n totalClicked,\n readRate: totalSent > 0 ? totalRead / totalSent : 0,\n clickRate: totalSent > 0 ? totalClicked / totalSent : 0,\n byCategory,\n byType,\n byDate,\n };\n}\n\n// ============================================================================\n// EXPORT SERVICE OBJECT\n// ============================================================================\n\nexport const adminNotificationService = {\n // Broadcasts\n createBroadcast,\n updateBroadcast,\n deleteBroadcast,\n getAllBroadcasts,\n getBroadcastsByStatus,\n getBroadcastById,\n publishBroadcast,\n scheduleBroadcast,\n pauseBroadcast,\n endBroadcast,\n\n // Templates\n createTemplate,\n updateTemplate,\n deleteTemplate,\n getAllTemplates,\n getTemplateById,\n getFirestoreTemplateByEventType,\n\n // Analytics\n getBroadcastAnalytics,\n getOverallAnalytics,\n};\n\nexport default adminNotificationService;\n"],"names":[],"mappings":"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"}
|
|
@@ -434,6 +434,7 @@ export {
|
|
|
434
434
|
recordImpression as r,
|
|
435
435
|
getEligibleCampaignIds as s,
|
|
436
436
|
trackImpression as t,
|
|
437
|
-
getCampaignHistory as u
|
|
437
|
+
getCampaignHistory as u,
|
|
438
|
+
getState as v
|
|
438
439
|
};
|
|
439
|
-
//# sourceMappingURL=analytics-
|
|
440
|
+
//# sourceMappingURL=analytics-40-S_fHC.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics-CdpCtTpu.js","sources":["../src/firebase/config.ts","../src/firebase/init.ts","../src/services/campaigns.ts","../src/services/analytics.ts"],"sourcesContent":["/**\n * Firebase Configuration Types\n *\n * Types and interfaces for configuring the shared-features Firebase connection.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\n/**\n * Firebase configuration object\n */\nexport interface FirebaseConfig {\n apiKey: string;\n authDomain: string;\n projectId: string;\n storageBucket?: string;\n messagingSenderId?: string;\n appId?: string;\n measurementId?: string;\n}\n\n/**\n * Target platform for the consumer project\n */\nexport type ConsumerPlatform = 'web' | 'android' | 'ios' | 'extension';\n\n/**\n * Configuration for initializing shared-features\n */\nexport interface SharedFeaturesConfig {\n /**\n * Firebase configuration for aoneahsan.com's Firebase project.\n * All values from environment variables.\n */\n firebaseConfig: FirebaseConfig;\n\n /**\n * Unique identifier for this project (e.g., 'ztools', '2fa-studio')\n */\n projectId: string;\n\n /**\n * Display name for this project (e.g., 'ZTools', '2FA Studio')\n */\n projectName: string;\n\n /**\n * Platform type for targeting\n */\n platform: ConsumerPlatform;\n\n /**\n * Whether to enable debug logging (default: false)\n */\n debug?: boolean;\n}\n\n/**\n * Internal state after initialization\n */\nexport interface SharedFeaturesState {\n /** Whether the package has been initialized */\n initialized: boolean;\n /** The active configuration */\n config: SharedFeaturesConfig | null;\n /** Unique device ID for anonymous tracking */\n deviceId: string | null;\n}\n\n/**\n * Singleton state\n */\nlet state: SharedFeaturesState = {\n initialized: false,\n config: null,\n deviceId: null,\n};\n\n/**\n * Get current state\n */\nexport function getState(): SharedFeaturesState {\n return state;\n}\n\n/**\n * Set state (internal use only)\n */\nexport function setState(newState: Partial<SharedFeaturesState>): void {\n state = { ...state, ...newState };\n}\n\n/**\n * Get configuration (throws if not initialized)\n */\nexport function getConfig(): SharedFeaturesConfig {\n if (!state.initialized || !state.config) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return state.config;\n}\n\n/**\n * Check if initialized\n */\nexport function isInitialized(): boolean {\n return state.initialized;\n}\n","/**\n * Firebase Initialization\n *\n * Initialize a secondary Firebase app for connecting to aoneahsan.com's\n * Firebase project from consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { initializeApp, getApps, FirebaseApp } from 'firebase/app';\nimport { getFirestore, Firestore } from 'firebase/firestore';\nimport { getAuth, Auth } from 'firebase/auth';\nimport {\n SharedFeaturesConfig,\n setState,\n getState,\n isInitialized,\n} from './config';\n\nconst SHARED_FEATURES_APP_NAME = 'shared-features';\n\nlet firebaseApp: FirebaseApp | null = null;\nlet firestoreDb: Firestore | null = null;\nlet firebaseAuth: Auth | null = null;\n\n/**\n * Generate a unique device ID for anonymous tracking\n */\nfunction generateDeviceId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 15);\n return `device_${timestamp}_${randomPart}`;\n}\n\n/**\n * Get or create device ID from storage\n */\nasync function getOrCreateDeviceId(): Promise<string> {\n const STORAGE_KEY = 'shared_features_device_id';\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: STORAGE_KEY });\n if (result.value) {\n return result.value;\n }\n const newId = generateDeviceId();\n await Preferences.set({ key: STORAGE_KEY, value: newId });\n return newId;\n } catch {\n // Fallback to localStorage for web\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored) {\n return stored;\n }\n const newId = generateDeviceId();\n localStorage.setItem(STORAGE_KEY, newId);\n return newId;\n } catch {\n // If all storage fails, generate ephemeral ID\n return generateDeviceId();\n }\n }\n}\n\n/**\n * Initialize shared-features with the given configuration.\n *\n * This creates a secondary Firebase app connection to aoneahsan.com's\n * Firebase project, separate from the consumer project's own Firebase.\n *\n * @param config - Configuration object with Firebase config and project info\n * @returns Initialized state object\n *\n * @example\n * ```typescript\n * import { initSharedFeatures } from 'shared-features';\n *\n * initSharedFeatures({\n * firebaseConfig: {\n * apiKey: import.meta.env.VITE_SHARED_FEATURES_API_KEY,\n * authDomain: import.meta.env.VITE_SHARED_FEATURES_AUTH_DOMAIN,\n * projectId: import.meta.env.VITE_SHARED_FEATURES_PROJECT_ID,\n * },\n * projectId: 'ztools',\n * projectName: 'ZTools',\n * platform: 'web',\n * });\n * ```\n */\nexport async function initSharedFeatures(\n config: SharedFeaturesConfig\n): Promise<{ app: FirebaseApp; db: Firestore; auth: Auth }> {\n // Return existing instance if already initialized with same config\n if (isInitialized() && firebaseApp && firestoreDb && firebaseAuth) {\n const currentConfig = getState().config;\n if (\n currentConfig &&\n currentConfig.firebaseConfig.projectId === config.firebaseConfig.projectId\n ) {\n return { app: firebaseApp, db: firestoreDb, auth: firebaseAuth };\n }\n }\n\n // Check if app already exists\n const existingApps = getApps();\n const existingApp = existingApps.find(\n (app) => app.name === SHARED_FEATURES_APP_NAME\n );\n\n if (existingApp) {\n firebaseApp = existingApp;\n } else {\n // Initialize new Firebase app with config from environment variables\n firebaseApp = initializeApp(config.firebaseConfig, SHARED_FEATURES_APP_NAME);\n }\n\n // Get Firestore and Auth instances\n firestoreDb = getFirestore(firebaseApp);\n firebaseAuth = getAuth(firebaseApp);\n\n // Get or create device ID\n const deviceId = await getOrCreateDeviceId();\n\n // Update state\n setState({\n initialized: true,\n config,\n deviceId,\n });\n\n if (config.debug) {\n console.log('[shared-features] Initialized:', {\n projectId: config.projectId,\n projectName: config.projectName,\n platform: config.platform,\n deviceId,\n });\n }\n\n return { app: firebaseApp, db: firestoreDb, auth: firebaseAuth };\n}\n\n/**\n * Get the shared-features Firebase app instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesApp(): FirebaseApp {\n if (!firebaseApp) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firebaseApp;\n}\n\n/**\n * Get the shared-features Firestore instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesDb(): Firestore {\n if (!firestoreDb) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firestoreDb;\n}\n\n/**\n * Get the shared-features Auth instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesAuth(): Auth {\n if (!firebaseAuth) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firebaseAuth;\n}\n\n/**\n * Get the device ID for anonymous tracking.\n * Returns null if not initialized.\n */\nexport function getDeviceId(): string | null {\n return getState().deviceId;\n}\n","/**\n * Campaigns Service\n *\n * Handles fetching and displaying advertising campaigns from the\n * centralized aoneahsan.com Firebase backend.\n *\n * Consumer projects only need read access - all campaign management\n * is done through the aoneahsan.com admin panel.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDocs,\n getDoc,\n query,\n where,\n orderBy,\n limit as firestoreLimit,\n Timestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig } from '../firebase/config';\nimport type {\n Campaign,\n CampaignWithProduct,\n Product,\n AdPlacement,\n CampaignStatus,\n FetchCampaignsOptions,\n} from '../types/campaigns';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_CAMPAIGNS = 'zaions_campaigns';\nconst COLLECTION_PRODUCTS = 'zaions_products';\n\n// Cache for campaigns\ninterface CampaignsCache {\n data: Campaign[];\n timestamp: number;\n}\nlet campaignsCache: CampaignsCache | null = null;\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\n// Products cache\nlet productsCache: Map<string, Product> | null = null;\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Check if cache is valid\n */\nfunction isCacheValid(): boolean {\n if (!campaignsCache) return false;\n return Date.now() - campaignsCache.timestamp < CACHE_TTL_MS;\n}\n\n/**\n * Convert Firestore document to Campaign\n */\nfunction docToCampaign(\n docId: string,\n data: Record<string, unknown>\n): Campaign {\n return {\n id: docId,\n productId: data.productId as string,\n name: data.name as string,\n status: data.status as CampaignStatus,\n targetPlatforms: data.targetPlatforms as Campaign['targetPlatforms'],\n targetAudience: data.targetAudience as Campaign['targetAudience'],\n targetProjects: (data.targetProjects as string[]) || [],\n excludeProductUsers: data.excludeProductUsers as boolean,\n placements: data.placements as AdPlacement[],\n priority: data.priority as number,\n frequencyDays: data.frequencyDays as number,\n maxImpressions: data.maxImpressions as number | null,\n startDate: data.startDate as Timestamp,\n endDate: data.endDate as Timestamp | null,\n variant: data.variant as Campaign['variant'],\n customTitle: data.customTitle as string | undefined,\n customTagline: data.customTagline as string | undefined,\n customCta: data.customCta as string | undefined,\n customCtaUrl: data.customCtaUrl as string | undefined,\n customDescription: data.customDescription as string | undefined,\n customProductColor: data.customProductColor as string | undefined,\n customIcon: data.customIcon as string | undefined,\n customFeatures: data.customFeatures as string[] | undefined,\n totalImpressions: data.totalImpressions as number,\n totalClicks: data.totalClicks as number,\n totalCloses: data.totalCloses as number,\n createdAt: data.createdAt as Timestamp,\n updatedAt: data.updatedAt as Timestamp,\n createdBy: data.createdBy as string,\n updatedBy: data.updatedBy as string | undefined,\n };\n}\n\n/**\n * Convert Firestore document to Product\n */\nfunction docToProduct(docId: string, data: Record<string, unknown>): Product {\n return {\n id: docId,\n name: data.name as string,\n tagline: data.tagline as string,\n description: data.description as string,\n type: data.type as Product['type'],\n url: data.url as string,\n color: data.color as string,\n features: (data.features as string[]) || [],\n icon64: data.icon64 as string | undefined,\n icon128: data.icon128 as string | undefined,\n chromeStoreUrl: data.chromeStoreUrl as string | undefined,\n playStoreUrl: data.playStoreUrl as string | undefined,\n appStoreUrl: data.appStoreUrl as string | undefined,\n webUrl: data.webUrl as string | undefined,\n enabled: data.enabled as boolean,\n createdAt: data.createdAt as Timestamp,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\n// ============================================================================\n// PRODUCTS\n// ============================================================================\n\n/**\n * Fetch all products from Firestore\n */\nexport async function fetchProducts(): Promise<Product[]> {\n const db = getSharedFeaturesDb();\n const snapshot = await getDocs(collection(db, COLLECTION_PRODUCTS));\n const products = snapshot.docs.map((d) => docToProduct(d.id, d.data()));\n\n // Update cache\n productsCache = new Map(products.map((p) => [p.id, p]));\n\n return products;\n}\n\n/**\n * Get a product by ID (uses cache if available)\n */\nexport async function getProductById(\n productId: string\n): Promise<Product | null> {\n // Check cache first\n if (productsCache?.has(productId)) {\n return productsCache.get(productId) || null;\n }\n\n // Fetch from Firestore\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_PRODUCTS, productId));\n\n if (!docSnap.exists()) return null;\n\n const product = docToProduct(docSnap.id, docSnap.data());\n\n // Add to cache\n if (!productsCache) productsCache = new Map();\n productsCache.set(productId, product);\n\n return product;\n}\n\n// ============================================================================\n// CAMPAIGNS\n// ============================================================================\n\n/**\n * Fetch campaigns with optional filters\n */\nexport async function fetchCampaigns(\n options: FetchCampaignsOptions = {}\n): Promise<Campaign[]> {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n // Check cache first (only for unfiltered queries)\n if (\n !options.placement &&\n !options.status &&\n !options.productId &&\n isCacheValid()\n ) {\n return campaignsCache!.data;\n }\n\n let q = query(collection(db, COLLECTION_CAMPAIGNS));\n\n // Apply filters\n if (options.status) {\n q = query(q, where('status', '==', options.status));\n }\n\n if (options.productId) {\n q = query(q, where('productId', '==', options.productId));\n }\n\n q = query(q, orderBy('priority', 'desc'));\n\n if (options.limit) {\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let campaigns = snapshot.docs.map((d) => docToCampaign(d.id, d.data()));\n\n // Client-side filtering for array fields\n if (options.placement) {\n campaigns = campaigns.filter((c) =>\n c.placements.includes(options.placement!)\n );\n }\n\n // Filter by target platform (from config)\n campaigns = campaigns.filter(\n (c) =>\n c.targetPlatforms.includes(config.platform) ||\n c.targetPlatforms.length === 0\n );\n\n // Filter by target projects (empty = all projects)\n campaigns = campaigns.filter(\n (c) =>\n c.targetProjects.length === 0 ||\n c.targetProjects.includes(config.projectId)\n );\n\n // Cache unfiltered results\n if (!options.placement && !options.status && !options.productId) {\n campaignsCache = {\n data: campaigns,\n timestamp: Date.now(),\n };\n }\n\n return campaigns;\n}\n\n/**\n * Fetch active campaigns for a specific placement\n * Returns campaigns with resolved product data\n */\nexport async function fetchActiveCampaigns(\n placement: AdPlacement\n): Promise<CampaignWithProduct[]> {\n const now = Timestamp.now();\n\n const campaigns = await fetchCampaigns({ status: 'active' });\n\n // Ensure products are loaded\n if (!productsCache || productsCache.size === 0) {\n await fetchProducts();\n }\n\n // Filter by placement and date range\n const eligible = campaigns.filter((c) => {\n // Placement check\n if (!c.placements.includes(placement)) return false;\n\n // Date range check\n if (c.startDate.toMillis() > now.toMillis()) return false;\n if (c.endDate && c.endDate.toMillis() < now.toMillis()) return false;\n\n // Max impressions check\n if (c.maxImpressions !== null && c.totalImpressions >= c.maxImpressions)\n return false;\n\n return true;\n });\n\n // Resolve products\n const result: CampaignWithProduct[] = [];\n\n for (const campaign of eligible) {\n const product = await getProductById(campaign.productId);\n if (product && product.enabled) {\n result.push({ ...campaign, product });\n }\n }\n\n return result;\n}\n\n/**\n * Get a single campaign by ID\n */\nexport async function getCampaignById(\n campaignId: string\n): Promise<Campaign | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_CAMPAIGNS, campaignId));\n\n if (!docSnap.exists()) return null;\n return docToCampaign(docSnap.id, docSnap.data());\n}\n\n/**\n * Clear the campaigns cache (useful for manual refresh)\n */\nexport function clearCampaignsCache(): void {\n campaignsCache = null;\n}\n\n/**\n * Clear the products cache\n */\nexport function clearProductsCache(): void {\n productsCache = null;\n}\n","/**\n * Analytics Service\n *\n * Handles recording impressions, clicks, and other ad interactions\n * from consumer projects to the centralized aoneahsan.com Firebase.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n addDoc,\n serverTimestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig, getState } from '../firebase/config';\nimport type {\n AdAction,\n AdPlacement,\n AdVariant,\n RecordImpressionInput,\n AdHistoryEntry,\n} from '../types/campaigns';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_IMPRESSIONS = 'zaions_impressions';\nconst LOCAL_STORAGE_KEY = 'shared_features_ad_history';\n\n// ============================================================================\n// LOCAL HISTORY MANAGEMENT\n// ============================================================================\n\n/**\n * Get ad history from local storage\n */\nasync function getLocalAdHistory(): Promise<Record<string, AdHistoryEntry>> {\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: LOCAL_STORAGE_KEY });\n if (result.value) {\n return JSON.parse(result.value);\n }\n } catch {\n // Fallback to localStorage\n try {\n const stored = localStorage.getItem(LOCAL_STORAGE_KEY);\n if (stored) {\n return JSON.parse(stored);\n }\n } catch {\n // Ignore\n }\n }\n return {};\n}\n\n/**\n * Save ad history to local storage\n */\nasync function saveLocalAdHistory(\n history: Record<string, AdHistoryEntry>\n): Promise<void> {\n const serialized = JSON.stringify(history);\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.set({ key: LOCAL_STORAGE_KEY, value: serialized });\n } catch {\n // Fallback to localStorage\n try {\n localStorage.setItem(LOCAL_STORAGE_KEY, serialized);\n } catch {\n // Ignore storage errors\n }\n }\n}\n\n/**\n * Update local ad history for a campaign\n */\nasync function updateLocalHistory(\n campaignId: string,\n productId: string,\n action: AdAction,\n frequencyDays: number\n): Promise<void> {\n const history = await getLocalAdHistory();\n const now = Date.now();\n const nextEligibleAt = now + frequencyDays * 24 * 60 * 60 * 1000;\n\n const existing = history[campaignId];\n\n if (existing) {\n history[campaignId] = {\n ...existing,\n lastSeenAt: now,\n impressionCount:\n action === 'impression'\n ? existing.impressionCount + 1\n : existing.impressionCount,\n clicked: existing.clicked || action === 'click',\n closed: existing.closed || action === 'close',\n nextEligibleAt:\n action === 'impression' ? nextEligibleAt : existing.nextEligibleAt,\n };\n } else {\n history[campaignId] = {\n campaignId,\n productId,\n lastSeenAt: now,\n impressionCount: action === 'impression' ? 1 : 0,\n clicked: action === 'click',\n closed: action === 'close',\n nextEligibleAt,\n };\n }\n\n await saveLocalAdHistory(history);\n}\n\n// ============================================================================\n// ELIGIBILITY CHECKING\n// ============================================================================\n\n/**\n * Check if user is eligible to see a campaign (based on frequency capping)\n */\nexport async function isEligibleForCampaign(\n campaignId: string,\n _frequencyDays: number = 20\n): Promise<boolean> {\n // Note: _frequencyDays kept for API consistency; eligibility uses stored nextEligibleAt\n const history = await getLocalAdHistory();\n const entry = history[campaignId];\n\n if (!entry) return true;\n\n return Date.now() >= entry.nextEligibleAt;\n}\n\n/**\n * Get all campaigns the user is currently eligible to see\n */\nexport async function getEligibleCampaignIds(): Promise<string[]> {\n const history = await getLocalAdHistory();\n const now = Date.now();\n\n return Object.entries(history)\n .filter(([, entry]) => now >= entry.nextEligibleAt)\n .map(([campaignId]) => campaignId);\n}\n\n/**\n * Get local ad history for a campaign\n */\nexport async function getCampaignHistory(\n campaignId: string\n): Promise<AdHistoryEntry | null> {\n const history = await getLocalAdHistory();\n return history[campaignId] || null;\n}\n\n// ============================================================================\n// IMPRESSION RECORDING\n// ============================================================================\n\n/**\n * Record an ad impression, click, or close\n *\n * This sends the event to the centralized Firebase and updates local history.\n */\nexport async function recordImpression(\n input: RecordImpressionInput,\n frequencyDays: number = 20\n): Promise<void> {\n const config = getConfig();\n const state = getState();\n const db = getSharedFeaturesDb();\n\n const impressionData = {\n campaignId: input.campaignId,\n productId: input.productId,\n projectId: config.projectId,\n userId: null, // Consumer projects don't authenticate with aoneahsan.com\n deviceId: state.deviceId || 'unknown',\n platform: config.platform,\n placement: input.placement,\n action: input.action,\n variant: input.variant,\n timestamp: serverTimestamp(),\n ...(input.sessionId && { sessionId: input.sessionId }),\n };\n\n try {\n // Record to centralized Firebase\n await addDoc(collection(db, COLLECTION_IMPRESSIONS), impressionData);\n\n if (config.debug) {\n console.log('[shared-features] Recorded impression:', impressionData);\n }\n } catch (error) {\n if (config.debug) {\n console.error('[shared-features] Failed to record impression:', error);\n }\n // Don't throw - we don't want analytics failures to break the UI\n }\n\n // Update local history for frequency capping\n await updateLocalHistory(\n input.campaignId,\n input.productId,\n input.action,\n frequencyDays\n );\n}\n\n/**\n * Convenience function to record an impression\n */\nexport async function trackImpression(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant,\n frequencyDays?: number\n): Promise<void> {\n await recordImpression(\n {\n campaignId,\n productId,\n placement,\n action: 'impression',\n variant,\n },\n frequencyDays\n );\n}\n\n/**\n * Convenience function to record a click\n */\nexport async function trackClick(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant\n): Promise<void> {\n await recordImpression({\n campaignId,\n productId,\n placement,\n action: 'click',\n variant,\n });\n}\n\n/**\n * Convenience function to record a close/dismiss\n */\nexport async function trackClose(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant\n): Promise<void> {\n await recordImpression({\n campaignId,\n productId,\n placement,\n action: 'close',\n variant,\n });\n}\n"],"names":["firestoreLimit","state"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"analytics-40-S_fHC.js","sources":["../src/firebase/config.ts","../src/firebase/init.ts","../src/services/campaigns.ts","../src/services/analytics.ts"],"sourcesContent":["/**\n * Firebase Configuration Types\n *\n * Types and interfaces for configuring the shared-features Firebase connection.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\n/**\n * Firebase configuration object\n */\nexport interface FirebaseConfig {\n apiKey: string;\n authDomain: string;\n projectId: string;\n storageBucket?: string;\n messagingSenderId?: string;\n appId?: string;\n measurementId?: string;\n}\n\n/**\n * Target platform for the consumer project\n */\nexport type ConsumerPlatform = 'web' | 'android' | 'ios' | 'extension';\n\n/**\n * Configuration for initializing shared-features\n */\nexport interface SharedFeaturesConfig {\n /**\n * Firebase configuration for aoneahsan.com's Firebase project.\n * All values from environment variables.\n */\n firebaseConfig: FirebaseConfig;\n\n /**\n * Unique identifier for this project (e.g., 'ztools', '2fa-studio')\n */\n projectId: string;\n\n /**\n * Display name for this project (e.g., 'ZTools', '2FA Studio')\n */\n projectName: string;\n\n /**\n * Platform type for targeting\n */\n platform: ConsumerPlatform;\n\n /**\n * Whether to enable debug logging (default: false)\n */\n debug?: boolean;\n}\n\n/**\n * Internal state after initialization\n */\nexport interface SharedFeaturesState {\n /** Whether the package has been initialized */\n initialized: boolean;\n /** The active configuration */\n config: SharedFeaturesConfig | null;\n /** Unique device ID for anonymous tracking */\n deviceId: string | null;\n}\n\n/**\n * Singleton state\n */\nlet state: SharedFeaturesState = {\n initialized: false,\n config: null,\n deviceId: null,\n};\n\n/**\n * Get current state\n */\nexport function getState(): SharedFeaturesState {\n return state;\n}\n\n/**\n * Set state (internal use only)\n */\nexport function setState(newState: Partial<SharedFeaturesState>): void {\n state = { ...state, ...newState };\n}\n\n/**\n * Get configuration (throws if not initialized)\n */\nexport function getConfig(): SharedFeaturesConfig {\n if (!state.initialized || !state.config) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return state.config;\n}\n\n/**\n * Check if initialized\n */\nexport function isInitialized(): boolean {\n return state.initialized;\n}\n","/**\n * Firebase Initialization\n *\n * Initialize a secondary Firebase app for connecting to aoneahsan.com's\n * Firebase project from consumer projects.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport { initializeApp, getApps, FirebaseApp } from 'firebase/app';\nimport { getFirestore, Firestore } from 'firebase/firestore';\nimport { getAuth, Auth } from 'firebase/auth';\nimport {\n SharedFeaturesConfig,\n setState,\n getState,\n isInitialized,\n} from './config';\n\nconst SHARED_FEATURES_APP_NAME = 'shared-features';\n\nlet firebaseApp: FirebaseApp | null = null;\nlet firestoreDb: Firestore | null = null;\nlet firebaseAuth: Auth | null = null;\n\n/**\n * Generate a unique device ID for anonymous tracking\n */\nfunction generateDeviceId(): string {\n const timestamp = Date.now();\n const randomPart = Math.random().toString(36).substring(2, 15);\n return `device_${timestamp}_${randomPart}`;\n}\n\n/**\n * Get or create device ID from storage\n */\nasync function getOrCreateDeviceId(): Promise<string> {\n const STORAGE_KEY = 'shared_features_device_id';\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: STORAGE_KEY });\n if (result.value) {\n return result.value;\n }\n const newId = generateDeviceId();\n await Preferences.set({ key: STORAGE_KEY, value: newId });\n return newId;\n } catch {\n // Fallback to localStorage for web\n try {\n const stored = localStorage.getItem(STORAGE_KEY);\n if (stored) {\n return stored;\n }\n const newId = generateDeviceId();\n localStorage.setItem(STORAGE_KEY, newId);\n return newId;\n } catch {\n // If all storage fails, generate ephemeral ID\n return generateDeviceId();\n }\n }\n}\n\n/**\n * Initialize shared-features with the given configuration.\n *\n * This creates a secondary Firebase app connection to aoneahsan.com's\n * Firebase project, separate from the consumer project's own Firebase.\n *\n * @param config - Configuration object with Firebase config and project info\n * @returns Initialized state object\n *\n * @example\n * ```typescript\n * import { initSharedFeatures } from 'shared-features';\n *\n * initSharedFeatures({\n * firebaseConfig: {\n * apiKey: import.meta.env.VITE_SHARED_FEATURES_API_KEY,\n * authDomain: import.meta.env.VITE_SHARED_FEATURES_AUTH_DOMAIN,\n * projectId: import.meta.env.VITE_SHARED_FEATURES_PROJECT_ID,\n * },\n * projectId: 'ztools',\n * projectName: 'ZTools',\n * platform: 'web',\n * });\n * ```\n */\nexport async function initSharedFeatures(\n config: SharedFeaturesConfig\n): Promise<{ app: FirebaseApp; db: Firestore; auth: Auth }> {\n // Return existing instance if already initialized with same config\n if (isInitialized() && firebaseApp && firestoreDb && firebaseAuth) {\n const currentConfig = getState().config;\n if (\n currentConfig &&\n currentConfig.firebaseConfig.projectId === config.firebaseConfig.projectId\n ) {\n return { app: firebaseApp, db: firestoreDb, auth: firebaseAuth };\n }\n }\n\n // Check if app already exists\n const existingApps = getApps();\n const existingApp = existingApps.find(\n (app) => app.name === SHARED_FEATURES_APP_NAME\n );\n\n if (existingApp) {\n firebaseApp = existingApp;\n } else {\n // Initialize new Firebase app with config from environment variables\n firebaseApp = initializeApp(config.firebaseConfig, SHARED_FEATURES_APP_NAME);\n }\n\n // Get Firestore and Auth instances\n firestoreDb = getFirestore(firebaseApp);\n firebaseAuth = getAuth(firebaseApp);\n\n // Get or create device ID\n const deviceId = await getOrCreateDeviceId();\n\n // Update state\n setState({\n initialized: true,\n config,\n deviceId,\n });\n\n if (config.debug) {\n console.log('[shared-features] Initialized:', {\n projectId: config.projectId,\n projectName: config.projectName,\n platform: config.platform,\n deviceId,\n });\n }\n\n return { app: firebaseApp, db: firestoreDb, auth: firebaseAuth };\n}\n\n/**\n * Get the shared-features Firebase app instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesApp(): FirebaseApp {\n if (!firebaseApp) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firebaseApp;\n}\n\n/**\n * Get the shared-features Firestore instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesDb(): Firestore {\n if (!firestoreDb) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firestoreDb;\n}\n\n/**\n * Get the shared-features Auth instance.\n * Throws if not initialized.\n */\nexport function getSharedFeaturesAuth(): Auth {\n if (!firebaseAuth) {\n throw new Error(\n 'shared-features has not been initialized. Call initSharedFeatures() first.'\n );\n }\n return firebaseAuth;\n}\n\n/**\n * Get the device ID for anonymous tracking.\n * Returns null if not initialized.\n */\nexport function getDeviceId(): string | null {\n return getState().deviceId;\n}\n","/**\n * Campaigns Service\n *\n * Handles fetching and displaying advertising campaigns from the\n * centralized aoneahsan.com Firebase backend.\n *\n * Consumer projects only need read access - all campaign management\n * is done through the aoneahsan.com admin panel.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n doc,\n getDocs,\n getDoc,\n query,\n where,\n orderBy,\n limit as firestoreLimit,\n Timestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig } from '../firebase/config';\nimport type {\n Campaign,\n CampaignWithProduct,\n Product,\n AdPlacement,\n CampaignStatus,\n FetchCampaignsOptions,\n} from '../types/campaigns';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_CAMPAIGNS = 'zaions_campaigns';\nconst COLLECTION_PRODUCTS = 'zaions_products';\n\n// Cache for campaigns\ninterface CampaignsCache {\n data: Campaign[];\n timestamp: number;\n}\nlet campaignsCache: CampaignsCache | null = null;\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\n\n// Products cache\nlet productsCache: Map<string, Product> | null = null;\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Check if cache is valid\n */\nfunction isCacheValid(): boolean {\n if (!campaignsCache) return false;\n return Date.now() - campaignsCache.timestamp < CACHE_TTL_MS;\n}\n\n/**\n * Convert Firestore document to Campaign\n */\nfunction docToCampaign(\n docId: string,\n data: Record<string, unknown>\n): Campaign {\n return {\n id: docId,\n productId: data.productId as string,\n name: data.name as string,\n status: data.status as CampaignStatus,\n targetPlatforms: data.targetPlatforms as Campaign['targetPlatforms'],\n targetAudience: data.targetAudience as Campaign['targetAudience'],\n targetProjects: (data.targetProjects as string[]) || [],\n excludeProductUsers: data.excludeProductUsers as boolean,\n placements: data.placements as AdPlacement[],\n priority: data.priority as number,\n frequencyDays: data.frequencyDays as number,\n maxImpressions: data.maxImpressions as number | null,\n startDate: data.startDate as Timestamp,\n endDate: data.endDate as Timestamp | null,\n variant: data.variant as Campaign['variant'],\n customTitle: data.customTitle as string | undefined,\n customTagline: data.customTagline as string | undefined,\n customCta: data.customCta as string | undefined,\n customCtaUrl: data.customCtaUrl as string | undefined,\n customDescription: data.customDescription as string | undefined,\n customProductColor: data.customProductColor as string | undefined,\n customIcon: data.customIcon as string | undefined,\n customFeatures: data.customFeatures as string[] | undefined,\n totalImpressions: data.totalImpressions as number,\n totalClicks: data.totalClicks as number,\n totalCloses: data.totalCloses as number,\n createdAt: data.createdAt as Timestamp,\n updatedAt: data.updatedAt as Timestamp,\n createdBy: data.createdBy as string,\n updatedBy: data.updatedBy as string | undefined,\n };\n}\n\n/**\n * Convert Firestore document to Product\n */\nfunction docToProduct(docId: string, data: Record<string, unknown>): Product {\n return {\n id: docId,\n name: data.name as string,\n tagline: data.tagline as string,\n description: data.description as string,\n type: data.type as Product['type'],\n url: data.url as string,\n color: data.color as string,\n features: (data.features as string[]) || [],\n icon64: data.icon64 as string | undefined,\n icon128: data.icon128 as string | undefined,\n chromeStoreUrl: data.chromeStoreUrl as string | undefined,\n playStoreUrl: data.playStoreUrl as string | undefined,\n appStoreUrl: data.appStoreUrl as string | undefined,\n webUrl: data.webUrl as string | undefined,\n enabled: data.enabled as boolean,\n createdAt: data.createdAt as Timestamp,\n updatedAt: data.updatedAt as Timestamp,\n };\n}\n\n// ============================================================================\n// PRODUCTS\n// ============================================================================\n\n/**\n * Fetch all products from Firestore\n */\nexport async function fetchProducts(): Promise<Product[]> {\n const db = getSharedFeaturesDb();\n const snapshot = await getDocs(collection(db, COLLECTION_PRODUCTS));\n const products = snapshot.docs.map((d) => docToProduct(d.id, d.data()));\n\n // Update cache\n productsCache = new Map(products.map((p) => [p.id, p]));\n\n return products;\n}\n\n/**\n * Get a product by ID (uses cache if available)\n */\nexport async function getProductById(\n productId: string\n): Promise<Product | null> {\n // Check cache first\n if (productsCache?.has(productId)) {\n return productsCache.get(productId) || null;\n }\n\n // Fetch from Firestore\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_PRODUCTS, productId));\n\n if (!docSnap.exists()) return null;\n\n const product = docToProduct(docSnap.id, docSnap.data());\n\n // Add to cache\n if (!productsCache) productsCache = new Map();\n productsCache.set(productId, product);\n\n return product;\n}\n\n// ============================================================================\n// CAMPAIGNS\n// ============================================================================\n\n/**\n * Fetch campaigns with optional filters\n */\nexport async function fetchCampaigns(\n options: FetchCampaignsOptions = {}\n): Promise<Campaign[]> {\n const config = getConfig();\n const db = getSharedFeaturesDb();\n\n // Check cache first (only for unfiltered queries)\n if (\n !options.placement &&\n !options.status &&\n !options.productId &&\n isCacheValid()\n ) {\n return campaignsCache!.data;\n }\n\n let q = query(collection(db, COLLECTION_CAMPAIGNS));\n\n // Apply filters\n if (options.status) {\n q = query(q, where('status', '==', options.status));\n }\n\n if (options.productId) {\n q = query(q, where('productId', '==', options.productId));\n }\n\n q = query(q, orderBy('priority', 'desc'));\n\n if (options.limit) {\n q = query(q, firestoreLimit(options.limit));\n }\n\n const snapshot = await getDocs(q);\n let campaigns = snapshot.docs.map((d) => docToCampaign(d.id, d.data()));\n\n // Client-side filtering for array fields\n if (options.placement) {\n campaigns = campaigns.filter((c) =>\n c.placements.includes(options.placement!)\n );\n }\n\n // Filter by target platform (from config)\n campaigns = campaigns.filter(\n (c) =>\n c.targetPlatforms.includes(config.platform) ||\n c.targetPlatforms.length === 0\n );\n\n // Filter by target projects (empty = all projects)\n campaigns = campaigns.filter(\n (c) =>\n c.targetProjects.length === 0 ||\n c.targetProjects.includes(config.projectId)\n );\n\n // Cache unfiltered results\n if (!options.placement && !options.status && !options.productId) {\n campaignsCache = {\n data: campaigns,\n timestamp: Date.now(),\n };\n }\n\n return campaigns;\n}\n\n/**\n * Fetch active campaigns for a specific placement\n * Returns campaigns with resolved product data\n */\nexport async function fetchActiveCampaigns(\n placement: AdPlacement\n): Promise<CampaignWithProduct[]> {\n const now = Timestamp.now();\n\n const campaigns = await fetchCampaigns({ status: 'active' });\n\n // Ensure products are loaded\n if (!productsCache || productsCache.size === 0) {\n await fetchProducts();\n }\n\n // Filter by placement and date range\n const eligible = campaigns.filter((c) => {\n // Placement check\n if (!c.placements.includes(placement)) return false;\n\n // Date range check\n if (c.startDate.toMillis() > now.toMillis()) return false;\n if (c.endDate && c.endDate.toMillis() < now.toMillis()) return false;\n\n // Max impressions check\n if (c.maxImpressions !== null && c.totalImpressions >= c.maxImpressions)\n return false;\n\n return true;\n });\n\n // Resolve products\n const result: CampaignWithProduct[] = [];\n\n for (const campaign of eligible) {\n const product = await getProductById(campaign.productId);\n if (product && product.enabled) {\n result.push({ ...campaign, product });\n }\n }\n\n return result;\n}\n\n/**\n * Get a single campaign by ID\n */\nexport async function getCampaignById(\n campaignId: string\n): Promise<Campaign | null> {\n const db = getSharedFeaturesDb();\n const docSnap = await getDoc(doc(db, COLLECTION_CAMPAIGNS, campaignId));\n\n if (!docSnap.exists()) return null;\n return docToCampaign(docSnap.id, docSnap.data());\n}\n\n/**\n * Clear the campaigns cache (useful for manual refresh)\n */\nexport function clearCampaignsCache(): void {\n campaignsCache = null;\n}\n\n/**\n * Clear the products cache\n */\nexport function clearProductsCache(): void {\n productsCache = null;\n}\n","/**\n * Analytics Service\n *\n * Handles recording impressions, clicks, and other ad interactions\n * from consumer projects to the centralized aoneahsan.com Firebase.\n *\n * @author Ahsan Mahmood <aoneahsan@gmail.com>\n */\n\nimport {\n collection,\n addDoc,\n serverTimestamp,\n} from 'firebase/firestore';\nimport { getSharedFeaturesDb } from '../firebase/init';\nimport { getConfig, getState } from '../firebase/config';\nimport type {\n AdAction,\n AdPlacement,\n AdVariant,\n RecordImpressionInput,\n AdHistoryEntry,\n} from '../types/campaigns';\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst COLLECTION_IMPRESSIONS = 'zaions_impressions';\nconst LOCAL_STORAGE_KEY = 'shared_features_ad_history';\n\n// ============================================================================\n// LOCAL HISTORY MANAGEMENT\n// ============================================================================\n\n/**\n * Get ad history from local storage\n */\nasync function getLocalAdHistory(): Promise<Record<string, AdHistoryEntry>> {\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n const result = await Preferences.get({ key: LOCAL_STORAGE_KEY });\n if (result.value) {\n return JSON.parse(result.value);\n }\n } catch {\n // Fallback to localStorage\n try {\n const stored = localStorage.getItem(LOCAL_STORAGE_KEY);\n if (stored) {\n return JSON.parse(stored);\n }\n } catch {\n // Ignore\n }\n }\n return {};\n}\n\n/**\n * Save ad history to local storage\n */\nasync function saveLocalAdHistory(\n history: Record<string, AdHistoryEntry>\n): Promise<void> {\n const serialized = JSON.stringify(history);\n\n try {\n // Try Capacitor Preferences first\n const { Preferences } = await import('@capacitor/preferences');\n await Preferences.set({ key: LOCAL_STORAGE_KEY, value: serialized });\n } catch {\n // Fallback to localStorage\n try {\n localStorage.setItem(LOCAL_STORAGE_KEY, serialized);\n } catch {\n // Ignore storage errors\n }\n }\n}\n\n/**\n * Update local ad history for a campaign\n */\nasync function updateLocalHistory(\n campaignId: string,\n productId: string,\n action: AdAction,\n frequencyDays: number\n): Promise<void> {\n const history = await getLocalAdHistory();\n const now = Date.now();\n const nextEligibleAt = now + frequencyDays * 24 * 60 * 60 * 1000;\n\n const existing = history[campaignId];\n\n if (existing) {\n history[campaignId] = {\n ...existing,\n lastSeenAt: now,\n impressionCount:\n action === 'impression'\n ? existing.impressionCount + 1\n : existing.impressionCount,\n clicked: existing.clicked || action === 'click',\n closed: existing.closed || action === 'close',\n nextEligibleAt:\n action === 'impression' ? nextEligibleAt : existing.nextEligibleAt,\n };\n } else {\n history[campaignId] = {\n campaignId,\n productId,\n lastSeenAt: now,\n impressionCount: action === 'impression' ? 1 : 0,\n clicked: action === 'click',\n closed: action === 'close',\n nextEligibleAt,\n };\n }\n\n await saveLocalAdHistory(history);\n}\n\n// ============================================================================\n// ELIGIBILITY CHECKING\n// ============================================================================\n\n/**\n * Check if user is eligible to see a campaign (based on frequency capping)\n */\nexport async function isEligibleForCampaign(\n campaignId: string,\n _frequencyDays: number = 20\n): Promise<boolean> {\n // Note: _frequencyDays kept for API consistency; eligibility uses stored nextEligibleAt\n const history = await getLocalAdHistory();\n const entry = history[campaignId];\n\n if (!entry) return true;\n\n return Date.now() >= entry.nextEligibleAt;\n}\n\n/**\n * Get all campaigns the user is currently eligible to see\n */\nexport async function getEligibleCampaignIds(): Promise<string[]> {\n const history = await getLocalAdHistory();\n const now = Date.now();\n\n return Object.entries(history)\n .filter(([, entry]) => now >= entry.nextEligibleAt)\n .map(([campaignId]) => campaignId);\n}\n\n/**\n * Get local ad history for a campaign\n */\nexport async function getCampaignHistory(\n campaignId: string\n): Promise<AdHistoryEntry | null> {\n const history = await getLocalAdHistory();\n return history[campaignId] || null;\n}\n\n// ============================================================================\n// IMPRESSION RECORDING\n// ============================================================================\n\n/**\n * Record an ad impression, click, or close\n *\n * This sends the event to the centralized Firebase and updates local history.\n */\nexport async function recordImpression(\n input: RecordImpressionInput,\n frequencyDays: number = 20\n): Promise<void> {\n const config = getConfig();\n const state = getState();\n const db = getSharedFeaturesDb();\n\n const impressionData = {\n campaignId: input.campaignId,\n productId: input.productId,\n projectId: config.projectId,\n userId: null, // Consumer projects don't authenticate with aoneahsan.com\n deviceId: state.deviceId || 'unknown',\n platform: config.platform,\n placement: input.placement,\n action: input.action,\n variant: input.variant,\n timestamp: serverTimestamp(),\n ...(input.sessionId && { sessionId: input.sessionId }),\n };\n\n try {\n // Record to centralized Firebase\n await addDoc(collection(db, COLLECTION_IMPRESSIONS), impressionData);\n\n if (config.debug) {\n console.log('[shared-features] Recorded impression:', impressionData);\n }\n } catch (error) {\n if (config.debug) {\n console.error('[shared-features] Failed to record impression:', error);\n }\n // Don't throw - we don't want analytics failures to break the UI\n }\n\n // Update local history for frequency capping\n await updateLocalHistory(\n input.campaignId,\n input.productId,\n input.action,\n frequencyDays\n );\n}\n\n/**\n * Convenience function to record an impression\n */\nexport async function trackImpression(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant,\n frequencyDays?: number\n): Promise<void> {\n await recordImpression(\n {\n campaignId,\n productId,\n placement,\n action: 'impression',\n variant,\n },\n frequencyDays\n );\n}\n\n/**\n * Convenience function to record a click\n */\nexport async function trackClick(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant\n): Promise<void> {\n await recordImpression({\n campaignId,\n productId,\n placement,\n action: 'click',\n variant,\n });\n}\n\n/**\n * Convenience function to record a close/dismiss\n */\nexport async function trackClose(\n campaignId: string,\n productId: string,\n placement: AdPlacement,\n variant: AdVariant\n): Promise<void> {\n await recordImpression({\n campaignId,\n productId,\n placement,\n action: 'close',\n variant,\n });\n}\n"],"names":["firestoreLimit","state"],"mappings":"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"}
|
|
@@ -450,6 +450,7 @@ exports.getProductById = getProductById;
|
|
|
450
450
|
exports.getSharedFeaturesApp = getSharedFeaturesApp;
|
|
451
451
|
exports.getSharedFeaturesAuth = getSharedFeaturesAuth;
|
|
452
452
|
exports.getSharedFeaturesDb = getSharedFeaturesDb;
|
|
453
|
+
exports.getState = getState;
|
|
453
454
|
exports.initSharedFeatures = initSharedFeatures;
|
|
454
455
|
exports.isEligibleForCampaign = isEligibleForCampaign;
|
|
455
456
|
exports.isInitialized = isInitialized;
|
|
@@ -457,4 +458,4 @@ exports.recordImpression = recordImpression;
|
|
|
457
458
|
exports.trackClick = trackClick;
|
|
458
459
|
exports.trackClose = trackClose;
|
|
459
460
|
exports.trackImpression = trackImpression;
|
|
460
|
-
//# sourceMappingURL=analytics
|
|
461
|
+
//# sourceMappingURL=analytics-lEzOx2vl.cjs.map
|