@abtnode/blocklet-services 1.16.15-beta-e3a24907 → 1.16.15-beta-933eb977

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/api/index.js CHANGED
@@ -19,6 +19,7 @@ const eventHub =
19
19
  process.env.NODE_ENV === 'test' ? require('@arcblock/event-hub/single') : require('@arcblock/event-hub');
20
20
  const logger = require('@abtnode/logger')(require('../package.json').name);
21
21
 
22
+ require('./libs/fetch');
22
23
  const cache = require('./cache');
23
24
  const { ensureProxyUrl } = require('./util');
24
25
  const { isProduction, isE2E } = require('./libs/env');
@@ -252,7 +253,7 @@ module.exports = function createServer(node, serverOptions = {}) {
252
253
  createFederatedRoutes.init(server, node, options);
253
254
  createUserRoutes.init(server, node, options);
254
255
  createEnvRoutes.init(server, node, options);
255
- createBlockletRoutes.init(server, node);
256
+ createBlockletRoutes.init(server, node, eventHub);
256
257
  createConnectSessionRoutes.init(server, node, options);
257
258
  createConnectRelayRoutes.init(server, node, options, wsRouter);
258
259
  authRoutes.attachDidAuthHandlers(server);
@@ -0,0 +1,11 @@
1
+ // This polyfill is required for open-graph generator to work
2
+ const fetch = require('node-fetch');
3
+
4
+ const { Headers, Request, Response } = fetch;
5
+
6
+ if (!global.fetch) {
7
+ global.fetch = fetch;
8
+ global.Headers = Headers;
9
+ global.Request = Request;
10
+ global.Response = Response;
11
+ }
package/api/libs/image.js CHANGED
@@ -3,11 +3,14 @@
3
3
  const fs = require('fs-extra');
4
4
  const path = require('path');
5
5
  const sharp = require('sharp');
6
+ const joinUrl = require('url-join');
6
7
  const toLower = require('lodash/toLower');
7
8
  const { Joi } = require('@arcblock/validator');
8
9
  const stringify = require('json-stable-stringify');
9
10
  const md5 = require('@abtnode/util/lib/md5');
10
11
  const formatError = require('@abtnode/util/lib/format-error');
12
+ const { getPassportColor } = require('@abtnode/auth/lib/util/create-passport-svg');
13
+ const { WELLKNOWN_SERVICE_PATH_PREFIX } = require('@abtnode/constant');
11
14
 
12
15
  const logger = require('@abtnode/logger')('@abtnode/blocklet-services/image');
13
16
 
@@ -243,11 +246,69 @@ const processImage = (src, extension, dest, params) => {
243
246
  });
244
247
  };
245
248
 
