ultimate-jekyll-manager 0.0.198 → 0.0.200

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.
Files changed (29) hide show
  1. package/.claude/settings.local.json +3 -2
  2. package/README.md +0 -2
  3. package/TODO-ATTRIBUTION2.md +753 -0
  4. package/bin/ultimate-jekyll +1 -1
  5. package/dist/assets/js/core/auth.js +37 -32
  6. package/dist/assets/js/core/exit-popup.js +3 -0
  7. package/dist/assets/js/core/query-strings.js +16 -12
  8. package/dist/assets/js/libs/auth.js +12 -0
  9. package/dist/assets/js/libs/form-manager.js +1 -1
  10. package/dist/assets/js/pages/account/sections/api-keys.js +1 -4
  11. package/dist/assets/js/pages/account/sections/connections.js +11 -17
  12. package/dist/assets/js/pages/account/sections/delete.js +5 -15
  13. package/dist/assets/js/pages/account/sections/security.js +2 -6
  14. package/dist/assets/js/pages/admin/notifications/new/index.js +5 -16
  15. package/dist/assets/js/pages/download/index.js +3 -6
  16. package/dist/assets/js/pages/oauth2/index.js +5 -5
  17. package/dist/assets/js/pages/payment/checkout/modules/session.js +1 -1
  18. package/dist/assets/js/pages/token/index.js +1 -5
  19. package/dist/assets/themes/bootstrap/overrides/_avatars.scss +5 -1
  20. package/dist/assets/themes/bootstrap/overrides/_buttons-adaptive.scss +68 -55
  21. package/dist/assets/themes/bootstrap/overrides/_color-shades.scss +25 -0
  22. package/dist/assets/themes/bootstrap/overrides/_index.scss +1 -0
  23. package/dist/assets/themes/classy/css/components/_buttons.scss +1 -2
  24. package/dist/build.js +1 -1
  25. package/dist/defaults/dist/pages/test/libraries/bootstrap.html +7 -0
  26. package/dist/defaults/src/_config.yml +10 -6
  27. package/dist/gulp/tasks/defaults.js +1 -1
  28. package/dist/gulp/tasks/sass.js +4 -0
  29. package/package.json +13 -13
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const argv = require('yargs').argv;
2
+ const argv = require('yargs')(process.argv.slice(2)).parseSync();
3
3
  const cli = new (require('../dist/cli.js'))(argv);
