wiki-plugin-shoppe 0.0.43 → 0.0.46

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": "wiki-plugin-shoppe",
3
- "version": "0.0.43",
3
+ "version": "0.0.46",
4
4
  "description": "Multi-tenant digital goods shoppe for federated wiki, powered by Sanora",
5
5
  "keywords": [
6
6
  "wiki",
package/server/server.js CHANGED
@@ -84,10 +84,24 @@ function getSanoraUrl() {
84
84
  }
85
85
 
86
86
  function getAddieUrl() {
87
- try { return new URL(getSanoraUrl()).origin + '/plugin/allyabase/addie'; } catch { /* fall through */ }
87
+ const sanora = getSanoraUrl();
88
+ try {
89
+ const url = new URL(sanora);
90
+ // Only derive from origin when sanora is a wiki proxy URL (has a path component).
91
+ // A bare host:port URL (e.g. http://localhost:7243) means Addie is on its own port.
92
+ if (url.pathname && url.pathname !== '/') {
93
+ return url.origin + '/plugin/allyabase/addie';
94
+ }
95
+ } catch { /* fall through */ }
88
96
  return `http://localhost:${process.env.ADDIE_PORT || 3005}`;
89
97
  }
90
98
 
99
+ // Returns the public-facing Sanora URL (via wiki proxy) for browser-visible resource URLs.
100
+ // Always constructed from the current request so it matches the host the browser is using.
101
+ function getSanoraPublicUrl(req) {
102
+ return `${reqProto(req)}://${req.get('host')}/plugin/allyabase/sanora`;
103
+ }
104
+
91
105
  function getLucilleUrl() {
92
106
  const config = loadConfig();
93
107
  if (config.lucilleUrl) return config.lucilleUrl.replace(/\/$/, '');
@@ -1501,7 +1515,7 @@ async function processArchive(zipPath, onProgress = () => {}) {
1501
1515
  // PORTFOLIO PAGE GENERATION
1502
1516
  // ============================================================
1503
1517
 
1504
- async function getShoppeGoods(tenant) {
1518
+ async function getShoppeGoods(tenant, imageBaseUrl) {
1505
1519
  let products = {};
1506
1520
  try {
1507
1521
  const resp = await fetch(`${getSanoraUrl()}/products/${tenant.uuid}`, { timeout: 15000 });
@@ -1556,7 +1570,7 @@ async function getShoppeGoods(tenant) {
1556
1570
  description: product.description || '',
1557
1571
  price: product.price || 0,
1558
1572
  shipping: product.shipping || 0,
1559
- image: product.image ? `${getSanoraUrl()}/images/${product.image}` : null,
1573
+ image: product.image ? `${imageBaseUrl || getSanoraUrl()}/images/${product.image}` : null,
1560
1574
  url: resolvedUrl,
1561
1575
  ...(isPost && { category: product.category, tags: product.tags || '' }),
1562
1576
  ...(lucillePlayerUrl && { lucillePlayerUrl }),
@@ -3207,7 +3221,7 @@ async function startServer(params) {
3207
3221
  const product = products[title] || Object.values(products).find(p => p.title === title);
3208
3222
  if (!product) return res.status(404).send('<h1>Product not found</h1>');
3209
3223
 
3210
- const imageUrl = product.image ? `${sanoraUrlInternal}/images/${product.image}` : '';
3224
+ const imageUrl = product.image ? `${sanoraUrl}/images/${product.image}` : '';
3211
3225
  const ebookUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}/download/${encodeURIComponent(title)}`;
3212
3226
  const shoppeUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}`;
3213
3227
  const payees = tenant.addieKeys
@@ -3243,6 +3257,8 @@ async function startServer(params) {
3243
3257
  tenantUuid: tenant.uuid,
3244
3258
  keywords: extractKeywords(product),
3245
3259
  shopName: tenant.name || '',
3260
+ shopName_json: JSON.stringify(tenant.name || ''),
3261
+ title_json: JSON.stringify(product.title || title),
3246
3262
  category: product.category || 'book',
3247
3263
  });
3248
3264
 
@@ -3278,7 +3294,7 @@ async function startServer(params) {
3278
3294
  const schedule = await getAppointmentSchedule(tenant, product);
3279
3295
  const wikiOrigin = `${reqProto(req)}://${req.get('host')}`;
3280
3296
  const shoppeUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}`;
3281
- const imageUrl = product.image ? `${sanoraUrl}/images/${product.image}` : '';
3297
+ const imageUrl = product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : '';
3282
3298
 
3283
3299
  const price = product.price || 0;
3284
3300
  const html = fillTemplate(APPOINTMENT_BOOKING_TMPL, {
@@ -3293,7 +3309,8 @@ async function startServer(params) {
3293
3309
  proceedLabel: price === 0 ? 'Confirm Booking →' : 'Continue to Payment →',
3294
3310
  shoppeUrl,
3295
3311
  tenantUuid: tenant.uuid,
3296
- keywords: extractKeywords(product)
3312
+ keywords: extractKeywords(product),
3313
+ title_json: JSON.stringify(product.title || title),
3297
3314
  });
3298
3315
 
3299
3316
  res.set('Content-Type', 'text/html');
@@ -3346,7 +3363,7 @@ async function startServer(params) {
3346
3363
  const tierInfo = await getTierInfo(tenant, product);
3347
3364
  const wikiOrigin = `${reqProto(req)}://${req.get('host')}`;
3348
3365
  const shoppeUrl = `${wikiOrigin}/plugin/shoppe/${tenant.uuid}`;
3349
- const imageUrl = product.image ? `${sanoraUrl}/images/${product.image}` : '';
3366
+ const imageUrl = product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : '';
3350
3367
  const benefits = tierInfo && tierInfo.benefits
3351
3368
  ? tierInfo.benefits.map(b => `<li>${escHtml(b)}</li>`).join('')
3352
3369
  : '';
@@ -3362,7 +3379,8 @@ async function startServer(params) {
3362
3379
  renewalDays: String(tierInfo ? (tierInfo.renewalDays || 30) : 30),
3363
3380
  shoppeUrl,
3364
3381
  tenantUuid: tenant.uuid,
3365
- keywords: extractKeywords(product)
3382
+ keywords: extractKeywords(product),
3383
+ title_json: JSON.stringify(product.title || title),
3366
3384
  });
3367
3385
 
3368
3386
  res.set('Content-Type', 'text/html');
@@ -3527,7 +3545,7 @@ async function startServer(params) {
3527
3545
  productId: product.productId,
3528
3546
  description: product.description || '',
3529
3547
  price: product.price || 0,
3530
- image: product.image ? `${sanoraUrl}/images/${product.image}` : null,
3548
+ image: product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : null,
3531
3549
  benefits: tierInfo ? (tierInfo.benefits || []) : [],
3532
3550
  renewalDays: tierInfo ? (tierInfo.renewalDays || 30) : 30,
3533
3551
  active: status.active,
@@ -4036,14 +4054,15 @@ async function startServer(params) {
4036
4054
  if (!purchased) return res.status(403).send('<h1>No purchase found for this key</h1>');
4037
4055
  }
4038
4056
 
4039
- const imageUrl = product.image ? `${sanoraUrl}/images/${product.image}` : '';
4057
+ const sanoraPublicUrl = getSanoraPublicUrl(req);
4058
+ const imageUrl = product.image ? `${sanoraPublicUrl}/images/${product.image}` : '';
4040
4059
 
4041
4060
  // Map artifact UUIDs to download paths by extension
4042
4061
  let epubPath = '', pdfPath = '', mobiPath = '';
4043
4062
  (product.artifacts || []).forEach(artifact => {
4044
- if (artifact.includes('epub')) epubPath = `${sanoraUrl}/artifacts/${artifact}`;
4045
- if (artifact.includes('pdf')) pdfPath = `${sanoraUrl}/artifacts/${artifact}`;
4046
- if (artifact.includes('mobi')) mobiPath = `${sanoraUrl}/artifacts/${artifact}`;
4063
+ if (artifact.includes('epub')) epubPath = `${sanoraPublicUrl}/artifacts/${artifact}`;
4064
+ if (artifact.includes('pdf')) pdfPath = `${sanoraPublicUrl}/artifacts/${artifact}`;
4065
+ if (artifact.includes('mobi')) mobiPath = `${sanoraPublicUrl}/artifacts/${artifact}`;
4047
4066
  });
4048
4067
 
4049
4068
  const html = fillTemplate(EBOOK_DOWNLOAD_TMPL, {
@@ -4089,7 +4108,7 @@ async function startServer(params) {
4089
4108
  const fm = parseFrontMatter(mdContent);
4090
4109
  const postTitle = fm.title || title;
4091
4110
  const postDate = fm.date || '';
4092
- const imageUrl = product.image ? `${getSanoraUrl()}/images/${product.image}` : null;
4111
+ const imageUrl = product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : null;
4093
4112
 
4094
4113
  res.set('Content-Type', 'text/html');
4095
4114
  res.send(generatePostHTML(tenant, postTitle, postDate, imageUrl, fm.body || mdContent));
@@ -4164,7 +4183,7 @@ async function startServer(params) {
4164
4183
  productId: product.productId,
4165
4184
  title: product.title,
4166
4185
  category: product.category,
4167
- image: product.image ? `${sanoraUrl}/images/${product.image}` : null,
4186
+ image: product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : null,
4168
4187
  price: product.price,
4169
4188
  paidAt: match.paidAt,
4170
4189
  status: match.status,
@@ -4190,7 +4209,7 @@ async function startServer(params) {
4190
4209
  try {
4191
4210
  const tenant = getTenantByIdentifier(req.params.identifier);
4192
4211
  if (!tenant) return res.status(404).json({ error: 'Shoppe not found' });
4193
- const goods = await getShoppeGoods(tenant);
4212
+ const goods = await getShoppeGoods(tenant, getSanoraPublicUrl(req));
4194
4213
  const cat = req.query.category;
4195
4214
  res.json({ success: true, goods: (cat && goods[cat]) ? goods[cat] : goods });
4196
4215
  } catch (err) {
@@ -4212,7 +4231,7 @@ async function startServer(params) {
4212
4231
  const tracks = [];
4213
4232
  for (const [key, product] of Object.entries(products)) {
4214
4233
  if (product.category !== 'music') continue;
4215
- const cover = product.image ? `${sanoraUrl}/images/${product.image}` : null;
4234
+ const cover = product.image ? `${getSanoraPublicUrl(req)}/images/${product.image}` : null;
4216
4235
  const artifacts = product.artifacts || [];
4217
4236
  if (artifacts.length > 1) {
4218
4237
  albums.push({
@@ -4246,7 +4265,7 @@ async function startServer(params) {
4246
4265
  try {
4247
4266
  const tenant = getTenantByIdentifier(req.params.identifier);
4248
4267
  if (!tenant) return res.status(404).send('<h1>Shoppe not found</h1>');
4249
- const goods = await getShoppeGoods(tenant);
4268
+ const goods = await getShoppeGoods(tenant, getSanoraPublicUrl(req));
4250
4269
 
4251
4270
  // Check if the request carries a valid owner signature — if so, embed auth
4252
4271
  // params in the page so the upload button can authenticate with upload-info.
@@ -186,7 +186,7 @@
186
186
 
187
187
  const SHOPPE_URL = '{{shoppeUrl}}';
188
188
  const PRODUCT_ID = '{{productId}}';
189
- const TITLE = '{{title}}';
189
+ const TITLE = {{title_json}};
190
190
  const AMOUNT = {{amount}};
191
191
  const TIMEZONE = '{{timezone}}';
192
192
 
@@ -506,7 +506,7 @@
506
506
  headers: {'Content-Type': 'application/json'},
507
507
  body: JSON.stringify({
508
508
  productId: '{{productId}}',
509
- title: '{{title}}',
509
+ title: {{title_json}},
510
510
  ...(URL_PAYEES && { payees: URL_PAYEES })
511
511
  })
512
512
  });
@@ -548,7 +548,7 @@
548
548
  body: JSON.stringify({
549
549
  orderRef,
550
550
  productId: '{{productId}}',
551
- title: '{{title}}',
551
+ title: {{title_json}},
552
552
  amount: {{amount}},
553
553
  address: window.pendingAddress,
554
554
  paymentIntentId
@@ -193,7 +193,7 @@
193
193
  return p;
194
194
  }).filter(p => p.pubKey);
195
195
  }
196
- const URL_PAYEES = parseUrlPayees();
196
+ var URL_PAYEES = parseUrlPayees();
197
197
  </script>
198
198
 
199
199
  <!-- ── Shoppere (pubKey) path ────────────────────────────────────────────── -->
@@ -202,7 +202,7 @@
202
202
  const _buyerPubKey = '{{buyerPubKey}}' || new URLSearchParams(window.location.search).get('pubKey') || '';
203
203
  const _buyerTimestamp = '{{buyerTimestamp}}' || new URLSearchParams(window.location.search).get('timestamp') || '';
204
204
  const _buyerSignature = '{{buyerSignature}}' || new URLSearchParams(window.location.search).get('signature') || '';
205
- const IS_SHOPPERE = !!(_buyerPubKey && _buyerTimestamp && _buyerSignature);
205
+ var IS_SHOPPERE = !!(_buyerPubKey && _buyerTimestamp && _buyerSignature);
206
206
  // Affiliate (NFC proximity charge / referral link) param — present when buyer arrived via a referred link
207
207
  const _affiliatePubKey = new URLSearchParams(window.location.search).get('affiliatePubKey') || '';
208
208
  const IS_AFFILIATE_CHARGE = !!_affiliatePubKey;
@@ -227,7 +227,7 @@
227
227
  timestamp: _buyerTimestamp,
228
228
  signature: _buyerSignature,
229
229
  productId: '{{productId}}',
230
- title: '{{title}}',
230
+ title: {{title_json}},
231
231
  ...(URL_PAYEES && { payees: URL_PAYEES }),
232
232
  ...(IS_AFFILIATE_CHARGE && { affiliatePubKey: _affiliatePubKey })
233
233
  })
@@ -294,7 +294,7 @@
294
294
  timestamp: _buyerTimestamp,
295
295
  signature: _buyerSignature,
296
296
  productId: '{{productId}}',
297
- title: '{{title}}',
297
+ title: {{title_json}},
298
298
  ...(paymentIntentId && { paymentIntentId })
299
299
  })
300
300
  });
@@ -329,8 +329,8 @@
329
329
  const deepLink = 'shoppere://buy?' + new URLSearchParams({
330
330
  domain: window.location.hostname,
331
331
  shopId: '{{tenantUuid}}',
332
- shopName: '{{shopName}}',
333
- productTitle: '{{title}}',
332
+ shopName: {{shopName_json}},
333
+ productTitle: {{title_json}},
334
334
  productId: '{{productId}}',
335
335
  price: '{{amount}}',
336
336
  category: '{{category}}',
@@ -553,7 +553,7 @@
553
553
  body: JSON.stringify({
554
554
  recoveryKey,
555
555
  productId: '{{productId}}',
556
- title: '{{title}}',
556
+ title: {{title_json}},
557
557
  ...(URL_PAYEES && { payees: URL_PAYEES })
558
558
  })
559
559
  });
@@ -147,7 +147,7 @@
147
147
 
148
148
  const SHOPPE_URL = '{{shoppeUrl}}';
149
149
  const PRODUCT_ID = '{{productId}}';
150
- const TITLE = '{{title}}';
150
+ const TITLE = {{title_json}};
151
151
  const AMOUNT = {{amount}};
152
152
  const RENEWAL_DAYS = {{renewalDays}};
153
153