249
+ const fallbackFont = fs.readFile(path.resolve(__dirname, '../fonts/noto-sans-sc-regular.otf'));
250
+ const generateOgImage = async (info, format = 'png', width = 1200, height = 630) => {
251
+ const { default: satori } = await import('satori'); // eslint-disable-line import/no-unresolved
252
+ const { html } = await import('satori-html'); // eslint-disable-line import/no-unresolved
253
+ const styles = {
254
+ container: 'display: flex; flex-direction: column; justify-content: center; align-items: center;',
255
+ font: 'color: #FFF; font-weight: 400; font-family: Arial,sans-serif; text-align: center; text-transform: capitalize; padding: 0 24px;',
256
+ };
257
+ const color = getPassportColor(info.passportColor, info.did);
258
+ // Do not delete following code block, it's used to debug the og image, please request with `/blocklet/og.html?nocache=1`
259
+ // const markup = `<div style="width: ${width}px; height: ${height}px; background: ${color.start}; ${styles.container}">
260
+ // <img
261
+ // src="${joinUrl(info.appUrl, WELLKNOWN_SERVICE_PATH_PREFIX, '/blocklet/logo')}"
262
+ // width="180"
263
+ // height="180"
264
+ // style="width: 180px; height: 180px; border-radius: 50%"
265
+ // />
266
+ // <h2 style="font-size: 3rem; margin: 16px 0; ${styles.font}">${info.name}</h2>
267
+ // <h3 style="font-size: 2rem; margin: 0; ${styles.font}">${info.description}</h3>
268
+ // </div>`;
269
+ // if (format === 'html') {
270
+ // return markup;
271
+ // }
272
+
273
+ const svg = await satori(
274
+ // @ts-ignore
275
+ html`<div style="width: ${width}px; height: ${height}px; background: ${color.start}; ${styles.container}">
276
+ <img
277
+ src="${joinUrl(info.appUrl, WELLKNOWN_SERVICE_PATH_PREFIX, '/blocklet/logo')}"
278
+ width="180"
279
+ height="180"
280
+ style="width: 180px; height: 180px; border-radius: 50%"
281
+ />
282
+ <h2 style="font-size: 3rem; margin: 16px 0; ${styles.font}">${info.name}</h2>
283
+ <h3 style="font-size: 2rem; margin: 0; ${styles.font}">${info.description}</h3>
284
+ </div>`,
285
+ {
286
+ width,
287
+ height,
288
+ fonts: [
289
+ {
290
+ name: 'Noto',
291
+ data: await fallbackFont,
292
+ weight: 400,
293
+ style: 'normal',
294
+ },
295
+ ],
296
+ }
297
+ );
298
+
299
+ if (format === 'svg') {
300
+ return svg;
301
+ }
302
+
303
+ return sharp(Buffer.from(svg)).png().toBuffer();
304
+ };
305
+
246
306
  module.exports = {
247
307
  isImageAccepted,
248
308
  isImageRequest,
249
309
  processAndRespond,
250
310
  processImage,
311
+ generateOgImage,
251
312
  EXTENSIONS,
252
313
  MODES,
253
314
  };
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-console */
2
- const fs = require('fs');
2
+ const fs = require('fs-extra');
3
3
  const path = require('path');
4
4
  const cloneDeep = require('lodash/cloneDeep');
5
5
  const dayjs = require('@abtnode/util/lib/dayjs');
@@ -39,10 +39,10 @@ const logger = require('@abtnode/logger')(require('../../package.json').name);
39
39
 
40
40
  const { createDownloadLogStream } = require('@abtnode/core/lib/util/log');
41
41
 
42
- const { BlockletStatus } = require('@blocklet/constant');
42
+ const { BlockletStatus, BlockletInternalEvents } = require('@blocklet/constant');
43
43
 
44
44
  const { checkAdminPermission } = require('../middlewares/check-permission');
45
- const { isImageAccepted, isImageRequest, processAndRespond } = require('../libs/image');
45
+ const { isImageAccepted, isImageRequest, processAndRespond, generateOgImage } = require('../libs/image');
46
46
 
