backend-manager 5.0.189 → 5.0.191
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/CLAUDE.md +113 -18
- package/TODO-2.md +3 -0
- package/package.json +1 -1
- package/src/manager/events/auth/before-create.js +4 -0
- package/src/manager/events/auth/before-signin.js +5 -0
- package/src/manager/events/auth/on-create.js +6 -1
- package/src/manager/events/auth/on-delete.js +6 -1
- package/src/manager/events/auth/utils.js +46 -1
- package/src/manager/functions/core/actions/api/admin/cron.js +1 -1
- package/src/manager/index.js +1 -1
- package/src/manager/libraries/abandoned-cart-config.js +1 -1
- package/src/manager/routes/admin/cron/post.js +1 -1
- /package/src/manager/{cron → events/cron}/daily/data-requests.js +0 -0
- /package/src/manager/{cron → events/cron}/daily/expire-paypal-cancellations.js +0 -0
- /package/src/manager/{cron → events/cron}/daily/ghostii-auto-publisher.js +0 -0
- /package/src/manager/{cron → events/cron}/daily/marketing-newsletter-generate.js +0 -0
- /package/src/manager/{cron → events/cron}/daily/marketing-prune.js +0 -0
- /package/src/manager/{cron → events/cron}/daily/reset-usage.js +0 -0
- /package/src/manager/{cron → events/cron}/daily.js +0 -0
- /package/src/manager/{cron → events/cron}/frequent/abandoned-carts.js +0 -0
- /package/src/manager/{cron → events/cron}/frequent/email-queue.js +0 -0
- /package/src/manager/{cron → events/cron}/frequent/marketing-campaigns.js +0 -0
- /package/src/manager/{cron → events/cron}/frequent.js +0 -0
- /package/src/manager/{cron → events/cron}/runner.js +0 -0
package/CLAUDE.md
CHANGED
|
@@ -65,24 +65,32 @@ src/
|
|
|
65
65
|
stripe.js # Stripe SDK init, fetchResource, toUnified*, resolvePriceId
|
|
66
66
|
paypal.js # PayPal fetchResource, toUnified* (custom_id parsing)
|
|
67
67
|
test.js # Test processor (delegates to Stripe shapes)
|
|
68
|
-
|
|
68
|
+
events/ # All event-driven code
|
|
69
|
+
auth/ # Auth event handlers (hookable)
|
|
70
|
+
before-create.js # Disposable email blocking + IP rate limiting
|
|
71
|
+
before-signin.js # Activity update + sign-in analytics
|
|
72
|
+
on-create.js # User doc creation
|
|
73
|
+
on-delete.js # User doc deletion + marketing cleanup
|
|
74
|
+
utils.js # Shared utilities (retryWrite, runAuthHook)
|
|
75
|
+
cron/ # Cron job runners
|
|
76
|
+
runner.js # Shared cron job runner (BEM + consumer hooks)
|
|
77
|
+
daily.js # Daily cron entry point
|
|
78
|
+
daily/{job}.js # Individual daily cron jobs
|
|
79
|
+
frequent.js # Frequent cron entry point
|
|
80
|
+
frequent/{job}.js # Individual frequent cron jobs
|
|
81
|
+
firestore/ # Firestore triggers
|
|
82
|
+
payments-webhooks/ # Webhook processing pipeline
|
|
83
|
+
on-write.js # Orchestrator: fetch→transform→transition→write
|
|
84
|
+
analytics.js # Payment analytics tracking (GA4, Meta, TikTok)
|
|
85
|
+
transitions/ # State transition detection + handlers
|
|
86
|
+
index.js # Transition detection logic
|
|
87
|
+
send-email.js # Shared email helper for handlers
|
|
88
|
+
subscription/ # Subscription transition handlers
|
|
89
|
+
one-time/ # One-time payment transition handlers
|
|
90
|
+
functions/core/ # Built-in functions
|
|
69
91
|
actions/
|
|
70
|
-
api.js
|
|
71
|
-
api/{category}/{action}.js
|
|
72
|
-
events/
|
|
73
|
-
auth/ # Auth event handlers
|
|
74
|
-
firestore/ # Firestore triggers
|
|
75
|
-
payments-webhooks/ # Webhook processing pipeline
|
|
76
|
-
on-write.js # Orchestrator: fetch→transform→transition→write
|
|
77
|
-
analytics.js # Payment analytics tracking (GA4, Meta, TikTok)
|
|
78
|
-
transitions/ # State transition detection + handlers
|
|
79
|
-
index.js # Transition detection logic
|
|
80
|
-
send-email.js # Shared email helper for handlers
|
|
81
|
-
subscription/ # Subscription transition handlers
|
|
82
|
-
one-time/ # One-time payment transition handlers
|
|
83
|
-
cron/
|
|
84
|
-
daily.js # Daily cron runner
|
|
85
|
-
daily/{job}.js # Individual cron jobs
|
|
92
|
+
api.js # Main bm_api handler
|
|
93
|
+
api/{category}/{action}.js # API command handlers
|
|
86
94
|
routes/ # Built-in routes
|
|
87
95
|
admin/
|
|
88
96
|
post/ # POST /admin/post - Create blog posts via GitHub
|
|
@@ -142,6 +150,11 @@ functions/
|
|
|
142
150
|
{endpoint}/
|
|
143
151
|
index.js # Schema definition
|
|
144
152
|
hooks/
|
|
153
|
+
auth/
|
|
154
|
+
before-create.js # Custom pre-signup checks (can block)
|
|
155
|
+
before-signin.js # Custom pre-signin checks (can block)
|
|
156
|
+
on-create.js # Post-signup side effects (non-blocking)
|
|
157
|
+
on-delete.js # Post-deletion side effects (non-blocking)
|
|
145
158
|
cron/
|
|
146
159
|
daily/
|
|
147
160
|
{job}.js # Custom daily jobs
|
|
@@ -416,6 +429,80 @@ Job.prototype.main = function () {
|
|
|
416
429
|
module.exports = Job;
|
|
417
430
|
```
|
|
418
431
|
|
|
432
|
+
### Auth Hooks (Consumer Project)
|
|
433
|
+
|
|
434
|
+
Auth hooks let consumer projects inject custom logic into BEM's auth event lifecycle. BEM runs its core handler first, then looks for a matching hook at `hooks/auth/{event-name}.js`.
|
|
435
|
+
|
|
436
|
+
| Hook | File | Behavior |
|
|
437
|
+
|------|------|----------|
|
|
438
|
+
| `before-create` | `hooks/auth/before-create.js` | Runs after BEM's disposable email + rate limit checks. **Can throw `HttpsError` to block signup.** |
|
|
439
|
+
| `before-signin` | `hooks/auth/before-signin.js` | Runs after BEM's activity update. **Can throw `HttpsError` to block sign-in.** |
|
|
440
|
+
| `on-create` | `hooks/auth/on-create.js` | Runs after BEM creates the user doc. **Non-blocking** — errors are caught and logged. |
|
|
441
|
+
| `on-delete` | `hooks/auth/on-delete.js` | Runs after BEM deletes the user doc. **Non-blocking** — errors are caught and logged. |
|
|
442
|
+
|
|
443
|
+
Hook signature (same as BEM's internal handlers):
|
|
444
|
+
```javascript
|
|
445
|
+
module.exports = async ({ Manager, assistant, user, context, libraries }) => {
|
|
446
|
+
// user: AuthUserRecord (uid, email, providerData, etc.)
|
|
447
|
+
// context: AuthEventContext for blocking functions (ipAddress, userAgent, additionalUserInfo)
|
|
448
|
+
// EventContext for triggers (eventId, eventType, timestamp — no IP/userAgent)
|
|
449
|
+
// libraries: { admin, functions, ... }
|
|
450
|
+
};
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### Blocking hook example (before-create)
|
|
454
|
+
|
|
455
|
+
```javascript
|
|
456
|
+
// hooks/auth/before-create.js — Only allow Google OAuth signups
|
|
457
|
+
const ENFORCE = true;
|
|
458
|
+
|
|
459
|
+
const ALLOWED_PROVIDERS = ['google.com'];
|
|
460
|
+
|
|
461
|
+
module.exports = async ({ assistant, user, context, libraries }) => {
|
|
462
|
+
if (!ENFORCE) { return; }
|
|
463
|
+
|
|
464
|
+
const { functions } = libraries;
|
|
465
|
+
const provider = context.additionalUserInfo?.providerId;
|
|
466
|
+
|
|
467
|
+
if (!ALLOWED_PROVIDERS.includes(provider)) {
|
|
468
|
+
assistant.error(`hook/before-create: Blocked provider '${provider}' for ${user.email}`);
|
|
469
|
+
throw new functions.auth.HttpsError('permission-denied', 'Please sign up with Google.');
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
#### Non-blocking hook example (on-create)
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
// hooks/auth/on-create.js — Auto-delete spam referrals
|
|
478
|
+
const powertools = require('node-powertools');
|
|
479
|
+
|
|
480
|
+
const ENFORCE = true;
|
|
481
|
+
const BLOCKED_AFFILIATE_CODES = ['iLvQjmvm'];
|
|
482
|
+
|
|
483
|
+
module.exports = async ({ Manager, assistant, user, context, libraries }) => {
|
|
484
|
+
if (!ENFORCE) { return; }
|
|
485
|
+
|
|
486
|
+
const { admin } = libraries;
|
|
487
|
+
const uid = user.uid;
|
|
488
|
+
|
|
489
|
+
// Poll until signup route attaches attribution.affiliate.code
|
|
490
|
+
let referredBy = null;
|
|
491
|
+
|
|
492
|
+
await powertools.poll(async () => {
|
|
493
|
+
const userDoc = await admin.firestore().doc(`users/${uid}`).get().catch(() => null);
|
|
494
|
+
if (!userDoc?.exists) { return true; }
|
|
495
|
+
referredBy = userDoc.data()?.attribution?.affiliate?.code;
|
|
496
|
+
return !!referredBy;
|
|
497
|
+
}, { interval: 10000, timeout: 60000 }).catch(() => {});
|
|
498
|
+
|
|
499
|
+
if (!referredBy || !BLOCKED_AFFILIATE_CODES.includes(referredBy)) { return; }
|
|
500
|
+
|
|
501
|
+
// Delete spam account (triggers on-delete for cleanup)
|
|
502
|
+
await admin.auth().deleteUser(uid).catch(e => assistant.error('Delete failed:', e));
|
|
503
|
+
};
|
|
504
|
+
```
|
|
505
|
+
|
|
419
506
|
## Common Operations
|
|
420
507
|
|
|
421
508
|
### Authenticate User
|
|
@@ -482,7 +569,9 @@ Manager.handlers.bm_api = function (mod, position) {
|
|
|
482
569
|
| Schemas | `schemas/{name}/` | `index.js` or `{method}.js` |
|
|
483
570
|
| API Commands | `actions/api/{category}/` | `{action}.js` |
|
|
484
571
|
| Auth Events | `events/auth/` | `{event}.js` |
|
|
485
|
-
|
|
|
572
|
+
| Auth Hooks (consumer) | `hooks/auth/` | `{event}.js` |
|
|
573
|
+
| Cron Jobs (BEM) | `events/cron/daily/` | `{job}.js` |
|
|
574
|
+
| Cron Jobs (consumer) | `hooks/cron/daily/` | `{job}.js` |
|
|
486
575
|
|
|
487
576
|
## Admin Post Route
|
|
488
577
|
|
|
@@ -1302,6 +1391,12 @@ marketing: {
|
|
|
1302
1391
|
| Rate limiting | `src/manager/helpers/usage.js` |
|
|
1303
1392
|
| User properties + schema | `src/manager/helpers/user.js` |
|
|
1304
1393
|
| Batch utilities | `src/manager/helpers/utilities.js` |
|
|
1394
|
+
| Auth: before-create | `src/manager/events/auth/before-create.js` |
|
|
1395
|
+
| Auth: before-signin | `src/manager/events/auth/before-signin.js` |
|
|
1396
|
+
| Auth: on-create | `src/manager/events/auth/on-create.js` |
|
|
1397
|
+
| Auth: on-delete | `src/manager/events/auth/on-delete.js` |
|
|
1398
|
+
| Auth: shared utilities | `src/manager/events/auth/utils.js` |
|
|
1399
|
+
| Cron runner | `src/manager/events/cron/runner.js` |
|
|
1305
1400
|
| Main API handler | `src/manager/functions/core/actions/api.js` |
|
|
1306
1401
|
| Config template | `templates/backend-manager-config.json` |
|
|
1307
1402
|
| CLI entry | `src/cli/index.js` |
|
package/TODO-2.md
CHANGED
|
@@ -23,6 +23,9 @@ waht about when they request a cancel
|
|
|
23
23
|
Read cancellation-requested.js
|
|
24
24
|
The category is order/cancellation-requested (line 13).
|
|
25
25
|
|
|
26
|
+
---
|
|
27
|
+
MIRROR settigns in BEM JSON so that usage reset can properly get MIRRED DOCS liek slapform forms or chatsy agents DOCS
|
|
28
|
+
|
|
26
29
|
---
|
|
27
30
|
GHOSTII REVAMP
|
|
28
31
|
* better logic for generating posts. better model? claude?
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { isDisposable } = require('../../libraries/email/validation.js');
|
|
2
|
+
const { runAuthHook } = require('./utils.js');
|
|
2
3
|
|
|
3
4
|
const ERROR_TOO_MANY_ATTEMPTS = 'Unable to create account at this time. Please try again later.';
|
|
4
5
|
const ERROR_DISPOSABLE_EMAIL = 'This email domain is not allowed. Please use a different email address.';
|
|
@@ -71,5 +72,8 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
|
|
|
71
72
|
usage.increment('signups');
|
|
72
73
|
await usage.update();
|
|
73
74
|
|
|
75
|
+
// Run consumer hook (can throw HttpsError to block signup)
|
|
76
|
+
await runAuthHook('before-create', { Manager, assistant, user, context, libraries });
|
|
77
|
+
|
|
74
78
|
assistant.log(`beforeCreate: Completed for ${user.uid} (${Date.now() - startTime}ms)`);
|
|
75
79
|
};
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
* Note: recaptchaScore requires reCAPTCHA Enterprise (Google Cloud level), NOT the Firebase SMS fraud toggle.
|
|
25
25
|
* Note: credential tokens (idToken, accessToken, refreshToken) require opt-in via BlockingOptions.
|
|
26
26
|
*/
|
|
27
|
+
const { runAuthHook } = require('./utils.js');
|
|
28
|
+
|
|
27
29
|
module.exports = async ({ Manager, assistant, user, context, libraries }) => {
|
|
28
30
|
const startTime = Date.now();
|
|
29
31
|
const { admin } = libraries;
|
|
@@ -60,5 +62,8 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
|
|
|
60
62
|
assistant.log(`beforeSignIn: Updated user activity`);
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
// Run consumer hook (can throw HttpsError to block sign-in)
|
|
66
|
+
await runAuthHook('before-signin', { Manager, assistant, user, context, libraries });
|
|
67
|
+
|
|
63
68
|
assistant.log(`beforeSignIn: Completed for ${user.uid} (${Date.now() - startTime}ms)`);
|
|
64
69
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { retryWrite, MAX_RETRIES } = require('./utils.js');
|
|
1
|
+
const { retryWrite, runAuthHook, MAX_RETRIES } = require('./utils.js');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* onCreate - Create user doc
|
|
@@ -87,6 +87,11 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
|
|
|
87
87
|
// Don't reject - the user was already created in Auth
|
|
88
88
|
// The user/signup endpoint will handle creating the doc if it's missing
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
// Run consumer hook (non-blocking — errors logged but don't fail)
|
|
92
|
+
await runAuthHook('on-create', { Manager, assistant, user, context, libraries }).catch(e => {
|
|
93
|
+
assistant.error('onCreate: Consumer hook error:', e);
|
|
94
|
+
});
|
|
90
95
|
};
|
|
91
96
|
|
|
92
97
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { retryWrite, MAX_RETRIES } = require('./utils.js');
|
|
1
|
+
const { retryWrite, runAuthHook, MAX_RETRIES } = require('./utils.js');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* onDelete - Delete user doc
|
|
@@ -70,5 +70,10 @@ module.exports = async ({ Manager, assistant, user, context, libraries }) => {
|
|
|
70
70
|
uuid: user.uid,
|
|
71
71
|
}).event('user_delete', {});
|
|
72
72
|
|
|
73
|
+
// Run consumer hook (non-blocking — errors logged but don't fail)
|
|
74
|
+
await runAuthHook('on-delete', { Manager, assistant, user, context, libraries }).catch(e => {
|
|
75
|
+
assistant.error('onDelete: Consumer hook error:', e);
|
|
76
|
+
});
|
|
77
|
+
|
|
73
78
|
assistant.log(`onDelete: Completed for ${user.uid} (${Date.now() - startTime}ms)`);
|
|
74
79
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const jetpack = require('fs-jetpack');
|
|
2
|
+
|
|
1
3
|
const MAX_RETRIES = 3;
|
|
2
4
|
const RETRY_DELAY_MS = 1000;
|
|
3
5
|
|
|
@@ -26,4 +28,47 @@ async function retryWrite(assistant, tag, fn) {
|
|
|
26
28
|
throw lastError; // All retries failed
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Run consumer auth hooks from hooks/auth/{eventName}.js
|
|
33
|
+
*
|
|
34
|
+
* Similar to how cron discovers hooks at hooks/cron/{schedule}/*.js,
|
|
35
|
+
* auth hooks are discovered at hooks/auth/{eventName}.js in the consumer project.
|
|
36
|
+
*
|
|
37
|
+
* For blocking functions (before-create, before-signin):
|
|
38
|
+
* - Hook can throw HttpsError to block the operation
|
|
39
|
+
* - Hook runs AFTER BEM's core checks (disposable email, rate limiting, etc.)
|
|
40
|
+
*
|
|
41
|
+
* For trigger functions (on-create, on-delete):
|
|
42
|
+
* - Hook errors are logged but don't block the operation
|
|
43
|
+
*
|
|
44
|
+
* Hook signature:
|
|
45
|
+
* module.exports = async ({ Manager, assistant, user, context, libraries }) => { ... }
|
|
46
|
+
*
|
|
47
|
+
* Consumer project structure:
|
|
48
|
+
* functions/
|
|
49
|
+
* hooks/
|
|
50
|
+
* auth/
|
|
51
|
+
* before-create.js — runs after BEM checks, can block signup
|
|
52
|
+
* before-signin.js — runs after BEM signin logic, can block signin
|
|
53
|
+
* on-create.js — runs after BEM creates user doc
|
|
54
|
+
* on-delete.js — runs after BEM deletes user doc
|
|
55
|
+
*/
|
|
56
|
+
async function runAuthHook(eventName, args) {
|
|
57
|
+
const { Manager, assistant } = args;
|
|
58
|
+
const hookPath = `${Manager.cwd}/hooks/auth/${eventName}.js`;
|
|
59
|
+
|
|
60
|
+
// Check if hook file exists
|
|
61
|
+
if (!jetpack.exists(hookPath)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
assistant.log(`${eventName}: Running consumer hook @ ${hookPath}`);
|
|
66
|
+
|
|
67
|
+
// Load and execute — passes the same args object the BEM handler received
|
|
68
|
+
const hook = require(hookPath);
|
|
69
|
+
await hook(args);
|
|
70
|
+
|
|
71
|
+
assistant.log(`${eventName}: Consumer hook completed`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { retryWrite, runAuthHook, MAX_RETRIES };
|
|
@@ -22,7 +22,7 @@ Module.prototype.main = function () {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// Run the cron job
|
|
25
|
-
Manager._process((new (require(
|
|
25
|
+
Manager._process((new (require(`${Manager.rootDirectory}/events/cron/${payload.data.payload.id}.js`))()).init(Manager, { context: {}, }))
|
|
26
26
|
.then((res) => {
|
|
27
27
|
return resolve({data: res});
|
|
28
28
|
})
|
package/src/manager/index.js
CHANGED
|
@@ -14,8 +14,8 @@ const util = require('util');
|
|
|
14
14
|
const core = './functions/core';
|
|
15
15
|
const wrappers = './functions/wrappers';
|
|
16
16
|
const _legacy = './functions/_legacy';
|
|
17
|
-
const cron = path.resolve(__dirname, './cron');
|
|
18
17
|
const events = path.resolve(__dirname, './events');
|
|
18
|
+
const cron = path.resolve(events, './cron');
|
|
19
19
|
|
|
20
20
|
const BEM_CONFIG_TEMPLATE_PATH = path.resolve(__dirname, '../../templates/backend-manager-config.json');
|
|
21
21
|
const BEM_PACKAGE = require('../../package.json');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Abandoned cart reminder configuration (SSOT)
|
|
3
3
|
*
|
|
4
4
|
* Used by:
|
|
5
|
-
* - cron/frequent/abandoned-carts.js (processing reminders)
|
|
5
|
+
* - events/cron/frequent/abandoned-carts.js (processing reminders)
|
|
6
6
|
* - Client-side checkout page (creating cart doc with first delay)
|
|
7
7
|
*/
|
|
8
8
|
module.exports = {
|
|
@@ -22,7 +22,7 @@ module.exports = async ({ assistant, Manager, user, settings, analytics }) => {
|
|
|
22
22
|
assistant.log('Running cron job:', settings.id);
|
|
23
23
|
|
|
24
24
|
// Run the cron job
|
|
25
|
-
const cronPath =
|
|
25
|
+
const cronPath = `${Manager.rootDirectory}/events/cron/${settings.id}.js`;
|
|
26
26
|
const cronHandler = require(cronPath);
|
|
27
27
|
const result = await cronHandler({ Manager, assistant, context: {}, libraries: Manager.libraries }).catch(e => e);
|
|
28
28
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|