@abtnode/blocklet-services 1.16.15-beta-04d1e821 → 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
  };
@@ -58,6 +58,12 @@ function checkUserEnable(user, { locale }) {
58
58
  }
59
59
  }
60
60
 
61
+ /**
62
+ * 组装用户 profile
63
+ * @param {{ avatar?: string, fullName: string, email: string}} profile
64
+ * @param {{node: any; req: any; teamDid: string; isCreate: boolean = false}} options
65
+ * @returns {Promise<{avatar: string, fullName: string, email: string}>}
66
+ */
61
67
  async function composeProfileData({ avatar, fullName, email }, { node, req, teamDid, isCreate = false }) {
62
68
  const profile = {};
63
69
  let avatarLocal;
@@ -89,7 +95,10 @@ async function composeProfileData({ avatar, fullName, email }, { node, req, team
89
95
  return profile;
90
96
  }
91
97
 
92
- async function loginWallet({ did, pk, avatar, email, fullName }, { node, req, locale, componentId, teamDid }) {
98
+ async function loginWallet(
99
+ { did, pk, avatar, email, fullName },
100
+ { node, req, locale, componentId, teamDid, updateInfo = true }
101
+ ) {
93
102
  const provider = LOGIN_PROVIDER.WALLET;
94
103
  const { error } = loginWalletSchema.validate({
95
104
  provider,
@@ -113,10 +122,12 @@ async function loginWallet({ did, pk, avatar, email, fullName }, { node, req, lo
113
122
  },
114
123
  });
115
124
 
116
- let profile;
125
+ let profile = {};
117
126
  if (currentUser) {
118
- await checkUserEnable(currentUser, { locale });
119
- profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid });
127
+ if (updateInfo) {
128
+ await checkUserEnable(currentUser, { locale });
129
+ profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid });
130
+ }
120
131
  } else {
121
132
  await checkNeedInvite({ req, node, teamDid, componentId, locale });
122
133
  profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid, isCreate: true });
@@ -145,7 +156,7 @@ async function loginWallet({ did, pk, avatar, email, fullName }, { node, req, lo
145
156
 
146
157
  async function loginOAuth(
147
158
  { provider, id, avatar, email, fullName },
148
- { node, req, locale, componentId, teamDid, blockletWallet }
159
+ { node, req, locale, componentId, teamDid, blockletWallet, updateInfo = true }
149
160
  ) {
150
161
  const { error } = loginOAuthSchema.validate({
151
162
  provider,
@@ -171,10 +182,12 @@ async function loginOAuth(
171
182
  },
172
183
  });
173
184
 
174
- let profile;
185
+ let profile = {};
175
186
  if (currentUser) {
176
- await checkUserEnable(currentUser, { locale });
177
- profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid });
187
+ if (updateInfo) {
188
+ await checkUserEnable(currentUser, { locale });
189
+ profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid });
190
+ }
178
191
  } else {
179
192
  await checkNeedInvite({ req, node, teamDid, componentId, locale });
180
193
  profile = await composeProfileData({ avatar, email, fullName }, { node, req, teamDid, isCreate: true });
@@ -201,7 +214,17 @@ async function loginOAuth(
201
214
  }
202
215
 
203
216
  async function login(req, node, options) {
204
- const { provider = LOGIN_PROVIDER.WALLET, did, pk, avatar, email, fullName, id, locale = 'en' } = req.body;
217
+ const {
218
+ provider = LOGIN_PROVIDER.WALLET,
219
+ did,
220
+ pk,
221
+ avatar,
222
+ email,
223
+ fullName,
224
+ id,
225
+ locale = 'en',
226
+ updateInfo = true,
227
+ } = req.body;
205
228
 
206
229
  const componentId = req.get('x-blocklet-component-id');
207
230
  if (!componentId) {
@@ -212,11 +235,14 @@ async function login(req, node, options) {
212
235
 
213
236
  let doc;
214
237
  if (provider === LOGIN_PROVIDER.WALLET) {
215
- doc = await loginWallet({ did, pk, avatar, email, fullName }, { node, req, locale, componentId, teamDid });
238
+ doc = await loginWallet(
239
+ { did, pk, avatar, email, fullName },
240
+ { node, req, locale, componentId, teamDid, updateInfo }
241
+ );
216
242
  } else {
217
243
  doc = await loginOAuth(
218
244
  { provider, id, avatar, email, fullName },
219
- { node, req, locale, componentId, teamDid, blockletWallet }
245
+ { node, req, locale, componentId, teamDid, blockletWallet, updateInfo }
220
246
  );
221
247
  }
222
248
 
@@ -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>