backend-manager 5.0.75 → 5.0.77

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "5.0.75",
3
+ "version": "5.0.77",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -74,7 +74,6 @@
74
74
  "npm-api": "^1.0.1",
75
75
  "paypal-server-api": "^2.0.14",
76
76
  "pushid": "^1.0.0",
77
- "resolve-account": "^1.0.26",
78
77
  "shortid": "^2.2.17",
79
78
  "uid-generator": "^2.0.0",
80
79
  "uuid": "^9.0.1",
@@ -16,7 +16,6 @@ const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
16
16
 
17
17
  // State
18
18
  let postId;
19
- let appObject;
20
19
 
21
20
  /**
22
21
  * Ghostii Auto Publisher cron job
@@ -30,11 +29,8 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
30
29
  // Set post ID
31
30
  postId = moment().unix();
32
31
 
33
- // Get app content
34
- appObject = await getAppData(Manager.config.app.id).catch((e) => e);
35
- if (appObject instanceof Error) {
36
- throw appObject;
37
- }
32
+ // Build app object from local config
33
+ const appObject = buildAppObject(Manager.config);
38
34
 
39
35
  // Log
40
36
  assistant.log('App object', appObject);
@@ -44,8 +40,6 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
44
40
 
45
41
  // Loop through each item
46
42
  for (const settings of settingsArray) {
47
- const appId = settings.app || appObject.id;
48
-
49
43
  // Fix settings
50
44
  settings.articles = settings.articles || 0;
51
45
  settings.sources = randomize(settings.sources || []);
@@ -53,16 +47,23 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
53
47
  settings.prompt = settings.prompt || '';
54
48
  settings.chance = settings.chance || 1.0;
55
49
  settings.author = settings.author || undefined;
56
- settings.app = await getAppData(appId).catch((e) => e);
57
50
 
58
- // Check for errors
59
- if (settings.app instanceof Error) {
60
- assistant.error('Error fetching app data', settings.app);
61
- continue;
51
+ // Resolve app data for this ghostii item
52
+ if (settings.app && settings.appUrl) {
53
+ // Cross-app: fetch from the other project's /app endpoint
54
+ settings.app = await fetchRemoteApp(settings.appUrl).catch((e) => e);
55
+
56
+ if (settings.app instanceof Error) {
57
+ assistant.error('Error fetching remote app data', settings.app);
58
+ continue;
59
+ }
60
+ } else {
61
+ // Same-app: use local config
62
+ settings.app = appObject;
62
63
  }
63
64
 
64
65
  // Log
65
- assistant.log(`Settings (app=${appId})`, settings);
66
+ assistant.log(`Settings (app=${settings.app.id})`, settings);
66
67
 
67
68
  // Quit if articles are disabled
68
69
  if (!settings.articles || !settings.sources.length) {
@@ -88,6 +89,35 @@ module.exports = async ({ Manager, assistant, context, libraries }) => {
88
89
  }
89
90
  };
90
91
 
92
+ /**
93
+ * Build app object from Manager.config (same shape as getApp response)
94
+ */
95
+ function buildAppObject(config) {
96
+ return {
97
+ id: config.app?.id,
98
+ name: config.brand?.name,
99
+ brand: {
100
+ description: config.brand?.description || '',
101
+ },
102
+ url: config.brand?.url,
103
+ github: {
104
+ user: config.github?.user,
105
+ repo: (config.github?.repo_website || '').split('/').pop(),
106
+ },
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Fetch app data from a remote BEM project's /app endpoint
112
+ */
113
+ function fetchRemoteApp(appUrl) {
114
+ return fetch(`${appUrl}/backend-manager/app`, {
115
+ timeout: 30000,
116
+ tries: 3,
117
+ response: 'json',
118
+ });
119
+ }
120
+
91
121
  async function harvest(assistant, settings) {
92
122
  const date = moment().format('MMMM YYYY');
93
123
 
@@ -151,18 +181,6 @@ async function harvest(assistant, settings) {
151
181
  }
152
182
  }
153
183
 
154
- function getAppData(id) {
155
- return fetch('https://us-central1-itw-creative-works.cloudfunctions.net/getApp', {
156
- method: 'post',
157
- timeout: 30000,
158
- tries: 3,
159
- response: 'json',
160
- body: {
161
- id: id,
162
- },
163
- });
164
- }
165
-
166
184
  function getURLContent(url) {
167
185
  return fetch(url, {
168
186
  timeout: 30000,
@@ -1,4 +1,3 @@
1
- const fetch = require('wonderful-fetch');
2
1
  const { merge } = require('lodash');
3
2
 
4
3
  function Module() {
@@ -8,9 +7,7 @@ function Module() {
8
7
  Module.prototype.main = function () {
9
8
  const self = this;
10
9
  const Manager = self.Manager;
11
- const Api = self.Api;
12
10
  const assistant = self.assistant;
13
- const payload = self.payload;
14
11
 
15
12
  return new Promise(async function(resolve, reject) {
16
13
  const defaultProviders = {
@@ -40,14 +37,8 @@ Module.prototype.main = function () {
40
37
  },
41
38
  }
42
39
 
43
- // Get app
44
- const appObject = await self.getAppObject();
45
- if (appObject instanceof Error) {
46
- return reject(assistant.errorify(`Failed to get app object: ${appObject}`, {code: 500}));
47
- }
48
-
49
- // Merge the default providers with the app providers
50
- const providers = merge(defaultProviders, appObject.authentication);
40
+ // Merge the default providers with the config providers
41
+ const providers = merge(defaultProviders, Manager.config.authentication || {});
51
42
 
52
43
  // Reformat the object so it's just provider=true/false
53
44
  const finalProviders = {};
@@ -64,39 +55,4 @@ Module.prototype.main = function () {
64
55
 
65
56
  };
66
57
 
67
- // Get app object
68
- Module.prototype.getAppObject = function () {
69
- const self = this;
70
-
71
- const Manager = self.Manager;
72
- const Api = self.Api;
73
- const assistant = self.assistant;
74
- const payload = self.payload;
75
-
76
- return new Promise(async function(resolve, reject) {
77
- const id = Manager.config.app.id;
78
-
79
- // Get the app settings
80
- fetch(`https://us-central1-itw-creative-works.cloudfunctions.net/getApp`, {
81
- method: 'post',
82
- response: 'json',
83
- body: {
84
- id: id,
85
- }
86
- })
87
- .then((r) => {
88
- assistant.log('getAppObject(): Response', r);
89
-
90
- // If data is missing, return an error
91
- if (!r) {
92
- throw new Error(`App with id ${id} not found`);
93
- }
94
-
95
- // Return the app object
96
- return resolve(r);
97
- })
98
- .catch(e => reject(e));
99
- });
100
- };
101
-
102
58
  module.exports = Module;
@@ -49,7 +49,7 @@ Module.prototype.main = function () {
49
49
  }
50
50
 
51
51
  const storage = Manager.storage({temporary: true});
52
- const ipPath = ['api:general:send-email', 'ips', assistant.request.geolocation.ip];
52
+ const ipPath = ['api:general:send-email', 'ips', assistant.request.geolocation.ip || 'unknown'];
53
53
  const emailPath = ['api:general:send-email', 'emails', payload.data.payload.email];
54
54
 
55
55
  const ipData = storage.get(ipPath).value() || {};
@@ -1,3 +1,6 @@
1
+ const path = require('path');
2
+ const { buildPublicConfig } = require(path.join(__dirname, '..', '..', '..', '..', '..', 'routes', 'app', 'get.js'));
3
+
1
4
  function Module() {
2
5
 
3
6
  }
@@ -11,10 +14,7 @@ Module.prototype.main = function () {
11
14
 
12
15
  return new Promise(async function(resolve, reject) {
13
16
 
14
- const fetch = Manager.require('wonderful-fetch');
15
-
16
17
  let uid = payload.data.payload.uid;
17
- const app = payload.data.payload.appId || payload.data.payload.app || Manager.config.app.id;
18
18
  let config = payload.data.payload.config || {};
19
19
 
20
20
  let uuid = null;
@@ -68,32 +68,17 @@ Module.prototype.main = function () {
68
68
  config = {};
69
69
  }
70
70
 
71
- // Fetch app details
72
- await fetch('https://us-central1-itw-creative-works.cloudfunctions.net/getApp', {
73
- method: 'post',
74
- timeout: 30000,
75
- tries: 3,
76
- response: 'json',
77
- body: {
78
- id: app,
79
- },
80
- })
81
- .then(result => {
82
- return resolve({
83
- data: {
84
- uuid: uuid,
85
- signInToken: signInToken,
86
- timestamp: new Date().toISOString(),
87
- ip: assistant.request.geolocation.ip,
88
- country: assistant.request.geolocation.country,
89
- app: result,
90
- config: config,
91
- }
92
- });
93
- })
94
- .catch(e => {
95
- return reject(assistant.errorify(`Error fetching app details: ${e}`, {code: 500}));
96
- })
71
+ return resolve({
72
+ data: {
73
+ uuid: uuid,
74
+ signInToken: signInToken,
75
+ timestamp: new Date().toISOString(),
76
+ ip: assistant.request.geolocation.ip,
77
+ country: assistant.request.geolocation.country,
78
+ app: buildPublicConfig(Manager.config),
79
+ config: config,
80
+ }
81
+ });
97
82
 
98
83
  });
99
84
 
@@ -1,5 +1,4 @@
1
1
  const pushid = require('pushid');
2
- const fetch = require('wonderful-fetch');
3
2
  const powertools = require('node-powertools');
4
3
 
5
4
  function Module() {
@@ -34,55 +33,42 @@ Module.prototype.main = function () {
34
33
  decision.promptReview = true;
35
34
  }
36
35
 
37
- // Get app data
38
- fetch(`https://us-central1-itw-creative-works.cloudfunctions.net/getApp`, {
39
- method: 'post',
40
- response: 'json',
41
- body: {
42
- id: Manager.config.app.id,
43
- }
44
- })
45
- .then((response) => {
46
- response.reviews = response.reviews || {};
47
- response.reviews.enabled = typeof response.reviews.enabled === 'undefined' ? true : response.reviews.enabled;
48
- response.reviews.sites = response.reviews.sites || [];
36
+ // Get review config from local config
37
+ const reviews = { ...(Manager.config.reviews || {}) };
38
+ reviews.enabled = typeof reviews.enabled === 'undefined' ? true : reviews.enabled;
39
+ reviews.sites = reviews.sites || [];
49
40
 
50
- // If reviews are enabled and there are review sites, prompt review
51
- if (response.reviews.enabled && response.reviews.sites.length > 0) {
52
- decision.reviewURL = powertools.random(response.reviews.sites);
53
- } else {
54
- decision.promptReview = false;
55
- }
41
+ // If reviews are enabled and there are review sites, prompt review
42
+ if (decision.promptReview && reviews.enabled && reviews.sites.length > 0) {
43
+ decision.reviewURL = powertools.random(reviews.sites);
44
+ } else {
45
+ decision.promptReview = false;
46
+ }
56
47
 
57
- assistant.log('Feedback submitted', docId, {appReviewData: response.reviews, request: request, decision: decision});
48
+ assistant.log('Feedback submitted', docId, {appReviewData: reviews, request: request, decision: decision});
58
49
 
59
- // Save feedback to firestore
60
- self.libraries.admin.firestore().doc(`feedback/${docId}`)
61
- .set({
62
- created: assistant.meta.startTime,
63
- feedback: request,
64
- decision: decision,
65
- owner: {
66
- uid: user?.auth?.uid ?? null,
67
- },
68
- metadata: Manager.Metadata().set({tag: 'user:submit-feedback'}),
69
- }, {merge: true})
70
- .then(r => {
71
- return resolve({
72
- data: {
73
- review: decision,
74
- originalRequest: request,
75
- }
76
- });
77
- })
78
- .catch((e) => {
79
- return reject(assistant.errorify(`Failed to save feedback: ${e.message}`, {code: 500, sentry: true}));
80
- })
50
+ // Save feedback to firestore
51
+ self.libraries.admin.firestore().doc(`feedback/${docId}`)
52
+ .set({
53
+ created: assistant.meta.startTime,
54
+ feedback: request,
55
+ decision: decision,
56
+ owner: {
57
+ uid: user?.auth?.uid ?? null,
58
+ },
59
+ metadata: Manager.Metadata().set({tag: 'user:submit-feedback'}),
60
+ }, {merge: true})
61
+ .then(r => {
62
+ return resolve({
63
+ data: {
64
+ review: decision,
65
+ originalRequest: request,
66
+ }
67
+ });
81
68
  })
82
69
  .catch((e) => {
83
- return reject(assistant.errorify(`Failed to get app: ${e.message}`, {code: 500, sentry: true}));
70
+ return reject(assistant.errorify(`Failed to save feedback: ${e.message}`, {code: 500, sentry: true}));
84
71
  })
85
-
86
72
  })
87
73
  .catch((e) => {
88
74
  return reject(e);
@@ -82,7 +82,7 @@ Module.prototype.main = function() {
82
82
  // Log
83
83
  // assistant.log(`Executing: ${resolved.command}`, self.payload, JSON.stringify(self.payload))
84
84
  // assistant.log(`Resolved URL: ${Manager.project.functionsUrl}?command=${encodeURIComponent(resolved.command)}&payload=${encodeURIComponent(JSON.stringify(self.assistant.request.data.payload))}`)
85
- assistant.log(`bm_api(${resolved.command}): Request (${geolocation.ip} @ ${geolocation.country}, ${geolocation.region}, ${geolocation.city}) [${method} > ${strippedUrl}]`, JSON.stringify(assistant.request.data));
85
+ assistant.log(`bm_api(${resolved.command}): Request (${geolocation.ip || 'unknown'} @ ${geolocation.country || '?'}, ${geolocation.region || '?'}, ${geolocation.city || '?'}) [${method} > ${strippedUrl}]`, JSON.stringify(assistant.request.data));
86
86
  assistant.log(`bm_api(${resolved.command}): Headers`, JSON.stringify(headers));
87
87
 
88
88
 
@@ -23,21 +23,21 @@ function Analytics(Manager, options) {
23
23
 
24
24
  // Set request properties
25
25
  self.request = {
26
- ip: self.assistant?.request?.geolocation?.ip || '127.0.0.1',
27
- country: self.assistant?.request?.geolocation?.country || '',
28
- city: self.assistant?.request?.geolocation?.city || '',
29
- region: self.assistant?.request?.geolocation?.region || '',
30
- referrer: self.assistant?.request?.referrer || '',
31
- userAgent: self.assistant?.request?.client?.userAgent || '',
32
- language: (self.assistant?.request?.client?.language || '').split(',')[0],
26
+ ip: self.assistant?.request?.geolocation?.ip || null,
27
+ country: self.assistant?.request?.geolocation?.country || null,
28
+ city: self.assistant?.request?.geolocation?.city || null,
29
+ region: self.assistant?.request?.geolocation?.region || null,
30
+ referrer: self.assistant?.request?.referrer || null,
31
+ userAgent: self.assistant?.request?.client?.userAgent || null,
32
+ language: (self.assistant?.request?.client?.language || '').split(',')[0] || null,
33
33
  mobile: self.assistant?.request?.client?.mobile || false,
34
- platform: self.assistant?.request?.client?.platform || '',
34
+ platform: self.assistant?.request?.client?.platform || null,
35
35
  name: self.assistant?.meta?.name || '',
36
36
  }
37
37
 
38
38
  // Remove blacklisted user agents
39
- self.request.userAgent = BLOCKED_USER_AGENTS.some((regex) => self.request.userAgent.match(regex))
40
- ? ''
39
+ self.request.userAgent = self.request.userAgent && BLOCKED_USER_AGENTS.some((regex) => self.request.userAgent.match(regex))
40
+ ? null
41
41
  : self.request.userAgent;
42
42
 
43
43
  // Fix options
@@ -170,7 +170,7 @@ ApiManager.prototype.getUser = async function (assistant) {
170
170
  // console.log('---authenticatedUser', authenticatedUser);
171
171
  const planId = authenticatedUser?.subscription?.product?.id || 'basic';
172
172
  let workingUID = !authenticatedUser.authenticated
173
- ? uuidv5(assistant.request.geolocation.ip, '1b671a64-40d5-491e-99b0-da01ff1f3341')
173
+ ? uuidv5(assistant.request.geolocation.ip || 'unknown', '1b671a64-40d5-491e-99b0-da01ff1f3341')
174
174
  : authenticatedUser.auth.uid
175
175
  authenticatedUser.ip = assistant.request.geolocation.ip;
176
176
  authenticatedUser.country = assistant.request.geolocation.country;
@@ -221,7 +221,8 @@ BackendAssistant.prototype.init = function (ref, options) {
221
221
  }
222
222
  self.request.url = tryUrl(self);
223
223
  self.request.path = self.ref.req.path || '';
224
- self.request.user = self.resolveAccount({authenticated: false});
224
+ self.request.user = self.Manager.User({}).properties;
225
+ self.request.user.authenticated = false;
225
226
 
226
227
  // Set body and query
227
228
  if (options.accept === 'json') {
@@ -656,7 +657,8 @@ BackendAssistant.prototype.authenticate = async function (options) {
656
657
 
657
658
  // Resolve the user
658
659
  if (options.resolve) {
659
- self.request.user = self.resolveAccount(user);
660
+ self.request.user = self.Manager.User(user).properties;
661
+ self.request.user.authenticated = user.authenticated || false;
660
662
  return self.request.user;
661
663
  } else {
662
664
  return user;
@@ -763,11 +765,6 @@ BackendAssistant.prototype.authenticate = async function (options) {
763
765
  }
764
766
  };
765
767
 
766
- BackendAssistant.prototype.resolveAccount = function (user) {
767
- const ResolveAccount = new (require('resolve-account'))();
768
-
769
- return ResolveAccount.resolve(undefined, user)
770
- }
771
768
 
772
769
  BackendAssistant.prototype.parseRepo = function (repo) {
773
770
  let repoSplit = repo.split('/');
@@ -795,43 +792,35 @@ BackendAssistant.prototype.parseRepo = function (repo) {
795
792
  BackendAssistant.prototype.getHeaderIp = function (headers) {
796
793
  headers = headers || {};
797
794
 
798
- return (
795
+ const value =
799
796
  // these are present for cloudflare requests (11/21/2020)
800
797
  headers['cf-connecting-ip']
801
798
  || headers['fastly-temp-xff']
802
799
 
803
800
  // these are present for non-cloudflare requests (11/21/2020)
804
801
  || headers['x-appengine-user-ip']
805
- || headers['x-forwarded-for']
802
+ || headers['x-forwarded-for'];
806
803
 
807
804
  // Not sure about these
808
805
  // || headers['fastly-client-ip']
809
806
 
810
- // If unsure, return local IP
811
- || '127.0.0.1'
812
- )
813
- .split(',')[0]
814
- .trim();
807
+ return value ? value.split(',')[0].trim() : null;
815
808
  }
816
809
 
817
810
  BackendAssistant.prototype.getHeaderContinent = function (headers) {
818
811
  headers = headers || {};
819
812
 
820
- return (
813
+ const value =
821
814
  // these are present for cloudflare requests (11/21/2020)
822
- headers['cf-ipcontinent']
815
+ headers['cf-ipcontinent'];
823
816
 
824
- // If unsure, return ZZ
825
- || 'ZZ'
826
- )
827
- .split(',')[0]
828
- .trim();
817
+ return value ? value.split(',')[0].trim() : null;
829
818
  }
830
819
 
831
820
  BackendAssistant.prototype.getHeaderCountry = function (headers) {
832
821
  headers = headers || {};
833
822
 
834
- return (
823
+ const value =
835
824
  // these are present for cloudflare requests (11/21/2020)
836
825
  headers['cf-ipcountry']
837
826
 
@@ -839,46 +828,34 @@ BackendAssistant.prototype.getHeaderCountry = function (headers) {
839
828
  || headers['x-country-code']
840
829
 
841
830
  // these are present for non-cloudflare requests (11/21/2020)
842
- || headers['x-appengine-country']
831
+ || headers['x-appengine-country'];
843
832
 
844
- // If unsure, return ZZ
845
- || 'ZZ'
846
- )
847
- .split(',')[0]
848
- .trim();
833
+ return value ? value.split(',')[0].trim() : null;
849
834
  }
850
835
 
851
836
  BackendAssistant.prototype.getHeaderRegion = function (headers) {
852
837
  headers = headers || {};
853
838
 
854
- return (
839
+ const value =
855
840
  // these are present for cloudflare requests (11/21/2020)
856
841
  headers['cf-region']
857
842
 
858
843
  // these are present for non-cloudflare requests (11/21/2020)
859
- || headers['x-appengine-region']
844
+ || headers['x-appengine-region'];
860
845
 
861
- // If unsure, return unknown
862
- || 'Unknown'
863
- )
864
- .split(',')[0]
865
- .trim();
846
+ return value ? value.split(',')[0].trim() : null;
866
847
  }
867
848
 
868
849
  BackendAssistant.prototype.getHeaderCity = function (headers) {
869
850
  headers = headers || {};
870
851
 
871
- return (
852
+ const value =
872
853
  // these are present for cloudflare requests (11/21/2020)
873
854
  headers['cf-ipcity']
874
855
 
875
- || headers['x-appengine-city']
856
+ || headers['x-appengine-city'];
876
857
 
877
- // If unsure, return unknown
878
- || 'Unknown'
879
- )
880
- .split(',')[0]
881
- .trim();
858
+ return value ? value.split(',')[0].trim() : null;
882
859
  }
883
860
 
884
861
  BackendAssistant.prototype.getHeaderLatitude = function (headers) {
@@ -917,33 +894,27 @@ BackendAssistant.prototype.getHeaderLongitude = function (headers) {
917
894
  BackendAssistant.prototype.getHeaderUserAgent = function (headers) {
918
895
  headers = headers || {};
919
896
 
920
- return (
921
- headers['user-agent']
922
- || ''
923
- )
924
- .trim();
897
+ const value = headers['user-agent'];
898
+
899
+ return value ? value.trim() : null;
925
900
  }
926
901
 
927
902
  BackendAssistant.prototype.getHeaderLanguage = function (headers) {
928
903
  headers = headers || {};
929
904
 
930
- return (
905
+ const value =
931
906
  headers['accept-language']
932
- || headers['x-orig-accept-language']
933
- || ''
934
- )
935
- .trim();
907
+ || headers['x-orig-accept-language'];
908
+
909
+ return value ? value.trim() : null;
936
910
  }
937
911
 
938
912
  BackendAssistant.prototype.getHeaderPlatform = function (headers) {
939
913
  headers = headers || {};
940
914
 
941
- return (
942
- headers['sec-ch-ua-platform']
943
- || ''
944
- )
945
- .replace(/"/ig, '')
946
- .trim();
915
+ const value = headers['sec-ch-ua-platform'];
916
+
917
+ return value ? value.replace(/"/ig, '').trim() : null;
947
918
  }
948
919
 
949
920
  BackendAssistant.prototype.getHeaderMobile = function (headers) {
@@ -959,7 +930,7 @@ BackendAssistant.prototype.getHeaderUrl = function (headers) {
959
930
  const self = this;
960
931
  headers = headers || {};
961
932
 
962
- return (
933
+ const value =
963
934
  // Origin header (most reliable for CORS requests)
964
935
  headers['origin']
965
936
 
@@ -968,12 +939,9 @@ BackendAssistant.prototype.getHeaderUrl = function (headers) {
968
939
  || headers['referer']
969
940
 
970
941
  // Reconstruct from host and path if available
971
- || (headers['host'] ? `https://${headers['host']}${self.ref.req?.originalUrl || self.ref.req?.url || ''}` : '')
942
+ || (headers['host'] ? `https://${headers['host']}${self.ref.req?.originalUrl || self.ref.req?.url || ''}` : null);
972
943
 
973
- // If unsure, return empty string
974
- || ''
975
- )
976
- .trim();
944
+ return value ? value.trim() : null;
977
945
  }
978
946
 
979
947
  /**
@@ -78,7 +78,7 @@ Middleware.prototype.run = function (libPath, options) {
78
78
  const strippedUrl = stripUrl(url);
79
79
 
80
80
  // Log
81
- assistant.log(`Middleware.process(): Request (${geolocation.ip} @ ${geolocation.country}, ${geolocation.region}, ${geolocation.city}) [${method} > ${strippedUrl}]`, safeStringify(data));
81
+ assistant.log(`Middleware.process(): Request (${geolocation.ip || 'unknown'} @ ${geolocation.country || '?'}, ${geolocation.region || '?'}, ${geolocation.city || '?'}) [${method} > ${strippedUrl}]`, safeStringify(data));
82
82
  assistant.log(`Middleware.process(): Headers`, safeStringify(headers));
83
83
 
84
84
  // Set paths
@@ -133,6 +133,7 @@ Middleware.prototype.run = function (libPath, options) {
133
133
  const uuid = assistant?.usage?.user?.auth?.uid
134
134
  || assistant.request.user.auth.uid
135
135
  || assistant.request.geolocation.ip
136
+ || 'unknown'
136
137
 
137
138
  assistant.analytics = Manager.Analytics({
138
139
  assistant: assistant,
@@ -59,7 +59,7 @@ Usage.prototype.init = function (assistant, options) {
59
59
  self.storage = Manager.storage({name: 'usage', temporary: true, clear: options.clear, log: options.log});
60
60
 
61
61
  // Set local key
62
- self.key = (options.key || self.assistant.request.geolocation.ip || '')
62
+ self.key = (options.key || self.assistant.request.geolocation.ip || 'unknown')
63
63
  // .replace(/[\.:]/g, '_');
64
64
 
65
65
  // Set paths
@@ -51,7 +51,7 @@ function User(Manager, settings, options) {
51
51
  timestampUNIX: getWithDefault(settings?.subscription?.expires?.timestampUNIX, oldDateUNIX, defaults),
52
52
  },
53
53
  trial: {
54
- activated: getWithDefault(settings?.subscription?.trial?.activated, false, defaults),
54
+ claimed: getWithDefault(settings?.subscription?.trial?.claimed, false, defaults),
55
55
  expires: {
56
56
  timestamp: getWithDefault(settings?.subscription?.trial?.expires?.timestamp, oldDate, defaults),
57
57
  timestampUNIX: getWithDefault(settings?.subscription?.trial?.expires?.timestampUNIX, oldDateUNIX, defaults),
@@ -109,24 +109,24 @@ function User(Manager, settings, options) {
109
109
  timestampUNIX: getWithDefault(settings?.activity?.created?.timestampUNIX, nowUNIX, defaults),
110
110
  },
111
111
  geolocation: {
112
- ip: getWithDefault(settings?.activity?.geolocation?.ip, '', defaults),
113
- continent: getWithDefault(settings?.activity?.geolocation?.continent, '', defaults),
114
- country: getWithDefault(settings?.activity?.geolocation?.country, '', defaults),
115
- region: getWithDefault(settings?.activity?.geolocation?.region, '', defaults),
116
- city: getWithDefault(settings?.activity?.geolocation?.city, '', defaults),
112
+ ip: settings?.activity?.geolocation?.ip ?? null,
113
+ continent: settings?.activity?.geolocation?.continent ?? null,
114
+ country: settings?.activity?.geolocation?.country ?? null,
115
+ region: settings?.activity?.geolocation?.region ?? null,
116
+ city: settings?.activity?.geolocation?.city ?? null,
117
117
  latitude: getWithDefault(settings?.activity?.geolocation?.latitude, 0, defaults),
118
118
  longitude: getWithDefault(settings?.activity?.geolocation?.longitude, 0, defaults),
119
119
  },
120
120
  client: {
121
- language: getWithDefault(settings?.activity?.client?.language, '', defaults),
121
+ language: settings?.activity?.client?.language ?? null,
122
122
  mobile: getWithDefault(settings?.activity?.client?.mobile, false, defaults),
123
- device: getWithDefault(settings?.activity?.client?.device, '', defaults),
124
- platform: getWithDefault(settings?.activity?.client?.platform, '', defaults),
125
- browser: getWithDefault(settings?.activity?.client?.browser, '', defaults),
126
- vendor: getWithDefault(settings?.activity?.client?.vendor, '', defaults),
127
- runtime: getWithDefault(settings?.activity?.client?.runtime, '', defaults),
128
- userAgent: getWithDefault(settings?.activity?.client?.userAgent, '', defaults),
129
- url: getWithDefault(settings?.activity?.client?.url, '', defaults),
123
+ device: settings?.activity?.client?.device ?? null,
124
+ platform: settings?.activity?.client?.platform ?? null,
125
+ browser: settings?.activity?.client?.browser ?? null,
126
+ vendor: settings?.activity?.client?.vendor ?? null,
127
+ runtime: settings?.activity?.client?.runtime ?? null,
128
+ userAgent: settings?.activity?.client?.userAgent ?? null,
129
+ url: settings?.activity?.client?.url ?? null,
130
130
  },
131
131
  },
132
132
  api: {
@@ -138,7 +138,7 @@ function User(Manager, settings, options) {
138
138
  period: getWithDefault(settings?.usage?.requests?.period, 0, defaults),
139
139
  total: getWithDefault(settings?.usage?.requests?.total, 0, defaults),
140
140
  last: {
141
- id: getWithDefault(settings?.usage?.requests?.last?.id, '', defaults),
141
+ id: settings?.usage?.requests?.last?.id ?? null,
142
142
  timestamp: getWithDefault(settings?.usage?.requests?.last?.timestamp, oldDate, defaults),
143
143
  timestampUNIX: getWithDefault(settings?.usage?.requests?.last?.timestampUNIX, oldDateUNIX, defaults),
144
144
  },
@@ -149,19 +149,19 @@ function User(Manager, settings, options) {
149
149
  timestamp: getWithDefault(settings?.personal?.birthday?.timestamp, oldDate, defaults),
150
150
  timestampUNIX: getWithDefault(settings?.personal?.birthday?.timestampUNIX, oldDateUNIX, defaults),
151
151
  },
152
- gender: getWithDefault(settings?.personal?.gender, '', defaults),
152
+ gender: settings?.personal?.gender ?? null,
153
153
  location: {
154
- country: getWithDefault(settings?.personal?.location?.country, '', defaults),
155
- region: getWithDefault(settings?.personal?.location?.region, '', defaults),
156
- city: getWithDefault(settings?.personal?.location?.city, '', defaults),
154
+ country: settings?.personal?.location?.country ?? null,
155
+ region: settings?.personal?.location?.region ?? null,
156
+ city: settings?.personal?.location?.city ?? null,
157
157
  },
158
158
  name: {
159
- first: getWithDefault(settings?.personal?.name?.first, '', defaults),
160
- last: getWithDefault(settings?.personal?.name?.last, '', defaults),
159
+ first: settings?.personal?.name?.first ?? null,
160
+ last: settings?.personal?.name?.last ?? null,
161
161
  },
162
162
  company: {
163
- name: getWithDefault(settings?.personal?.company?.name, '', defaults),
164
- position: getWithDefault(settings?.personal?.company?.position, '', defaults),
163
+ name: settings?.personal?.company?.name ?? null,
164
+ position: settings?.personal?.company?.position ?? null,
165
165
  },
166
166
  telephone: {
167
167
  countryCode: getWithDefault(settings?.personal?.telephone?.countryCode, 0, defaults),
@@ -1111,37 +1111,6 @@ Manager.prototype.setupCustomServer = function (_library, options) {
1111
1111
  });
1112
1112
  }
1113
1113
 
1114
- // Setup Custom Server
1115
- Manager.prototype.getApp = function (id) {
1116
- const self = this;
1117
-
1118
- // Get the app
1119
- return new Promise(function(resolve, reject) {
1120
- const fetch = require('wonderful-fetch');
1121
-
1122
- // Set ID
1123
- id = id || self.config.app.id;
1124
-
1125
- // If no ID, reject
1126
- if (!id) {
1127
- return reject(new Error('No ID provided'));
1128
- }
1129
-
1130
- // Fetch the app
1131
- fetch(`https://us-central1-itw-creative-works.cloudfunctions.net/getApp`, {
1132
- method: 'post',
1133
- response: 'json',
1134
- timeout: 30000,
1135
- tries: 3,
1136
- body: {
1137
- id: id,
1138
- }
1139
- })
1140
- .then((r) => resolve(r))
1141
- .catch((e) => reject(e));
1142
- });
1143
- }
1144
-
1145
1114
  function resolveProjectPackage(dir) {
1146
1115
  try {
1147
1116
  return require(path.resolve(dir, 'functions', 'package.json'));
@@ -243,7 +243,7 @@ OpenAI.prototype.request = function (options) {
243
243
  // Load prompt
244
244
  const prompt = loadContent(options.prompt, _log);
245
245
  const message = loadContent(options.message, _log);
246
- const user = options.user?.auth?.uid || assistant.request.geolocation.ip;
246
+ const user = options.user?.auth?.uid || assistant.request.geolocation.ip || 'unknown';
247
247
 
248
248
  // Log
249
249
  _log('Prompt', prompt);
@@ -0,0 +1,41 @@
1
+ /**
2
+ * GET /app - Public app configuration
3
+ * Returns a safe subset of the project's config (no secrets)
4
+ */
5
+ module.exports = async ({ assistant, Manager }) => {
6
+ const config = Manager.config;
7
+
8
+ return assistant.respond(buildPublicConfig(config));
9
+ };
10
+
11
+ /**
12
+ * Build a public-safe config object from Manager.config
13
+ * Excludes sensitive fields: sentry, google_analytics, ghostii, etc.
14
+ */
15
+ function buildPublicConfig(config) {
16
+ return {
17
+ id: config.app?.id,
18
+ name: config.brand?.name,
19
+ description: config.brand?.description,
20
+ url: config.brand?.url,
21
+ email: config.brand?.contact?.email,
22
+ images: config.brand?.images || {},
23
+ github: {
24
+ user: config.github?.user,
25
+ repo: (config.github?.repo_website || '').split('/').pop(),
26
+ },
27
+ authentication: config.authentication || {},
28
+ reviews: config.reviews || {},
29
+ firebaseConfig: config.firebaseConfig || {},
30
+ products: (config.products || []).map(p => ({
31
+ id: p.id,
32
+ name: p.name,
33
+ type: p.type,
34
+ limits: p.limits || {},
35
+ trial: p.trial || {},
36
+ prices: p.prices || {},
37
+ })),
38
+ };
39
+ }
40
+
41
+ module.exports.buildPublicConfig = buildPublicConfig;
@@ -5,8 +5,6 @@
5
5
  const { merge } = require('lodash');
6
6
 
7
7
  module.exports = async ({ assistant, Manager, analytics }) => {
8
- const fetch = Manager.require('wonderful-fetch');
9
-
10
8
  const defaultProviders = {
11
9
  ['password']: {
12
10
  enabled: true,
@@ -34,14 +32,8 @@ module.exports = async ({ assistant, Manager, analytics }) => {
34
32
  },
35
33
  };
36
34
 
37
- // Get app object
38
- const appObject = await getAppObject(Manager, assistant).catch(e => e);
39
- if (appObject instanceof Error) {
40
- return assistant.respond(`Failed to get app object: ${appObject}`, { code: 500 });
41
- }
42
-
43
- // Merge the default providers with the app providers
44
- const providers = merge(defaultProviders, appObject.authentication);
35
+ // Merge the default providers with the config providers
36
+ const providers = merge(defaultProviders, Manager.config.authentication || {});
45
37
 
46
38
  // Reformat the object so it's just provider=true/false
47
39
  const finalProviders = {};
@@ -56,25 +48,3 @@ module.exports = async ({ assistant, Manager, analytics }) => {
56
48
 
57
49
  return assistant.respond(finalProviders);
58
50
  };
59
-
60
- // Helper: Get app object from ITW
61
- async function getAppObject(Manager, assistant) {
62
- const fetch = Manager.require('wonderful-fetch');
63
- const id = Manager.config.app.id;
64
-
65
- const result = await fetch('https://us-central1-itw-creative-works.cloudfunctions.net/getApp', {
66
- method: 'post',
67
- response: 'json',
68
- body: {
69
- id: id,
70
- }
71
- });
72
-
73
- assistant.log('getAppObject(): Response', result);
74
-
75
- if (!result) {
76
- throw new Error(`App with id ${id} not found`);
77
- }
78
-
79
- return result;
80
- }
@@ -43,7 +43,7 @@ module.exports = async ({ assistant, Manager, settings, analytics }) => {
43
43
 
44
44
  // Check spam filter using local storage
45
45
  const storage = Manager.storage({ temporary: true });
46
- const ipPath = ['api:general:email', 'ips', assistant.request.geolocation.ip];
46
+ const ipPath = ['api:general:email', 'ips', assistant.request.geolocation.ip || 'unknown'];
47
47
  const emailPath = ['api:general:email', 'emails', settings.email];
48
48
 
49
49
  const ipData = storage.get(ipPath).value() || {};
@@ -2,13 +2,14 @@
2
2
  * POST /special/electron-client - Setup Electron Manager client
3
3
  * Returns client configuration with optional auth
4
4
  */
5
+ const path = require('path');
6
+ const { buildPublicConfig } = require(path.join(__dirname, '..', '..', 'app', 'get.js'));
7
+
5
8
  module.exports = async ({ assistant, Manager, settings, analytics, libraries }) => {
6
- const fetch = Manager.require('wonderful-fetch');
7
9
  const { admin } = libraries;
8
10
 
9
11
  // appId/app fallback to Manager.config
10
12
  let uid = settings.uid;
11
- const app = settings.appId || settings.app || Manager.config.app.id;
12
13
  let config = settings.config;
13
14
 
14
15
  let uuid = null;
@@ -42,21 +43,6 @@ module.exports = async ({ assistant, Manager, settings, analytics, libraries })
42
43
  config = {};
43
44
  }
44
45
 
45
- // Fetch app details
46
- const appDetails = await fetch('https://us-central1-itw-creative-works.cloudfunctions.net/getApp', {
47
- method: 'post',
48
- timeout: 30000,
49
- tries: 3,
50
- response: 'json',
51
- body: {
52
- id: app,
53
- },
54
- }).catch(e => e);
55
-
56
- if (appDetails instanceof Error) {
57
- return assistant.respond(`Error fetching app details: ${appDetails}`, { code: 500 });
58
- }
59
-
60
46
  // Track analytics
61
47
  analytics.event('special/electron-client', { action: 'setup' });
62
48
 
@@ -66,7 +52,7 @@ module.exports = async ({ assistant, Manager, settings, analytics, libraries })
66
52
  timestamp: new Date().toISOString(),
67
53
  ip: assistant.request.geolocation.ip,
68
54
  country: assistant.request.geolocation.country,
69
- app: appDetails,
55
+ app: buildPublicConfig(Manager.config),
70
56
  config: config,
71
57
  });
72
58
  };
@@ -1,5 +1,4 @@
1
1
  const pushid = require('pushid');
2
- const fetch = require('wonderful-fetch');
3
2
  const powertools = require('node-powertools');
4
3
 
5
4
  /**
@@ -30,17 +29,8 @@ module.exports = async ({ assistant, Manager, user, settings, libraries }) => {
30
29
  decision.promptReview = true;
31
30
  }
32
31
 
33
- // Get app data for review URLs
34
- const appResponse = await fetch('https://us-central1-itw-creative-works.cloudfunctions.net/getApp', {
35
- method: 'post',
36
- response: 'json',
37
- body: { id: Manager.config.app.id },
38
- }).catch((e) => {
39
- assistant.error(`Failed to get app: ${e.message}`);
40
- return {};
41
- });
42
-
43
- const reviews = appResponse.reviews || {};
32
+ // Get review config from local config
33
+ const reviews = { ...(Manager.config.reviews || {}) };
44
34
  reviews.enabled = typeof reviews.enabled === 'undefined' ? true : reviews.enabled;
45
35
  reviews.sites = reviews.sites || [];
46
36
 
@@ -0,0 +1 @@
1
+ module.exports = () => ({});
@@ -2,7 +2,7 @@ const uuid = require('uuid');
2
2
 
3
3
  /**
4
4
  * Helper to create a future expiration date for premium subscriptions
5
- * resolve-account checks subscription.expires to determine if subscription is active
5
+ * User() checks subscription.expires to determine if subscription is active
6
6
  * If expires is in the past (or default 1970), subscription gets downgraded to basic
7
7
  */
8
8
  function getFutureExpires(years = 10) {
@@ -38,7 +38,7 @@ function getPastExpires(years = 1) {
38
38
  * - properties: Object to merge into user doc after auth:on-create
39
39
  *
40
40
  * IMPORTANT: Premium accounts MUST have subscription.expires set to a future date
41
- * or resolve-account will downgrade them to basic
41
+ * and subscription.status set to 'active'
42
42
  */
43
43
  const STATIC_ACCOUNTS = {
44
44
  admin: {
@@ -2,6 +2,7 @@
2
2
  brand: {
3
3
  id: 'my-app',
4
4
  name: 'My Brand',
5
+ description: '',
5
6
  url: 'https://example.com',
6
7
  contact: {
7
8
  email: 'support@example.com',
@@ -16,6 +17,14 @@
16
17
  user: 'username',
17
18
  repo_website: 'https://github.com/username/backend-manager',
18
19
  },
20
+ authentication: {
21
+ 'password': { enabled: true },
22
+ 'google.com': { enabled: true },
23
+ },
24
+ reviews: {
25
+ enabled: true,
26
+ sites: [],
27
+ },
19
28
  sentry: {
20
29
  dsn: 'https://d965557418748jd749d837asf00552f@o777489.ingest.sentry.io/8789941',
21
30
  },
@@ -45,6 +54,8 @@
45
54
  prompt: '',
46
55
  chance: 1.0,
47
56
  author: 'alex-raeburn',
57
+ // app: 'other-app-id', // Optional: target a different app
58
+ // appUrl: 'https://api.otherapp.com', // Required if app is set (fetches /backend-manager/app)
48
59
  }
49
60
  ],
50
61
  products: [
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Test: GET /user
3
3
  * Tests the user resolve endpoint
4
- * Returns RESOLVED user account info for authenticated users
5
- * Validates that resolve-account correctly processes user data
4
+ * Returns user account info for authenticated users
5
+ * Validates that User() correctly structures user data
6
6
  */
7
7
  module.exports = {
8
8
  description: 'User resolve (account info)',
@@ -129,9 +129,9 @@ module.exports = {
129
129
  },
130
130
  },
131
131
 
132
- // Test 6: Premium expired user - verify subscription is downgraded to basic
132
+ // Test 6: Premium expired user - verify subscription retains product but shows cancelled status
133
133
  {
134
- name: 'premium-expired-user-downgraded',
134
+ name: 'premium-expired-user-cancelled',
135
135
  auth: 'premium-expired',
136
136
  timeout: 15000,
137
137
 
@@ -145,8 +145,9 @@ module.exports = {
145
145
  // Verify auth properties
146
146
  assert.equal(user.auth.uid, accounts['premium-expired'].uid, 'UID should match expired premium test account');
147
147
 
148
- // Verify subscription - expired premium should be downgraded to basic by resolve-account
149
- assert.equal(user.subscription.product.id, 'basic', 'Expired premium subscription should be downgraded to basic');
148
+ // Verify subscription - product.id stays premium, status reflects cancellation
149
+ assert.equal(user.subscription.product.id, 'premium', 'Product ID should remain premium');
150
+ assert.equal(user.subscription.status, 'cancelled', 'Status should be cancelled');
150
151
  },
151
152
  },
152
153
  ],