backend-manager 5.0.91 → 5.0.93
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/CHANGELOG.md +2 -2
- package/CLAUDE.md +14 -6
- package/README.md +6 -6
- package/TODO-MARKETING.md +3 -0
- package/TODO-PAYMENT-v2.md +71 -0
- package/TODO.md +7 -0
- package/package.json +3 -3
- package/src/cli/commands/{emulators.js → emulator.js} +15 -15
- package/src/cli/commands/index.js +1 -1
- package/src/cli/commands/setup-tests/{emulators-config.js → emulator-config.js} +4 -4
- package/src/cli/commands/setup-tests/index.js +2 -2
- package/src/cli/commands/setup-tests/project-id-consistency.js +1 -1
- package/src/cli/commands/test.js +16 -16
- package/src/cli/index.js +4 -4
- package/src/manager/events/auth/on-create.js +5 -158
- package/src/manager/events/firestore/payments-webhooks/analytics.js +4 -3
- package/src/manager/events/firestore/payments-webhooks/on-write.js +56 -6
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-completed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/one-time/purchase-failed.js +1 -1
- package/src/manager/events/firestore/payments-webhooks/transitions/send-email.js +32 -28
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/cancellation-requested.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/new-subscription.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-failed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/payment-recovered.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/plan-changed.js +3 -3
- package/src/manager/events/firestore/payments-webhooks/transitions/subscription/subscription-cancelled.js +3 -3
- package/src/manager/functions/core/actions/api/admin/send-email.js +0 -131
- package/src/manager/functions/core/actions/api/general/add-marketing-contact.js +2 -137
- package/src/manager/functions/core/actions/api/general/emails/general:download-app-link.js +2 -2
- package/src/manager/functions/core/actions/api/user/sign-up.js +1 -1
- package/src/manager/index.js +12 -0
- package/src/manager/libraries/email.js +523 -0
- package/src/manager/libraries/infer-contact.js +140 -0
- package/src/manager/libraries/prompts/infer-contact.md +43 -0
- package/src/manager/routes/admin/backup/post.js +4 -3
- package/src/manager/routes/admin/email/post.js +11 -428
- package/src/manager/routes/admin/hook/post.js +3 -2
- package/src/manager/routes/admin/notification/post.js +14 -12
- package/src/manager/routes/admin/post/post.js +5 -6
- package/src/manager/routes/admin/post/put.js +3 -2
- package/src/manager/routes/admin/stats/get.js +19 -10
- package/src/manager/routes/general/email/post.js +8 -21
- package/src/manager/routes/general/email/templates/download-app-link.js +2 -2
- package/src/manager/routes/marketing/contact/post.js +2 -100
- package/src/manager/routes/payments/intent/post.js +0 -2
- package/src/manager/routes/payments/intent/processors/test.js +9 -10
- package/src/manager/routes/user/oauth2/_helpers.js +3 -2
- package/src/manager/routes/user/oauth2/delete.js +3 -3
- package/src/manager/routes/user/oauth2/get.js +2 -2
- package/src/manager/routes/user/oauth2/post.js +9 -9
- package/src/manager/routes/user/sessions/delete.js +4 -3
- package/src/manager/routes/user/signup/post.js +254 -54
- package/src/manager/schemas/admin/email/post.js +13 -8
- package/src/test/run-tests.js +1 -1
- package/test/functions/admin/send-email.js +1 -88
- package/test/helpers/email.js +421 -0
- package/test/helpers/infer-contact.js +299 -0
- package/test/routes/admin/email.js +41 -90
- package/REFACTOR-BEM-API.md +0 -76
- package/REFACTOR-MIDDLEWARE.md +0 -62
- package/REFACTOR-PAYMENT.md +0 -66
|
@@ -39,7 +39,7 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
|
|
|
39
39
|
|
|
40
40
|
// Update stats if requested
|
|
41
41
|
if (settings.update) {
|
|
42
|
-
const error = await updateStats(
|
|
42
|
+
const error = await updateStats(assistant, data, settings.update);
|
|
43
43
|
|
|
44
44
|
if (error) {
|
|
45
45
|
return assistant.respond(error.message, { code: 500 });
|
|
@@ -58,7 +58,9 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
|
|
|
58
58
|
return assistant.respond(data);
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
async function updateStats(
|
|
61
|
+
async function updateStats(assistant, existingData, update) {
|
|
62
|
+
const Manager = assistant.Manager;
|
|
63
|
+
const { admin } = Manager.libraries;
|
|
62
64
|
const stats = admin.firestore().doc('meta/stats');
|
|
63
65
|
const newData = {
|
|
64
66
|
app: Manager.config?.app?.id || null,
|
|
@@ -70,7 +72,7 @@ async function updateStats(admin, assistant, Manager, existingData, update) {
|
|
|
70
72
|
|
|
71
73
|
// Update notification stats
|
|
72
74
|
if (update === true || update?.notifications) {
|
|
73
|
-
const count = await getAllNotifications(
|
|
75
|
+
const count = await getAllNotifications(assistant).catch((e) => e);
|
|
74
76
|
|
|
75
77
|
if (count instanceof Error) {
|
|
76
78
|
error = new Error(`Failed getting notifications: ${count.message}`);
|
|
@@ -81,7 +83,7 @@ async function updateStats(admin, assistant, Manager, existingData, update) {
|
|
|
81
83
|
|
|
82
84
|
// Update subscription stats
|
|
83
85
|
if (!error && (update === true || update?.subscriptions)) {
|
|
84
|
-
const subscriptions = await getAllSubscriptions(
|
|
86
|
+
const subscriptions = await getAllSubscriptions(assistant).catch((e) => e);
|
|
85
87
|
|
|
86
88
|
if (subscriptions instanceof Error) {
|
|
87
89
|
error = new Error(`Failed getting subscriptions: ${subscriptions.message}`);
|
|
@@ -92,7 +94,7 @@ async function updateStats(admin, assistant, Manager, existingData, update) {
|
|
|
92
94
|
|
|
93
95
|
// Update user stats
|
|
94
96
|
if (!error && (!existingData?.users?.total || update === true || update?.users)) {
|
|
95
|
-
const users = await getAllUsers(
|
|
97
|
+
const users = await getAllUsers(assistant).catch((e) => e);
|
|
96
98
|
|
|
97
99
|
if (users instanceof Error) {
|
|
98
100
|
error = new Error(`Failed getting users: ${users.message}`);
|
|
@@ -103,7 +105,7 @@ async function updateStats(admin, assistant, Manager, existingData, update) {
|
|
|
103
105
|
|
|
104
106
|
// Update online users
|
|
105
107
|
if (!error && (update === true || update?.online)) {
|
|
106
|
-
const online = await countOnlineUsers(
|
|
108
|
+
const online = await countOnlineUsers(assistant);
|
|
107
109
|
|
|
108
110
|
_.set(newData, 'users.online', online);
|
|
109
111
|
}
|
|
@@ -125,7 +127,9 @@ async function updateStats(admin, assistant, Manager, existingData, update) {
|
|
|
125
127
|
return error;
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
async function getAllUsers(
|
|
130
|
+
async function getAllUsers(assistant) {
|
|
131
|
+
const { admin } = assistant.Manager.libraries;
|
|
132
|
+
|
|
129
133
|
assistant.log('getAllUsers(): Starting...');
|
|
130
134
|
|
|
131
135
|
const users = [];
|
|
@@ -142,7 +146,9 @@ async function getAllUsers(admin, assistant) {
|
|
|
142
146
|
return users;
|
|
143
147
|
}
|
|
144
148
|
|
|
145
|
-
async function getAllNotifications(
|
|
149
|
+
async function getAllNotifications(assistant) {
|
|
150
|
+
const { admin } = assistant.Manager.libraries;
|
|
151
|
+
|
|
146
152
|
assistant.log('getAllNotifications(): Starting...');
|
|
147
153
|
|
|
148
154
|
const snap = await admin.firestore().collection('notifications').count().get();
|
|
@@ -153,7 +159,9 @@ async function getAllNotifications(admin, assistant) {
|
|
|
153
159
|
return count;
|
|
154
160
|
}
|
|
155
161
|
|
|
156
|
-
async function getAllSubscriptions(
|
|
162
|
+
async function getAllSubscriptions(assistant) {
|
|
163
|
+
const { admin } = assistant.Manager.libraries;
|
|
164
|
+
|
|
157
165
|
assistant.log('getAllSubscriptions(): Starting...');
|
|
158
166
|
|
|
159
167
|
const snapshot = await admin.firestore().collection('users')
|
|
@@ -195,7 +203,8 @@ async function getAllSubscriptions(admin, assistant) {
|
|
|
195
203
|
return stats;
|
|
196
204
|
}
|
|
197
205
|
|
|
198
|
-
async function countOnlineUsers(
|
|
206
|
+
async function countOnlineUsers(assistant) {
|
|
207
|
+
const { admin } = assistant.Manager.libraries;
|
|
199
208
|
let online = 0;
|
|
200
209
|
|
|
201
210
|
const paths = ['gatherings/online', 'sessions/app', 'sessions/online'];
|
|
@@ -4,10 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const { merge } = require('lodash');
|
|
7
|
-
|
|
8
|
-
module.exports = async ({ assistant, Manager, settings, analytics }) => {
|
|
9
|
-
const fetch = Manager.require('wonderful-fetch');
|
|
10
|
-
|
|
7
|
+
module.exports = async ({ assistant, Manager, settings }) => {
|
|
11
8
|
// Validate required parameters
|
|
12
9
|
if (!settings.id) {
|
|
13
10
|
return assistant.respond('Parameter {id} is required.', { code: 400 });
|
|
@@ -22,10 +19,7 @@ module.exports = async ({ assistant, Manager, settings, analytics }) => {
|
|
|
22
19
|
email: 3,
|
|
23
20
|
},
|
|
24
21
|
delay: 1,
|
|
25
|
-
payload: {
|
|
26
|
-
backendManagerKey: process.env.BACKEND_MANAGER_KEY,
|
|
27
|
-
app: Manager.config.app.id,
|
|
28
|
-
},
|
|
22
|
+
payload: {},
|
|
29
23
|
};
|
|
30
24
|
|
|
31
25
|
// Load email template
|
|
@@ -75,25 +69,18 @@ module.exports = async ({ assistant, Manager, settings, analytics }) => {
|
|
|
75
69
|
|
|
76
70
|
assistant.log('Email payload:', emailPayload);
|
|
77
71
|
|
|
78
|
-
// Send
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
response: 'json',
|
|
82
|
-
log: true,
|
|
83
|
-
headers: {
|
|
84
|
-
'Authorization': `Bearer ${process.env.BACKEND_MANAGER_KEY}`,
|
|
85
|
-
},
|
|
86
|
-
body: emailPayload.payload,
|
|
87
|
-
}).catch(e => e);
|
|
72
|
+
// Send email directly via library
|
|
73
|
+
const email = Manager.Email(assistant);
|
|
74
|
+
const result = await email.send(emailPayload.payload).catch(e => e);
|
|
88
75
|
|
|
89
76
|
if (result instanceof Error) {
|
|
90
|
-
return assistant.respond(
|
|
77
|
+
return assistant.respond(result.message, { code: result.code || 500, sentry: result.code !== 400 });
|
|
91
78
|
}
|
|
92
79
|
|
|
93
|
-
assistant.log('Response:', result);
|
|
80
|
+
assistant.log('Response:', result.status);
|
|
94
81
|
|
|
95
82
|
// Track analytics
|
|
96
|
-
analytics.event('general/email', { id: settings.id });
|
|
83
|
+
assistant.analytics.event('general/email', { id: settings.id });
|
|
97
84
|
|
|
98
85
|
return assistant.respond({ success: true });
|
|
99
86
|
};
|
|
@@ -16,8 +16,8 @@ module.exports = function (payload, config) {
|
|
|
16
16
|
},
|
|
17
17
|
categories: ['download'],
|
|
18
18
|
subject: `Free ${config.brand.name} download link for ${payload.name || 'you'}!`,
|
|
19
|
-
template: '
|
|
20
|
-
group:
|
|
19
|
+
template: 'main/misc/app-download-link',
|
|
20
|
+
group: 'marketing',
|
|
21
21
|
copy: false,
|
|
22
22
|
data: {},
|
|
23
23
|
}
|
|
@@ -9,9 +9,7 @@ const dns = require('dns').promises;
|
|
|
9
9
|
// Load disposable domains list
|
|
10
10
|
const DISPOSABLE_DOMAINS = require(path.join(__dirname, '..', '..', '..', 'libraries', 'disposable-domains.json'));
|
|
11
11
|
const DISPOSABLE_SET = new Set(DISPOSABLE_DOMAINS.map(d => d.toLowerCase()));
|
|
12
|
-
|
|
13
|
-
// Load OpenAI library
|
|
14
|
-
const OpenAI = require(path.join(__dirname, '..', '..', '..', 'libraries', 'openai'));
|
|
12
|
+
const { inferContact } = require(path.join(__dirname, '..', '..', '..', 'libraries', 'infer-contact.js'));
|
|
15
13
|
|
|
16
14
|
module.exports = async ({ assistant, Manager, settings, analytics }) => {
|
|
17
15
|
|
|
@@ -100,7 +98,7 @@ module.exports = async ({ assistant, Manager, settings, analytics }) => {
|
|
|
100
98
|
// Infer name if not provided
|
|
101
99
|
let nameInferred = null;
|
|
102
100
|
if (!firstName && !lastName) {
|
|
103
|
-
nameInferred = await
|
|
101
|
+
nameInferred = await inferContact(email, assistant);
|
|
104
102
|
firstName = nameInferred.firstName;
|
|
105
103
|
lastName = nameInferred.lastName;
|
|
106
104
|
}
|
|
@@ -212,102 +210,6 @@ async function validateWithZeroBounce(email) {
|
|
|
212
210
|
}
|
|
213
211
|
}
|
|
214
212
|
|
|
215
|
-
// Helper: Infer name from email
|
|
216
|
-
async function inferName(email, assistant) {
|
|
217
|
-
if (process.env.OPENAI_API_KEY) {
|
|
218
|
-
const aiResult = await inferNameWithAI(email, assistant);
|
|
219
|
-
if (aiResult && (aiResult.firstName || aiResult.lastName)) {
|
|
220
|
-
return aiResult;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return inferNameFromEmail(email);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Helper: Use AI to infer name
|
|
228
|
-
async function inferNameWithAI(email, assistant) {
|
|
229
|
-
try {
|
|
230
|
-
const ai = new OpenAI(assistant);
|
|
231
|
-
const result = await ai.request({
|
|
232
|
-
model: 'gpt-5-mini',
|
|
233
|
-
timeout: 30000,
|
|
234
|
-
maxTokens: 1024,
|
|
235
|
-
moderate: false,
|
|
236
|
-
response: 'json',
|
|
237
|
-
prompt: {
|
|
238
|
-
content: `
|
|
239
|
-
<identity>
|
|
240
|
-
You extract names and company from email addresses.
|
|
241
|
-
</identity>
|
|
242
|
-
|
|
243
|
-
<format>
|
|
244
|
-
Return ONLY valid JSON like so:
|
|
245
|
-
{
|
|
246
|
-
"firstName": "...",
|
|
247
|
-
"lastName": "...",
|
|
248
|
-
"company": "...",
|
|
249
|
-
"confidence": "..."
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
If you cannot determine a name, use empty strings.
|
|
253
|
-
</format>
|
|
254
|
-
`,
|
|
255
|
-
},
|
|
256
|
-
message: {
|
|
257
|
-
content: `Email: ${email}`,
|
|
258
|
-
},
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
if (result?.firstName !== undefined) {
|
|
262
|
-
return {
|
|
263
|
-
firstName: capitalize(result.firstName || ''),
|
|
264
|
-
lastName: capitalize(result.lastName || ''),
|
|
265
|
-
company: capitalize(result.company || ''),
|
|
266
|
-
confidence: typeof result.confidence === 'number' ? result.confidence : 0.5,
|
|
267
|
-
method: 'ai',
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
} catch (e) {
|
|
271
|
-
console.error('AI name inference error:', e);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Helper: Regex-based name inference
|
|
278
|
-
function inferNameFromEmail(email) {
|
|
279
|
-
const local = email.split('@')[0];
|
|
280
|
-
const cleaned = local.replace(/[0-9]+$/, '');
|
|
281
|
-
const parts = cleaned.split(/[._-]/);
|
|
282
|
-
|
|
283
|
-
if (parts.length >= 2) {
|
|
284
|
-
return {
|
|
285
|
-
firstName: capitalize(parts[0]),
|
|
286
|
-
lastName: capitalize(parts.slice(1).join(' ')),
|
|
287
|
-
confidence: 0.5,
|
|
288
|
-
method: 'regex',
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return {
|
|
293
|
-
firstName: capitalize(cleaned),
|
|
294
|
-
lastName: '',
|
|
295
|
-
confidence: 0.25,
|
|
296
|
-
method: 'regex',
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Helper: Capitalize string
|
|
301
|
-
function capitalize(str) {
|
|
302
|
-
if (!str) {
|
|
303
|
-
return '';
|
|
304
|
-
}
|
|
305
|
-
return str
|
|
306
|
-
.split(' ')
|
|
307
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
308
|
-
.join(' ');
|
|
309
|
-
}
|
|
310
|
-
|
|
311
213
|
// Helper: Add contact to SendGrid
|
|
312
214
|
async function addToSendGrid({ email, firstName, lastName, source, appId, brandName }) {
|
|
313
215
|
try {
|
|
@@ -19,11 +19,10 @@ module.exports = {
|
|
|
19
19
|
* @param {boolean} options.trial - Whether to include a trial period (subscriptions only)
|
|
20
20
|
* @param {string} options.confirmationUrl - Success redirect URL
|
|
21
21
|
* @param {string} options.cancelUrl - Cancel redirect URL
|
|
22
|
-
* @param {object} options.Manager - Manager instance
|
|
23
22
|
* @param {object} options.assistant - Assistant instance
|
|
24
23
|
* @returns {object} { id, url, raw }
|
|
25
24
|
*/
|
|
26
|
-
async createIntent({ uid, orderId, product, productId, frequency, trial, confirmationUrl,
|
|
25
|
+
async createIntent({ uid, orderId, product, productId, frequency, trial, confirmationUrl, assistant }) {
|
|
27
26
|
// Guard: test processor is not available in production
|
|
28
27
|
if (assistant.isProduction()) {
|
|
29
28
|
throw new Error('Test processor is not available in production');
|
|
@@ -32,10 +31,10 @@ module.exports = {
|
|
|
32
31
|
const productType = product.type || 'subscription';
|
|
33
32
|
|
|
34
33
|
if (productType === 'subscription') {
|
|
35
|
-
return createSubscriptionIntent({ uid, orderId, product, frequency, trial, confirmationUrl,
|
|
34
|
+
return createSubscriptionIntent({ uid, orderId, product, frequency, trial, confirmationUrl, assistant });
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
return createOneTimeIntent({ uid, orderId, product, productId, confirmationUrl,
|
|
37
|
+
return createOneTimeIntent({ uid, orderId, product, productId, confirmationUrl, assistant });
|
|
39
38
|
},
|
|
40
39
|
};
|
|
41
40
|
|
|
@@ -43,7 +42,7 @@ module.exports = {
|
|
|
43
42
|
* Create a test subscription intent
|
|
44
43
|
* Generates Stripe-shaped subscription + customer.subscription.created event
|
|
45
44
|
*/
|
|
46
|
-
async function createSubscriptionIntent({ uid, orderId, product, frequency, trial, confirmationUrl,
|
|
45
|
+
async function createSubscriptionIntent({ uid, orderId, product, frequency, trial, confirmationUrl, assistant }) {
|
|
47
46
|
// Get the price ID for the requested frequency
|
|
48
47
|
const priceId = resolvePriceId(product, 'subscription', frequency);
|
|
49
48
|
|
|
@@ -96,7 +95,7 @@ async function createSubscriptionIntent({ uid, orderId, product, frequency, tria
|
|
|
96
95
|
assistant.log(`Test subscription intent: sessionId=${sessionId}, subscriptionId=${subscriptionId}, eventId=${eventId}, trial=${!!subscription.trial_start}`);
|
|
97
96
|
|
|
98
97
|
// Auto-fire webhook
|
|
99
|
-
fireWebhook({ event,
|
|
98
|
+
fireWebhook({ event, assistant });
|
|
100
99
|
|
|
101
100
|
return {
|
|
102
101
|
id: sessionId,
|
|
@@ -109,7 +108,7 @@ async function createSubscriptionIntent({ uid, orderId, product, frequency, tria
|
|
|
109
108
|
* Create a test one-time payment intent
|
|
110
109
|
* Generates Stripe-shaped checkout session + checkout.session.completed event
|
|
111
110
|
*/
|
|
112
|
-
async function createOneTimeIntent({ uid, orderId, product, productId, confirmationUrl,
|
|
111
|
+
async function createOneTimeIntent({ uid, orderId, product, productId, confirmationUrl, assistant }) {
|
|
113
112
|
// Validate that a price exists (resolvePriceId throws if not found)
|
|
114
113
|
resolvePriceId(product, 'one-time', null);
|
|
115
114
|
|
|
@@ -140,7 +139,7 @@ async function createOneTimeIntent({ uid, orderId, product, productId, confirmat
|
|
|
140
139
|
assistant.log(`Test one-time intent: sessionId=${sessionId}, eventId=${eventId}, productId=${productId}`);
|
|
141
140
|
|
|
142
141
|
// Auto-fire webhook
|
|
143
|
-
fireWebhook({ event,
|
|
142
|
+
fireWebhook({ event, assistant });
|
|
144
143
|
|
|
145
144
|
return {
|
|
146
145
|
id: sessionId,
|
|
@@ -152,8 +151,8 @@ async function createOneTimeIntent({ uid, orderId, product, productId, confirmat
|
|
|
152
151
|
/**
|
|
153
152
|
* Fire-and-forget webhook to trigger the full pipeline
|
|
154
153
|
*/
|
|
155
|
-
function fireWebhook({ event,
|
|
156
|
-
const webhookUrl = `${Manager.project.apiUrl}/backend-manager/payments/webhook?processor=test&key=${process.env.BACKEND_MANAGER_KEY}`;
|
|
154
|
+
function fireWebhook({ event, assistant }) {
|
|
155
|
+
const webhookUrl = `${assistant.Manager.project.apiUrl}/backend-manager/payments/webhook?processor=test&key=${process.env.BACKEND_MANAGER_KEY}`;
|
|
157
156
|
fetch(webhookUrl, {
|
|
158
157
|
method: 'POST',
|
|
159
158
|
response: 'json',
|
|
@@ -14,8 +14,9 @@ const STATE_KEY = process.env.BACKEND_MANAGER_KEY
|
|
|
14
14
|
* Build context object with common OAuth2 data
|
|
15
15
|
* Used by GET, POST, DELETE handlers
|
|
16
16
|
*/
|
|
17
|
-
async function buildContext({ assistant,
|
|
18
|
-
const
|
|
17
|
+
async function buildContext({ assistant, user, settings, requireProvider = true }) {
|
|
18
|
+
const Manager = assistant.Manager;
|
|
19
|
+
const { admin } = Manager.libraries;
|
|
19
20
|
|
|
20
21
|
// Require authentication
|
|
21
22
|
if (!user.authenticated) {
|
|
@@ -8,14 +8,14 @@ const {
|
|
|
8
8
|
*
|
|
9
9
|
* Revokes tokens with the provider (best effort) and removes the connection.
|
|
10
10
|
*/
|
|
11
|
-
module.exports = async ({ assistant,
|
|
12
|
-
const context = await buildContext({ assistant,
|
|
11
|
+
module.exports = async ({ assistant, user, settings }) => {
|
|
12
|
+
const context = await buildContext({ assistant, user, settings });
|
|
13
13
|
|
|
14
14
|
if (context.error) {
|
|
15
15
|
return assistant.respond(context.error.message, { code: context.error.code });
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { admin, oauth2Provider, targetUid, targetUser, clientId, clientSecret } = context;
|
|
18
|
+
const { Manager, admin, oauth2Provider, targetUid, targetUser, clientId, clientSecret } = context;
|
|
19
19
|
|
|
20
20
|
assistant.log('OAuth2 DELETE request', { provider: settings.provider });
|
|
21
21
|
|
|
@@ -12,8 +12,8 @@ const {
|
|
|
12
12
|
* - authorize (default): Get authorization URL
|
|
13
13
|
* - status: Check connection status
|
|
14
14
|
*/
|
|
15
|
-
module.exports = async ({ assistant,
|
|
16
|
-
const context = await buildContext({ assistant,
|
|
15
|
+
module.exports = async ({ assistant, user, settings }) => {
|
|
16
|
+
const context = await buildContext({ assistant, user, settings });
|
|
17
17
|
|
|
18
18
|
if (context.error) {
|
|
19
19
|
return assistant.respond(context.error.message, { code: context.error.code });
|
|
@@ -14,18 +14,16 @@ const {
|
|
|
14
14
|
* - tokenize (default): Exchange authorization code for tokens
|
|
15
15
|
* - refresh: Refresh access token
|
|
16
16
|
*/
|
|
17
|
-
module.exports = async ({ assistant,
|
|
18
|
-
const { admin } = libraries;
|
|
19
|
-
|
|
17
|
+
module.exports = async ({ assistant, user, settings }) => {
|
|
20
18
|
assistant.log('OAuth2 POST request', { action: settings.action });
|
|
21
19
|
|
|
22
20
|
switch (settings.action) {
|
|
23
21
|
case 'refresh':
|
|
24
|
-
return processRefresh({ assistant,
|
|
22
|
+
return processRefresh({ assistant, user, settings });
|
|
25
23
|
|
|
26
24
|
case 'tokenize':
|
|
27
25
|
default:
|
|
28
|
-
return processTokenize({ assistant,
|
|
26
|
+
return processTokenize({ assistant, settings });
|
|
29
27
|
}
|
|
30
28
|
};
|
|
31
29
|
|
|
@@ -33,7 +31,9 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
|
|
|
33
31
|
// Handlers
|
|
34
32
|
// ============================================================================
|
|
35
33
|
|
|
36
|
-
async function processTokenize({ assistant,
|
|
34
|
+
async function processTokenize({ assistant, settings }) {
|
|
35
|
+
const Manager = assistant.Manager;
|
|
36
|
+
const { admin } = Manager.libraries;
|
|
37
37
|
assistant.log('processTokenize settings', {
|
|
38
38
|
hasCode: !!settings.code,
|
|
39
39
|
codeType: typeof settings.code,
|
|
@@ -167,14 +167,14 @@ async function processTokenize({ assistant, Manager, admin, settings }) {
|
|
|
167
167
|
return assistant.respond({ success: true });
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
async function processRefresh({ assistant,
|
|
171
|
-
const context = await buildContext({ assistant,
|
|
170
|
+
async function processRefresh({ assistant, user, settings }) {
|
|
171
|
+
const context = await buildContext({ assistant, user, settings });
|
|
172
172
|
|
|
173
173
|
if (context.error) {
|
|
174
174
|
return assistant.respond(context.error.message, { code: context.error.code });
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
const { admin, oauth2Provider, targetUid, targetUser, clientId, clientSecret } = context;
|
|
177
|
+
const { Manager, admin, oauth2Provider, targetUid, targetUser, clientId, clientSecret } = context;
|
|
178
178
|
|
|
179
179
|
const refreshToken = targetUser?.oauth2?.[settings.provider]?.token?.refresh_token;
|
|
180
180
|
|
|
@@ -26,10 +26,10 @@ module.exports = async ({ assistant, user, settings, libraries }) => {
|
|
|
26
26
|
let count = 0;
|
|
27
27
|
|
|
28
28
|
// Sign out of main session
|
|
29
|
-
count += await signOutOfSession(
|
|
29
|
+
count += await signOutOfSession(assistant, uid, sessionPath);
|
|
30
30
|
|
|
31
31
|
// Legacy for somiibo and old electron-manager
|
|
32
|
-
count += await signOutOfSession(
|
|
32
|
+
count += await signOutOfSession(assistant, uid, 'gatherings/online');
|
|
33
33
|
|
|
34
34
|
// Revoke Firebase refresh tokens
|
|
35
35
|
try {
|
|
@@ -47,7 +47,8 @@ module.exports = async ({ assistant, user, settings, libraries }) => {
|
|
|
47
47
|
/**
|
|
48
48
|
* Sign out of a specific session path
|
|
49
49
|
*/
|
|
50
|
-
async function signOutOfSession(
|
|
50
|
+
async function signOutOfSession(assistant, uid, sessionPath) {
|
|
51
|
+
const { admin } = assistant.Manager.libraries;
|
|
51
52
|
let count = 0;
|
|
52
53
|
|
|
53
54
|
const snapshot = await admin.database().ref(sessionPath)
|