47
47
  const polishBlocklet = (doc) => {
48
48
  const res = cloneDeep(doc);
@@ -53,7 +53,7 @@ const polishBlocklet = (doc) => {
53
53
  const prefix = WELLKNOWN_SERVICE_PATH_PREFIX;
54
54
 
55
55
  module.exports = {
56
- init(server, node) {
56
+ init(server, node, events) {
57
57
  const onSendFallbackLogo = ({ res, sendOptions }) => {
58
58
  res.sendStaticFile('/images/blocklet.png', sendOptions);
59
59
  };
@@ -114,8 +114,6 @@ module.exports = {
114
114
 
115
115
  const cacheDir = path.join(node.dataDirs.cache, 'services', 'image');
116
116
  server.get(`${prefix}${USER_AVATAR_PATH_PREFIX}/:fileName`, async (req, res) => {
117
- const sendOptions = { maxAge: '1y' };
118
-
119
117
  try {
120
118
  const blocklet = await req.getBlocklet();
121
119
  let { fileName } = req.params;
@@ -140,7 +138,7 @@ module.exports = {
140
138
  Promise.resolve([fs.createReadStream(avatarFile), path.extname(avatarFile).slice(1)])
141
139
  );
142
140
  } else {
143
- res.sendFile(avatarFile, sendOptions);
141
+ res.sendFile(avatarFile, { maxAge: '365d', immutable: true });
144
142
  }
145
143
  } catch (err) {
146
144
  logger.error('failed to send user avatar', { fileName: req.params.fileName, error: err });
@@ -465,5 +463,49 @@ module.exports = {
465
463
  ],
466
464
  });
467
465
  });
466
+
467
+ server.get(`${prefix}/blocklet/og.(png|svg|html)`, async (req, res) => {
468
+ try {
469
+ const format = req.path.split('.').pop();
470
+ const info = await req.getBlockletInfo();
471
+ const blocklet = await req.getBlocklet();
472
+ const cache = req.query.nocache !== '1';
473
+
474
+ const sourceFile = path.join(blocklet.env.dataDir, `og.${format}`);
475
+ if (!fs.existsSync(sourceFile) || !cache) {
476
+ const sourceData = await generateOgImage(info, format);
477
+ fs.writeFileSync(sourceFile, sourceData);
478
+ }
479
+
480
+ if (format === 'png' && isImageAccepted(req) && isImageRequest(req)) {
481
+ const appDir = path.join(cacheDir, blocklet.appPid);
482
+ processAndRespond(req, res, appDir, () =>
483
+ Promise.resolve([fs.createReadStream(sourceFile), path.extname(sourceFile).slice(1)])
484
+ );
485
+ } else {
486
+ res.sendFile(sourceFile, cache ? { maxAge: '365d', immutable: true } : { maxAge: 0 });
487
+ }
488
+ } catch (err) {
489
+ logger.error('failed to send open graph', { fileName: req.params.fileName, error: err });
490
+ res.status(500).send(err.message);
491
+ }
492
+ });
493
+ // Bust cache on blocklet config change
494
+ if (events) {
495
+ events.on(BlockletInternalEvents.appConfigChanged, ({ appDid }) => {
496
+ ['png', 'svg', 'html'].forEach((format) => {
497
+ const cached = path.join(node.dataDirs.data, appDid, `og.${format}`);
498
+ if (fs.existsSync(cached)) {
499
+ logger.info('bust og cache on blocklet config change', { appDid, cached });
500
+ try {
501
+ fs.unlinkSync(cached);
502
+ logger.info('bust og cache on blocklet config change', { appDid, format });
503
+ } catch (err) {
504
+ logger.error('bust og cache on blocklet config change', { appDid, format, error: err });
505
+ }
506
+ }
507
+ });
508
+ });
509
+ }
468
510
  },
469
511
  };
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "files": {
3
3
  "main.css": "/.well-known/service/static/static/css/main.7ea79dc8.css",
4
- "main.js": "/.well-known/service/static/static/js/main.1b821566.js",
4
+ "main.js": "/.well-known/service/static/static/js/main.4cc7f3ab.js",
5
5
  "static/js/4716.58477c5c.chunk.js": "/.well-known/service/static/static/js/4716.58477c5c.chunk.js",
6
6
  "static/js/6856.163537c7.chunk.js": "/.well-known/service/static/static/js/6856.163537c7.chunk.js",
7
7
  "static/js/1660.e2ff5a21.chunk.js": "/.well-known/service/static/static/js/1660.e2ff5a21.chunk.js",
8
8
  "static/js/9899.18509ac9.chunk.js": "/.well-known/service/static/static/js/9899.18509ac9.chunk.js",
9
- "static/js/6737.5aef67e7.chunk.js": "/.well-known/service/static/static/js/6737.5aef67e7.chunk.js",
9
+ "static/js/6737.6432760e.chunk.js": "/.well-known/service/static/static/js/6737.6432760e.chunk.js",
10
10
  "static/js/1760.3318c7ca.chunk.js": "/.well-known/service/static/static/js/1760.3318c7ca.chunk.js",
11
11
  "static/js/9620.4b7c9e8b.chunk.js": "/.well-known/service/static/static/js/9620.4b7c9e8b.chunk.js",
12
12
  "static/js/1480.f89236fc.chunk.js": "/.well-known/service/static/static/js/1480.f89236fc.chunk.js",
13
- "static/js/6186.2a5f97da.chunk.js": "/.well-known/service/static/static/js/6186.2a5f97da.chunk.js",
13
+ "static/js/6186.e711ab85.chunk.js": "/.well-known/service/static/static/js/6186.e711ab85.chunk.js",
14
14
  "static/js/4682.23dd054e.chunk.js": "/.well-known/service/static/static/js/4682.23dd054e.chunk.js",
15
15
  "static/js/6711.38948be4.chunk.js": "/.well-known/service/static/static/js/6711.38948be4.chunk.js",
16
16
  "static/js/8437.0d88b9db.chunk.js": "/.well-known/service/static/static/js/8437.0d88b9db.chunk.js",
@@ -98,16 +98,16 @@
98
98
  "index.html": "/.well-known/service/static/index.html",
99
99
  "static/media/space-connected.svg": "/.well-known/service/static/static/media/space-connected.9a4e18fd2bc7d065191b0d241a131c28.svg",
100
100
  "main.7ea79dc8.css.map": "/.well-known/service/static/static/css/main.7ea79dc8.css.map",
101
- "main.1b821566.js.map": "/.well-known/service/static/static/js/main.1b821566.js.map",
101
+ "main.4cc7f3ab.js.map": "/.well-known/service/static/static/js/main.4cc7f3ab.js.map",
102
102
  "4716.58477c5c.chunk.js.map": "/.well-known/service/static/static/js/4716.58477c5c.chunk.js.map",
103
103
  "6856.163537c7.chunk.js.map": "/.well-known/service/static/static/js/6856.163537c7.chunk.js.map",
104
104
  "1660.e2ff5a21.chunk.js.map": "/.well-known/service/static/static/js/1660.e2ff5a21.chunk.js.map",
105
105
  "9899.18509ac9.chunk.js.map": "/.well-known/service/static/static/js/9899.18509ac9.chunk.js.map",
106
- "6737.5aef67e7.chunk.js.map": "/.well-known/service/static/static/js/6737.5aef67e7.chunk.js.map",
106
+ "6737.6432760e.chunk.js.map": "/.well-known/service/static/static/js/6737.6432760e.chunk.js.map",
107
107
  "1760.3318c7ca.chunk.js.map": "/.well-known/service/static/static/js/1760.3318c7ca.chunk.js.map",
108
108
  "9620.4b7c9e8b.chunk.js.map": "/.well-known/service/static/static/js/9620.4b7c9e8b.chunk.js.map",
109
109
  "1480.f89236fc.chunk.js.map": "/.well-known/service/static/static/js/1480.f89236fc.chunk.js.map",
110
- "6186.2a5f97da.chunk.js.map": "/.well-known/service/static/static/js/6186.2a5f97da.chunk.js.map",
110
+ "6186.e711ab85.chunk.js.map": "/.well-known/service/static/static/js/6186.e711ab85.chunk.js.map",
111
111
  "4682.23dd054e.chunk.js.map": "/.well-known/service/static/static/js/4682.23dd054e.chunk.js.map",
112
112
  "6711.38948be4.chunk.js.map": "/.well-known/service/static/static/js/6711.38948be4.chunk.js.map",
113
113
  "8437.0d88b9db.chunk.js.map": "/.well-known/service/static/static/js/8437.0d88b9db.chunk.js.map",
@@ -174,6 +174,6 @@
174
174
  },
175
175
  "entrypoints": [
176
176
  "static/css/main.7ea79dc8.css",
177
- "static/js/main.1b821566.js"
177
+ "static/js/main.4cc7f3ab.js"
178
178
  ]
179
179
  }
package/build/index.html CHANGED
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#000000"/><title>Blocklet Service</title><link rel="manifest" href="/.well-known/service/manifest.json"/><script src="/.well-known/service/api/env"></script><script src="/__blocklet__.js"></script><script defer="defer" src="/.well-known/service/static/static/js/main.1b821566.js"></script><link href="/.well-known/service/static/static/css/main.7ea79dc8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#000000"/><title>Blocklet Service</title><link rel="manifest" href="/.well-known/service/manifest.json"/><script src="/.well-known/service/api/env"></script><script src="/__blocklet__.js"></script><script defer="defer" src="/.well-known/service/static/static/js/main.4cc7f3ab.js"></script><link href="/.well-known/service/static/static/css/main.7ea79dc8.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>