backend-manager 5.0.76 → 5.0.78

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.76",
3
+ "version": "5.0.78",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -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;
@@ -792,43 +792,35 @@ BackendAssistant.prototype.parseRepo = function (repo) {
792
792
  BackendAssistant.prototype.getHeaderIp = function (headers) {
793
793
  headers = headers || {};
794
794
 
795
- return (
795
+ const value =
796
796
  // these are present for cloudflare requests (11/21/2020)
797
797
  headers['cf-connecting-ip']
798
798
  || headers['fastly-temp-xff']
799
799
 
800
800
  // these are present for non-cloudflare requests (11/21/2020)
801
801
  || headers['x-appengine-user-ip']
802
- || headers['x-forwarded-for']
802
+ || headers['x-forwarded-for'];
803
803
 
804
804
  // Not sure about these
805
805
  // || headers['fastly-client-ip']
806
806
 
807
- // If unsure, return local IP
808
- || '127.0.0.1'
809
- )
810
- .split(',')[0]
811
- .trim();
807
+ return value ? value.split(',')[0].trim() : null;
812
808
  }
813
809
 
814
810
  BackendAssistant.prototype.getHeaderContinent = function (headers) {
815
811
  headers = headers || {};
816
812
 
817
- return (
813
+ const value =
818
814
  // these are present for cloudflare requests (11/21/2020)
819
- headers['cf-ipcontinent']
815
+ headers['cf-ipcontinent'];
820
816
 
821
- // If unsure, return ZZ
822
- || 'ZZ'
823
- )
824
- .split(',')[0]
825
- .trim();
817
+ return value ? value.split(',')[0].trim() : null;
826
818
  }
827
819
 
828
820
  BackendAssistant.prototype.getHeaderCountry = function (headers) {
829
821
  headers = headers || {};
830
822
 
831
- return (
823
+ const value =
832
824
  // these are present for cloudflare requests (11/21/2020)
833
825
  headers['cf-ipcountry']
834
826
 
@@ -836,46 +828,34 @@ BackendAssistant.prototype.getHeaderCountry = function (headers) {
836
828
  || headers['x-country-code']
837
829
 
838
830
  // these are present for non-cloudflare requests (11/21/2020)
839
- || headers['x-appengine-country']
831
+ || headers['x-appengine-country'];
840
832
 
841
- // If unsure, return ZZ
842
- || 'ZZ'
843
- )
844
- .split(',')[0]
845
- .trim();
833
+ return value ? value.split(',')[0].trim() : null;
846
834
  }
847
835
 
848
836
  BackendAssistant.prototype.getHeaderRegion = function (headers) {
849
837
  headers = headers || {};
850
838
 
851
- return (
839
+ const value =
852
840
  // these are present for cloudflare requests (11/21/2020)
853
841
  headers['cf-region']
854
842
 
855
843
  // these are present for non-cloudflare requests (11/21/2020)
856
- || headers['x-appengine-region']
844
+ || headers['x-appengine-region'];
857
845
 
858
- // If unsure, return unknown
859
- || 'Unknown'
860
- )
861
- .split(',')[0]
862
- .trim();
846
+ return value ? value.split(',')[0].trim() : null;
863
847
  }
864
848
 
865
849
  BackendAssistant.prototype.getHeaderCity = function (headers) {
866
850
  headers = headers || {};
867
851
 
868
- return (
852
+ const value =
869
853
  // these are present for cloudflare requests (11/21/2020)
870
854
  headers['cf-ipcity']
871
855
 
872
- || headers['x-appengine-city']
856
+ || headers['x-appengine-city'];
873
857
 
874
- // If unsure, return unknown
875
- || 'Unknown'
876
- )
877
- .split(',')[0]
878
- .trim();
858
+ return value ? value.split(',')[0].trim() : null;
879
859
  }
880
860
 
881
861
  BackendAssistant.prototype.getHeaderLatitude = function (headers) {
@@ -914,33 +894,27 @@ BackendAssistant.prototype.getHeaderLongitude = function (headers) {
914
894
  BackendAssistant.prototype.getHeaderUserAgent = function (headers) {
915
895
  headers = headers || {};
916
896
 
917
- return (
918
- headers['user-agent']
919
- || ''
920
- )
921
- .trim();
897
+ const value = headers['user-agent'];
898
+
899
+ return value ? value.trim() : null;
922
900
  }
923
901
 
924
902
  BackendAssistant.prototype.getHeaderLanguage = function (headers) {
925
903
  headers = headers || {};
926
904
 
927
- return (
905
+ const value =
928
906
  headers['accept-language']
929
- || headers['x-orig-accept-language']
930
- || ''
931
- )
932
- .trim();
907
+ || headers['x-orig-accept-language'];
908
+
909
+ return value ? value.trim() : null;
933
910
  }
934
911
 
935
912
  BackendAssistant.prototype.getHeaderPlatform = function (headers) {
936
913
  headers = headers || {};
937
914
 
938
- return (
939
- headers['sec-ch-ua-platform']
940
- || ''
941
- )
942
- .replace(/"/ig, '')
943
- .trim();
915
+ const value = headers['sec-ch-ua-platform'];
916
+
917
+ return value ? value.replace(/"/ig, '').trim() : null;
944
918
  }
945
919
 
946
920
  BackendAssistant.prototype.getHeaderMobile = function (headers) {
@@ -956,7 +930,7 @@ BackendAssistant.prototype.getHeaderUrl = function (headers) {
956
930
  const self = this;
957
931
  headers = headers || {};
958
932
 
959
- return (
933
+ const value =
960
934
  // Origin header (most reliable for CORS requests)
961
935
  headers['origin']
962
936
 
@@ -965,12 +939,9 @@ BackendAssistant.prototype.getHeaderUrl = function (headers) {
965
939
  || headers['referer']
966
940
 
967
941
  // Reconstruct from host and path if available
968
- || (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);
969
943
 
970
- // If unsure, return empty string
971
- || ''
972
- )
973
- .trim();
944
+ return value ? value.trim() : null;
974
945
  }
975
946
 
976
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,40 @@
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
+ reviews: config.reviews || {},
28
+ firebaseConfig: config.firebaseConfig || {},
29
+ products: (config.products || []).map(p => ({
30
+ id: p.id,
31
+ name: p.name,
32
+ type: p.type,
33
+ limits: p.limits || {},
34
+ trial: p.trial || {},
35
+ prices: p.prices || {},
36
+ })),
37
+ };
38
+ }
39
+
40
+ module.exports.buildPublicConfig = buildPublicConfig;
@@ -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,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',
@@ -12,6 +13,12 @@
12
13
  combomark: 'https://example.com/combomark.png',
13
14
  },
14
15
  },
16
+ reviews: {
17
+ enabled: true,
18
+ sites: [
19
+ 'trustpilot.com',
20
+ ],
21
+ },
15
22
  github: {
16
23
  user: 'username',
17
24
  repo_website: 'https://github.com/username/backend-manager',
@@ -45,6 +52,8 @@
45
52
  prompt: '',
46
53
  chance: 1.0,
47
54
  author: 'alex-raeburn',
55
+ // app: 'other-app-id', // Optional: target a different app
56
+ // appUrl: 'https://api.otherapp.com', // Required if app is set (fetches /backend-manager/app)
48
57
  }
49
58
  ],
50
59
  products: [
@@ -1,80 +0,0 @@
1
- /**
2
- * GET /firebase/providers - Get authentication providers
3
- * Returns enabled auth providers for the app
4
- */
5
- const { merge } = require('lodash');
6
-
7
- module.exports = async ({ assistant, Manager, analytics }) => {
8
- const fetch = Manager.require('wonderful-fetch');
9
-
10
- const defaultProviders = {
11
- ['password']: {
12
- enabled: true,
13
- },
14
- ['google.com']: {
15
- enabled: true,
16
- },
17
- ['facebook.com']: {
18
- enabled: false,
19
- },
20
- ['twitter.com']: {
21
- enabled: false,
22
- },
23
- ['github.com']: {
24
- enabled: false,
25
- },
26
- ['microsoft.com']: {
27
- enabled: false,
28
- },
29
- ['yahoo.com']: {
30
- enabled: false,
31
- },
32
- ['apple.com']: {
33
- enabled: false,
34
- },
35
- };
36
-
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);
45
-
46
- // Reformat the object so it's just provider=true/false
47
- const finalProviders = {};
48
- Object.keys(providers).forEach(key => {
49
- finalProviders[key] = providers[key].enabled;
50
- });
51
-
52
- assistant.log('Providers', finalProviders);
53
-
54
- // Track analytics
55
- analytics.event('firebase/providers', { action: 'get' });
56
-
57
- return assistant.respond(finalProviders);
58
- };
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
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Schema for GET /firebase/providers
3
- */
4
- module.exports = () => ({
5
- // No specific parameters required
6
- });