4
4
  (async function() {
5
5
  'use strict';
@@ -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,61 @@ 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
+ // Skip on auth pages to avoid blocking redirect (metadata will be sent on destination page)
160
+ const pagePath = document.documentElement.getAttribute('data-page-path');
161
+ const authPages = ['/signin', '/signup', '/reset'];
162
+ if (authPages.includes(pagePath)) {
163
+ return;
164
+ }
174
165
 
175
- // Only proceed if we haven't sent signup metadata yet
176
- if (signupSent) {
166
+ // Check if this is a new user account (created in last X minutes)
167
+ const accountAge = Date.now() - new Date(user.metadata.creationTime).getTime();
168
+ const signupProcessed = webManager.storage().get('flags.signupProcessed', null) === user.uid;
169
+
170
+ /* @dev-only:start */
171
+ {
172
+ // Log account age for debugging
173
+ const ageInMinutes = Math.floor(accountAge / 1000 / 60);
174
+ console.log('[Auth] Account age:', ageInMinutes, 'minutes, signupProcessed:', signupProcessed);
175
+ }
176
+ /* @dev-only:end */
177
+
178
+ // Only proceed if account is new and we haven't sent signup metadata yet
179
+ if (accountAge >= SIGNUP_MAX_AGE || signupProcessed) {
177
180
  return;
178
181
  }
179
182
 
183
+ // Get attribution data from storage
184
+ const attribution = webManager.storage().get('attribution', {});
185
+
180
186
  // Build the payload
181
187
  const payload = {
182
- // @TODO: REMOVE ONCE LEGACY SERVER CODE IS GONE
183
- affiliateCode: affiliateData?.code || '',
188
+ // Legacy support (can be removed once all servers are updated)
189
+ affiliateCode: attribution.affiliate?.code || '',
184
190
 
185
191
  // New structure
186
- affiliateData: affiliateData || {},
187
- utmData: utmData || {},
192
+ attribution: attribution,
188
193
  context: webManager.utilities().getContext(),
189
194
  };
190
195
 
191
196
  // Get server API URL
192
- const serverApiURL = `${webManager.getApiUrl()}/backend-manager`;
197
+ const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/signup`;
198
+
199
+ // Log
200
+ console.log('[Auth] Sending user metadata:', payload);
193
201
 
194
202
  // Make API call to send signup metadata
195
203
  const response = await authorizedFetch(serverApiURL, {
196
204
  method: 'POST',
197
205
  response: 'json',
198
- body: {
199
- command: 'user:sign-up',
200
- payload: payload
201
- }
206
+ body: payload,
202
207
  });
203
208
 
204
209
  // Log
205
210
  console.log('[Auth] User metadata sent successfully:', response);
206
211
 
207
- // Mark signup as sent (keep the marketing data for reference)
208
- webManager.storage().set('marketing.signupSent', true);
212
+ // Mark signup as sent for this user (keep the attribution data for reference)
213
+ webManager.storage().set('flags.signupProcessed', user.uid);
209
214
  } catch (error) {
210
215
  console.error('[Auth] Error sending user metadata:', error);
211
216
  // Don't throw - we don't want to block the signup flow
@@ -71,6 +71,9 @@ export default function (Manager, options) {
71
71
  url.searchParams.set('itm_campaign', 'exit-popup');
72
72
  url.searchParams.set('itm_content', window.location.pathname);
73
73
  $button.href = url.toString();
74
+
75
+ // Remove data-bs-dismiss so the link navigation works properly
76
+ $button.removeAttribute('data-bs-dismiss');
74
77
  }
75
78
  }
76
79
 
@@ -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
  }
@@ -13,6 +13,9 @@ export default function (Manager) {
13
13
  const url = new URL(window.location.href);
14
14
  const useAuthPopup = url.searchParams.get('authPopup') === 'true' || window !== window.top;
15
15
 
16
+ // Fire wakeup call immediately to prevent cold start on signup API call (fire-and-forget)
17
+ // wakeupServer(webManager);
18
+
16
19
  // Handle DOM ready
17
20
  webManager.dom().ready()
18
21
  .then(async () => {
@@ -653,4 +656,13 @@ export default function (Manager) {
653
656
  content_name: 'Password Reset'
654
657
  });
655
658
  }
659
+
660
+ // Wakeup server to prevent cold start on signup API call
661
+ function wakeupServer(webManager) {
662
+ const serverApiURL = `${webManager.getApiUrl()}/backend-manager/user/signup?wakeup=true`;
663
+
664
+ fetch(serverApiURL, { method: 'POST' })
665
+ .then(() => console.log('[Auth] Server wakeup sent'))
666
+ .catch(() => {}); // Silently ignore errors
667
+ }
656
668
  }
@@ -156,7 +156,7 @@ export class FormManager {
156
156
 
157
157
  // Focus the field with autofocus attribute if it exists (desktop only)
158
158
  const $autofocusField = this.$form.querySelector('[autofocus]');
159
- if ($autofocusField && !$autofocusField.disabled && window.Manager?.webManager?.utilities()?.getDeviceType() === 'desktop') {
159
+ if ($autofocusField && !$autofocusField.disabled && window.Manager?.webManager?.utilities()?.getDevice() === 'desktop') {
160
160
  this._focusField($autofocusField);
161
161
  }
162
162
  }
@@ -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
 
@@ -62,25 +62,15 @@ function setupDeleteAccountForm() {
62
62
  throw new Error('Account deletion cancelled. You must type "DELETE" exactly to confirm.');
63
63
  }
64
64
 
65
- // Final confirmation
66
- const finalConfirm = confirm('This is your last chance to cancel.\n\nAre you sure you want to permanently delete your account?');
67
-
68
- if (!finalConfirm) {
69
- throw new Error('Account deletion cancelled.');
70
- }
71
-
72
65
  // Send delete request to server
73
- const response = await authorizedFetch(webManager.getApiUrl(), {
74
- method: 'POST',
66
+ const response = await authorizedFetch(`${webManager.getApiUrl()}/backend-manager/user`, {
67
+ method: 'DELETE',
75
68
  timeout: 30000,
76
69
  response: 'json',
77
70
  tries: 2,
78
71
  body: {
79
- command: 'user:delete',
80
- payload: {
81
- reason: data.reason || '',
82
- confirmed: true,
83
- },
72
+ reason: data.reason || '',
73
+ confirmed: true,
84
74
  },
85
75
  });
86
76
 
@@ -111,4 +101,4 @@ export async function loadData() {
111
101
  // Called when section is shown
112
102
  export function onShow() {
113
103
  // Nothing needed
114
- }
104
+ }
@@ -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
@@ -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
@@ -9,12 +9,16 @@
9
9
  // ============================================
10
10
  $avatar-sizes: (
11
11
  null: 3rem, // default
12
+ 2xs: 0.5rem,
12
13
  xs: 1.5rem,
13
14
  sm: 2rem,
14
15
  md: 2.5rem,
15
16
  lg: 3.5rem,
16
17
  xl: 5rem,
17
- xxl: 7.5rem
18
+ 2xl: 7.5rem,
19
+ 3xl: 10rem,
20
+ 4xl: 12.5rem,
21
+ 5xl: 15rem
18
22
  ) !default;
19
23
 
20
24
  // ============================================