ultimate-jekyll-manager 0.0.197 → 0.0.199

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.
@@ -1,5 +1,8 @@
1
1
  import authorizedFetch from '__main_assets__/js/libs/authorized-fetch.js';
2
2
 
3
+ // Constants
4
+ const SIGNUP_MAX_AGE = 5 * 60 * 1000;
5
+
3
6
  // Auth Module
4
7
  export default function (Manager, options) {
5
8
  // Shortcuts
@@ -45,22 +48,8 @@ export default function (Manager, options) {
45
48
  if (user) {
46
49
  // User is authenticated
47
50
 
48
- // Check if this is a new user account (created in last 5 minutes) and send metadata
49
- const accountAge = Date.now() - new Date(user.metadata.creationTime).getTime();
50
- const fiveMinutes = 5 * 60 * 1000;
51
-
52
- /* @dev-only:start */
53
- {
54
- // Log account age for debugging
55
- const ageInMinutes = Math.floor(accountAge / 1000 / 60);
56
- console.log('[Auth] Account age:', ageInMinutes, 'minutes');
57
- }
58
- /* @dev-only:end */
59
-
60
51
  // Send user signup metadata if account is new
61
- if (accountAge < fiveMinutes) {
62
- await sendUserSignupMetadata(user, webManager);
63
- }
52
+ await sendUserSignupMetadata(user, webManager);
64
53
 
65
54
  // Check if page requires user to be unauthenticated (e.g., signin page)
66
55
  if (policy === 'unauthenticated') {
@@ -167,45 +156,54 @@ function setAnalyticsUserId(user, webManager) {
167
156
  // Send user metadata to server (affiliate, UTM params, etc.)
168
157
  async function sendUserSignupMetadata(user, webManager) {
169
158
  try {
170
- // Get affiliate data from storage
171
- const affiliateData = webManager.storage().get('marketing.affiliate', null);
172
- const utmData = webManager.storage().get('marketing.utm', null);
173
- const signupSent = webManager.storage().get('marketing.signupSent', false);
159
+ // Check if this is a new user account (created in last 5 minutes)
160
+ const accountAge = Date.now() - new Date(user.metadata.creationTime).getTime();
161
+ const signupProcessed = webManager.storage().get('flags.signupProcessed', false);
162
+
163
+ /* @dev-only:start */
164
+ {
165
+ // Log account age for debugging
166
+ const ageInMinutes = Math.floor(accountAge / 1000 / 60);
167
+ console.log('[Auth] Account age:', ageInMinutes, 'minutes, signupProcessed:', signupProcessed);
168
+ }
169
+ /* @dev-only:end */
174
170
 
175
- // Only proceed if we haven't sent signup metadata yet
176
- if (signupSent) {
171
+ // Only proceed if account is new and we haven't sent signup metadata yet
172
+ if (accountAge >= SIGNUP_MAX_AGE || signupProcessed) {
177
173
  return;
178
174
  }
179
175
 
176
+ // Get attribution data from storage
177
+ const attribution = webManager.storage().get('attribution', {});
178
+
180
179
  // Build the payload
181
180
  const payload = {
182
- // @TODO: REMOVE ONCE LEGACY SERVER CODE IS GONE
183
- affiliateCode: affiliateData?.code || '',
181
+ // Legacy support (can be removed once all servers are updated)
182
+ affiliateCode: attribution.affiliate?.code || '',
184
183
 
185
184
  // New structure
186
- affiliateData: affiliateData || {},
187
- utmData: utmData || {},
185
+ attribution: attribution,
188
186
  context: webManager.utilities().getContext(),
189
187
  };
190
188
 
191
189
  // Get server API URL
192
- const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
190
+ const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/signup`;
191
+
192
+ // Log
193
+ console.log('[Auth] Sending user metadata:', payload);
193
194
 
194
195
  // Make API call to send signup metadata
195
196
  const response = await authorizedFetch(serverApiURL, {
196
197
  method: 'POST',
197
198
  response: 'json',
198
- body: {
199
- command: 'user:sign-up',
200
- payload: payload
201
- }
199
+ body: payload,
202
200
  });
203
201
 
204
202
  // Log
205
203
  console.log('[Auth] User metadata sent successfully:', response);
206
204
 
207
- // Mark signup as sent (keep the marketing data for reference)
208
- webManager.storage().set('marketing.signupSent', true);
205
+ // Mark signup as sent (keep the attribution data for reference)
206
+ webManager.storage().set('flags.signupProcessed', true);
209
207
  } catch (error) {
210
208
  console.error('[Auth] Error sending user metadata:', error);
211
209
  // Don't throw - we don't want to block the signup flow
@@ -12,14 +12,22 @@ export default function (Manager, options) {
12
12
  // Get URL parameters
13
13
  const urlParams = new URLSearchParams(window.location.search);
14
14
 
15
+ // Get current attribution data
16
+ const attribution = webManager.storage().get('attribution', {});
17
+
15
18
  // Process affiliate/referral parameters
16
- processAffiliateParams(urlParams);
19
+ processAffiliateParams(urlParams, attribution);
17
20
 
18
21
  // Process UTM parameters
19
- processUTMParams(urlParams);
22
+ processUTMParams(urlParams, attribution);
23
+
24
+ // Save updated attribution if anything changed
25
+ if (Object.keys(attribution).length > 0) {
26
+ webManager.storage().set('attribution', attribution);
27
+ }
20
28
  }
21
29
 
22
- function processAffiliateParams(urlParams) {
30
+ function processAffiliateParams(urlParams, attribution) {
23
31
  // Check for aff or ref parameter
24
32
  const affParam = urlParams.get('aff') || urlParams.get('ref');
25
33
 
@@ -28,19 +36,16 @@ export default function (Manager, options) {
28
36
  return;
29
37
  }
30
38
 
31
- // Save to localStorage with timestamp
32
- const affiliateData = {
39
+ // Save affiliate data to attribution object
40
+ attribution.affiliate = {
33
41
  code: affParam,
34
42
  timestamp: new Date().toISOString(),
35
43
  url: window.location.href,
36
44
  page: window.location.pathname
37
45
  };
38
-
39
- // Use webManager storage to save affiliate data
40
- webManager.storage().set('marketing.affiliate', affiliateData);
41
46
  }
42
47
 
43
- function processUTMParams(urlParams) {
48
+ function processUTMParams(urlParams, attribution) {
44
49
  // Define UTM parameters to capture
45
50
  const utmParams = [
46
51
  'utm_source',
@@ -69,12 +74,11 @@ export default function (Manager, options) {
69
74
  return;
70
75
  }
71
76
 
72
- // Add metadata
77
+ // Add metadata and save to attribution object
73
78
  utmData.timestamp = new Date().toISOString();
74
79
  utmData.url = window.location.href;
75
80
  utmData.page = window.location.pathname;
76
81
 
77
- // Save to localStorage
78
- webManager.storage().set('marketing.utm', utmData);
82
+ attribution.utm = utmData;
79
83
  }
80
84
  }
@@ -65,7 +65,7 @@ function setupResetApiKeyForm() {
65
65
  }
66
66
 
67
67
  // Get server API URL
68
- const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
68
+ const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/api-keys`;
69
69
 
70
70
  // Make API call to reset API key
71
71
  const response = await authorizedFetch(serverApiURL, {
@@ -73,9 +73,6 @@ function setupResetApiKeyForm() {
73
73
  timeout: 30000,
74
74
  response: 'json',
75
75
  tries: 2,
76
- body: {
77
- command: 'user:regenerate-api-keys',
78
- },
79
76
  });
80
77
 
81
78
  if (!response.privateKey) {
@@ -16,7 +16,7 @@ const supportedProviders = ['google', 'discord', 'github', 'twitter', 'facebook'
16
16
 
17
17
  // Get API URL helper
18
18
  function getApiUrl() {
19
- return `${webManager.getApiUrl()}/backend-manager`;
19
+ return `${webManager.getApiUrl()}/backend-manager/user/oauth2`;
20
20
  }
21
21
 
22
22
  // Initialize connections section
@@ -295,14 +295,11 @@ async function handleConnect(providerId) {
295
295
  response: 'json',
296
296
  tries: 2,
297
297
  body: {
298
- command: 'user:oauth2',
299
- payload: {
300
- redirect: false,
301
- provider: providerId,
302
- state: 'authorize',
303
- scope: scope,
304
- referrer: window.location.href,
305
- },
298
+ redirect: false,
299
+ provider: providerId,
300
+ state: 'authorize',
301
+ scope: scope,
302
+ referrer: window.location.href,
306
303
  },
307
304
  });
308
305
 
@@ -338,14 +335,11 @@ async function handleDisconnect(providerId) {
338
335
  response: 'json',
339
336
  tries: 2,
340
337
  body: {
341
- command: 'user:oauth2',
342
- payload: {
343
- redirect: false,
344
- provider: providerId,
345
- state: 'deauthorize',
346
- scope: scope,
347
- referrer: window.location.href,
348
- },
338
+ redirect: false,
339
+ provider: providerId,
340
+ state: 'deauthorize',
341
+ scope: scope,
342
+ referrer: window.location.href,
349
343
  },
350
344
  });
351
345
 
@@ -70,17 +70,14 @@ function setupDeleteAccountForm() {
70
70
  }
71
71
 
72
72
  // Send delete request to server
73
- const response = await authorizedFetch(webManager.getApiUrl(), {
74
- method: 'POST',
73
+ const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user`, {
74
+ method: 'DELETE',
75
75
  timeout: 30000,
76
76
  response: 'json',
77
77
  tries: 2,
78
78
  body: {
79
- command: 'user:delete',
80
- payload: {
81
- reason: data.reason || '',
82
- confirmed: true,
83
- },
79
+ reason: data.reason || '',
80
+ confirmed: true,
84
81
  },
85
82
  });
86
83
 
@@ -198,17 +198,13 @@ async function updateActiveSessions(account) {
198
198
 
199
199
  // Fetch other active sessions from server
200
200
  try {
201
- const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
201
+ const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/sessions`;
202
202
 
203
203
  const data = await authorizedFetch(serverApiURL, {
204
- method: 'POST',
204
+ method: 'GET',
205
205
  timeout: 60000,
206
206
  response: 'json',
207
207
  tries: 2,
208
- body: {
209
- command: 'user:get-active-sessions',
210
- payload: {},
211
- },
212
208
  });
213
209
 
214
210
  // Process sessions from server response
@@ -240,7 +240,7 @@ function transformDataForAPI(formData) {
240
240
 
241
241
  // Send notification via API
242
242
  async function sendNotification(payload) {
243
- const functionsUrl = getAPIFunctionsUrl();
243
+ const functionsUrl = `${getAPIFunctionsUrl()}/admin/notification`;
244
244
 
245
245
  return authorizedFetch(functionsUrl, {
246
246
  method: 'POST',
@@ -248,28 +248,19 @@ async function sendNotification(payload) {
248
248
  response: 'json',
249
249
  tries: 1,
250
250
  log: true,
251
- body: {
252
- command: 'admin:send-notification',
253
- payload: payload,
254
- },
251
+ body: payload,
255
252
  });
256
253
  }
257
254
 
258
255
  // Fetch user statistics
259
256
  async function fetchUserStats() {
260
257
  try {
261
- const response = await authorizedFetch(getAPIFunctionsUrl(), {
262
- method: 'POST',
258
+ const response = await authorizedFetch(`${getAPIFunctionsUrl()}/admin/firestore?path=meta/stats`, {
259
+ method: 'GET',
263
260
  timeout: 60000,
264
261
  response: 'json',
265
262
  tries: 2,
266
263
  log: true,
267
- body: {
268
- command: 'admin:firestore-read',
269
- payload: {
270
- path: 'meta/stats',
271
- },
272
- },
273
264
  });
274
265
 
275
266
  // Extract user count from response
@@ -477,9 +468,7 @@ function clearAutoSubmitCountdown() {
477
468
 
478
469
  // Get API functions URL
479
470
  function getAPIFunctionsUrl() {
480
- // Get functions URL from webManager and add the endpoint
481
- //@TODO remove this when fixed api url
482
- return `${webManager.getFunctionsUrl('prod')}/bm_api`;
471
+ return `${webManager.getApiUrl()}/backend-manager`;
483
472
  }
484
473
 
485
474
  // Tracking functions
@@ -196,17 +196,14 @@ function setupMobileEmailForms() {
196
196
  console.log('Mobile email form submitted:', { platform, email: data.email });
197
197
 
198
198
  // Get API endpoint
199
- const apiEndpoint = `${webManager.getApiUrl()}/backend-manager`;
199
+ const apiEndpoint = `${webManager.getApiUrl()}/backend-manager/general/email`;
200
200
 
201
201
  // Send request using wonderful-fetch
202
202
  await fetch(apiEndpoint, {
203
203
  method: 'POST',
204
204
  body: {
205
- command: 'general:send-email',
206
- payload: {
207
- id: 'general:download-app-link',
208
- email: data.email,
209
- },
205
+ id: 'general:download-app-link',
206
+ email: data.email,
210
207
  },
211
208
  response: 'json',
212
209
  timeout: 30000,
@@ -100,16 +100,16 @@ async function handleOAuthCallback() {
100
100
 
101
101
  console.log('Tokenize payload:', payload);
102
102
 
103
+ // Build the RESTful URL (replace /backend-manager with /backend-manager/user/oauth2)
104
+ const serverUrl = stateParsed.serverUrl.replace(/\/backend-manager\/?$/, '/backend-manager/user/oauth2');
105
+
103
106
  // Call server to complete OAuth flow
104
- const response = await authorizedFetch(stateParsed.serverUrl, {
107
+ const response = await authorizedFetch(serverUrl, {
105
108
  method: 'POST',
106
109
  timeout: 60000,
107
110
  response: 'json',
108
111
  tries: 2,
109
- body: {
110
- command: 'user:oauth2',
111
- payload: payload
112
- }
112
+ body: payload,
113
113
  });
114
114
 
115
115
  console.log('Tokenize response:', response);
@@ -44,7 +44,7 @@ export function buildPaymentIntentData(webManager) {
44
44
  }
45
45
 
46
46
  // Get UTM parameters from storage
47
- const utmData = webManager.storage().get('marketing.utm');
47
+ const utmData = webManager.storage().get('attribution.utm');
48
48
  let utm = {};
49
49
 
50
50
  // Check if stored UTM data exists and is less than 30 days old
@@ -4,6 +4,7 @@
4
4
 
5
5
  // Libraries
6
6
  import { FormManager } from '__main_assets__/js/libs/form-manager.js';
7
+ import fetch from 'wonderful-fetch';
7
8
 
8
9
  let webManager = null;
9
10
 
@@ -21,6 +22,7 @@ export default (Manager) => {
21
22
  initializeSubscribeForm();
22
23
  initializeTooltips();
23
24
  initializeRefreshTimer();
25
+ initializeBuildInfo();
24
26
 
25
27
  // Fetch status data (if configured)
26
28
  fetchStatusData();
@@ -41,6 +43,14 @@ const config = {
41
43
  incidentsList: '#incidents-list',
42
44
  incidentsEmpty: '#incidents-empty',
43
45
  subscribeForm: '#status-subscribe-form',
46
+ buildInfoLoading: '#build-info-loading',
47
+ buildInfoContent: '#build-info-content',
48
+ buildInfoError: '#build-info-error',
49
+ buildTime: '#build-time',
50
+ buildTimeAgo: '#build-time-ago',
51
+ buildEnvironment: '#build-environment',
52
+ buildPackages: '#build-packages',
53
+ buildRepo: '#build-repo',
44
54
  },
45
55
  // Maps status levels to Bootstrap bg-* classes
46
56
  statusClasses: {
@@ -234,6 +244,148 @@ function initializeRefreshTimer() {
234
244
  }, 1000);
235
245
  }
236
246
 
247
+ // Initialize build info section
248
+ async function initializeBuildInfo() {
249
+ const $loading = document.querySelector(config.selectors.buildInfoLoading);
250
+ const $content = document.querySelector(config.selectors.buildInfoContent);
251
+ const $error = document.querySelector(config.selectors.buildInfoError);
252
+
253
+ if (!$loading || !$content) {
254
+ return;
255
+ }
256
+
257
+ try {
258
+ // Fetch build.json
259
+ const data = await fetch(`${window.location.origin}/build.json`, {
260
+ response: 'json',
261
+ });
262
+
263
+ console.log('Build info data:', data);
264
+
265
+ // Normalize the data structure
266
+ const buildData = {
267
+ timestamp: data.timestamp || null,
268
+ environment: data.environment || (data.serving ? 'development' : 'production'),
269
+ packages: data.packages || null,
270
+ repo: data.repo || null,
271
+ };
272
+
273
+ // Test older dates
274
+ // buildData.timestamp = Date.now() - 1000 * 60 * 60 * 24 * 7;
275
+
276
+ displayBuildInfo(buildData);
277
+ $loading.classList.add('d-none');
278
+ $content.classList.remove('d-none');
279
+ } catch (error) {
280
+ console.error('Failed to load build info:', error);
281
+ $loading.classList.add('d-none');
282
+ if ($error) {
283
+ $error.classList.remove('d-none');
284
+ }
285
+ }
286
+ }
287
+
288
+ // Display build information
289
+ function displayBuildInfo(data) {
290
+ const $time = document.querySelector(config.selectors.buildTime);
291
+ const $timeAgo = document.querySelector(config.selectors.buildTimeAgo);
292
+ const $environment = document.querySelector(config.selectors.buildEnvironment);
293
+ const $packages = document.querySelector(config.selectors.buildPackages);
294
+ const $repo = document.querySelector(config.selectors.buildRepo);
295
+
296
+ // Build time
297
+ if ($time && data.timestamp) {
298
+ const buildDate = new Date(data.timestamp);
299
+ $time.textContent = buildDate.toLocaleString('en-US', {
300
+ weekday: 'short',
301
+ year: 'numeric',
302
+ month: 'short',
303
+ day: 'numeric',
304
+ hour: 'numeric',
305
+ minute: '2-digit',
306
+ hour12: true,
307
+ });
308
+ }
309
+
310
+ // Time ago (and keep it updated every second)
311
+ if ($timeAgo && data.timestamp) {
312
+ updateTimeAgo($timeAgo, data.timestamp);
313
+ setInterval(() => updateTimeAgo($timeAgo, data.timestamp), 1000);
314
+ }
315
+
316
+ // Environment
317
+ if ($environment && data.environment) {
318
+ const envText = data.environment.charAt(0).toUpperCase() + data.environment.slice(1);
319
+ const envClass = data.environment === 'production' ? 'text-success' : 'text-warning';
320
+ $environment.innerHTML = `<span class="${envClass}">${escapeHtml(envText)}</span>`;
321
+ }
322
+
323
+ // Packages
324
+ if ($packages && data.packages) {
325
+ const packageBadges = Object.entries(data.packages)
326
+ .map(([name, version]) => {
327
+ return `<span class="badge bg-body-secondary text-body fw-normal">${escapeHtml(name)}: <span class="fw-semibold">${escapeHtml(version)}</span></span>`;
328
+ })
329
+ .join('');
330
+ $packages.innerHTML = packageBadges;
331
+ }
332
+
333
+ // Repository
334
+ if ($repo && data.repo) {
335
+ const repoUrl = `https://github.com/${data.repo.user}/${data.repo.name}`;
336
+ $repo.innerHTML = `<a href="${repoUrl}" target="_blank" rel="noopener noreferrer" class="text-decoration-none">${escapeHtml(data.repo.user)}/${escapeHtml(data.repo.name)}</a>`;
337
+ }
338
+ }
339
+
340
+ // Update relative time display
341
+ function updateTimeAgo($element, timestamp) {
342
+ const buildDate = new Date(timestamp);
343
+ const now = new Date();
344
+ const diffMs = now - buildDate;
345
+ const totalSeconds = Math.floor(diffMs / 1000);
346
+
347
+ if (totalSeconds <= 0) {
348
+ $element.textContent = 'Just now';
349
+ return;
350
+ }
351
+
352
+ // Calculate each unit
353
+ const years = Math.floor(totalSeconds / (365 * 24 * 60 * 60));
354
+ const months = Math.floor((totalSeconds % (365 * 24 * 60 * 60)) / (30 * 24 * 60 * 60));
355
+ const days = Math.floor((totalSeconds % (30 * 24 * 60 * 60)) / (24 * 60 * 60));
356
+ const hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
357
+ const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
358
+ const seconds = totalSeconds % 60;
359
+
360
+ // Build parts - once we start, include all remaining units
361
+ const parts = [];
362
+ let started = false;
363
+
364
+ if (years > 0) {
365
+ parts.push(`${years}y`);
366
+ started = true;
367
+ }
368
+ if (started || months > 0) {
369
+ parts.push(`${months}mo`);
370
+ started = true;
371
+ }
372
+ if (started || days > 0) {
373
+ parts.push(`${days}d`);
374
+ started = true;
375
+ }
376
+ if (started || hours > 0) {
377
+ parts.push(`${hours}h`);
378
+ started = true;
379
+ }
380
+ if (started || minutes > 0) {
381
+ parts.push(`${minutes}m`);
382
+ started = true;
383
+ }
384
+ parts.push(`${seconds}s`);
385
+
386
+ $element.textContent = parts.join(' ') + ' ago';
387
+ }
388
+
237
389
  // Fetch status data from API (if configured)
238
390
  function fetchStatusData() {
239
391
  // Check if there's a status API endpoint configured
@@ -68,17 +68,13 @@ export default function (Manager) {
68
68
 
69
69
  // Generate custom token via backend-manager API
70
70
  async function generateCustomToken(webManager) {
71
- const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
71
+ const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/token`;
72
72
 
73
73
  const response = await authorizedFetch(serverApiURL, {
74
74
  method: 'POST',
75
75
  timeout: 60000,
76
76
  response: 'json',
77
77
  tries: 2,
78
- body: {
79
- command: 'user:create-custom-token',
80
- payload: {},
81
- },
82
78
  });
83
79
 
84
80
  // Extract token from response