@xyo-network/image-thumbnail-plugin 2.70.7 → 2.70.9

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,12 +1,17 @@
1
1
  import { axios } from '@xyo-network/axios';
2
2
  import { PayloadHasher } from '@xyo-network/core';
3
3
  import { ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin';
4
+ import { isPayload, ModuleErrorSchema } from '@xyo-network/payload-model';
4
5
  import { AbstractWitness } from '@xyo-network/witness';
5
6
  import { subClass } from 'gm';
6
7
  import { sha256 } from 'hash-wasm';
7
8
  import compact from 'lodash/compact';
9
+ import isBuffer from 'lodash/isBuffer';
10
+ import { LRUCache } from 'lru-cache';
8
11
  import shajs from 'sha.js';
12
+ import Url from 'url-parse';
9
13
  import { ImageThumbnailWitnessConfigSchema } from './Config';
14
+ //TODO: Break this into two Witnesses?
10
15
  const gm = subClass({ imageMagick: '7+' });
11
16
  export const binaryToSha256 = async (data) => {
12
17
  await PayloadHasher.wasmInitialized;
@@ -21,22 +26,149 @@ export const binaryToSha256 = async (data) => {
21
26
  // eslint-disable-next-line deprecation/deprecation
22
27
  return shajs('sha256').update(data).digest().toString();
23
28
  };
29
+ const checkIpfsUrl = (urlToCheck) => {
30
+ const url = new Url(urlToCheck);
31
+ let protocol = url.protocol;
32
+ let host = url.host;
33
+ let path = url.pathname;
34
+ const query = url.query;
35
+ if (protocol === 'ipfs:') {
36
+ protocol = 'https:';
37
+ host = 'cloudflare-ipfs.com';
38
+ path = url.host === 'ipfs' ? `ipfs${path}` : `ipfs/${url.host}${path}`;
39
+ const root = `${protocol}//${host}/${path}`;
40
+ return query?.length > 0 ? `root?${query}` : root;
41
+ }
42
+ else {
43
+ return urlToCheck;
44
+ }
45
+ };
46
+ const checkDataUrl = (url) => {
47
+ if (url.startsWith('data:image')) {
48
+ const data = url.split(',')[1];
49
+ if (data) {
50
+ const buffer = Buffer.from(Uint8Array.from(atob(data), (c) => c.charCodeAt(0)));
51
+ console.log(`data buffer: ${buffer.length}`);
52
+ return [buffer, undefined];
53
+ }
54
+ else {
55
+ const error = {
56
+ message: 'Invalid data Url',
57
+ schema: ModuleErrorSchema,
58
+ url,
59
+ };
60
+ return [undefined, error];
61
+ }
62
+ }
63
+ else {
64
+ return [undefined, undefined];
65
+ }
66
+ };
24
67
  export class ImageThumbnailWitness extends AbstractWitness {
25
68
  static configSchemas = [ImageThumbnailWitnessConfigSchema];
69
+ _cache;
70
+ get cache() {
71
+ this._cache = this._cache ?? new LRUCache({ max: this.maxCacheEntries });
72
+ return this._cache;
73
+ }
74
+ get encoding() {
75
+ return this.config.encoding ?? 'PNG';
76
+ }
77
+ get height() {
78
+ return this.config.height ?? 128;
79
+ }
80
+ get maxCacheEntries() {
81
+ return this.config.maxCacheEntries ?? 5000;
82
+ }
83
+ get quality() {
84
+ return this.config.quality ?? 50;
85
+ }
86
+ get width() {
87
+ return this.config.width ?? 128;
88
+ }
26
89
  async observeHandler(payloads = []) {
27
- return compact(await Promise.all(payloads.map(async ({ url }) => {
90
+ const responsePairs = compact(await Promise.all(payloads.map(async ({ url }) => {
91
+ const cachedResult = this.cache.get(url);
92
+ if (cachedResult) {
93
+ return [url, cachedResult];
94
+ }
95
+ //if it is a data URL, return a Buffer
96
+ const [dataBuffer, dataError] = checkDataUrl(url);
97
+ if (dataBuffer) {
98
+ return [url, dataBuffer];
99
+ }
100
+ if (dataError) {
101
+ return [url, dataError];
102
+ }
28
103
  //if it is ipfs, go through cloud flair
29
- const mutatedUrl = url.replace('ipfs://', 'https://cloudflare-ipfs.com/');
30
- const response = await axios.get(mutatedUrl, {
31
- responseType: 'arraybuffer',
32
- });
33
- if (response.status >= 200 && response.status < 300) {
34
- const bytes = Buffer.from(response.data, 'binary');
104
+ const mutatedUrl = checkIpfsUrl(url);
105
+ try {
106
+ return [
107
+ url,
108
+ await axios.get(mutatedUrl, {
109
+ responseType: 'arraybuffer',
110
+ }),
111
+ ];
112
+ }
113
+ catch (ex) {
114
+ const axiosError = ex;
115
+ if (axiosError.isAxiosError) {
116
+ //selectively pick fields from AxiosError
117
+ const errorPayload = {
118
+ code: axiosError.code,
119
+ message: axiosError.message,
120
+ schema: ModuleErrorSchema,
121
+ status: axiosError.status,
122
+ url,
123
+ };
124
+ return [url, errorPayload];
125
+ }
126
+ else {
127
+ throw ex;
128
+ }
129
+ }
130
+ })));
131
+ return compact(await Promise.all(responsePairs.map(async ([url, urlResult]) => {
132
+ if (isPayload(urlResult)) {
133
+ this.cache.set(url, urlResult);
134
+ return urlResult;
135
+ }
136
+ let sourceBuffer;
137
+ if (isBuffer(urlResult)) {
138
+ sourceBuffer = urlResult;
139
+ }
140
+ else {
141
+ const response = urlResult;
142
+ if (response.status >= 200 && response.status < 300) {
143
+ const contentType = response.headers['content-type']?.toString();
144
+ if (contentType.split('/')[0] !== 'image') {
145
+ const error = {
146
+ message: `Invalid file type: ${contentType}`,
147
+ schema: ModuleErrorSchema,
148
+ url,
149
+ };
150
+ this.cache.set(url, error);
151
+ return error;
152
+ }
153
+ sourceBuffer = Buffer.from(response.data, 'binary');
154
+ }
155
+ else {
156
+ const error = {
157
+ schema: ModuleErrorSchema,
158
+ status: response.status,
159
+ url,
160
+ };
161
+ this.cache.set(url, error);
162
+ return error;
163
+ }
164
+ }
165
+ try {
35
166
  const thumb = await new Promise((resolve, reject) => {
36
- gm(bytes)
37
- .quality(50)
38
- .resize(128, 128)
39
- .toBuffer('PNG', (error, buffer) => {
167
+ gm(sourceBuffer)
168
+ .quality(this.quality)
169
+ .resize(this.width, this.height)
170
+ .flatten()
171
+ .toBuffer(this.encoding, (error, buffer) => {
40
172
  if (error) {
41
173
  reject(error);
42
174
  }
@@ -47,12 +179,22 @@ export class ImageThumbnailWitness extends AbstractWitness {
47
179
  });
48
180
  const result = {
49
181
  schema: ImageThumbnailSchema,
50
- sourceHash: await binaryToSha256(bytes),
182
+ sourceHash: await binaryToSha256(sourceBuffer),
51
183
  sourceUrl: url,
52
184
  url: `data:image/png;base64,${thumb.toString('base64')}`,
53
185
  };
186
+ this.cache.set(url, result);
54
187
  return result;
55
188
  }
189
+ catch (ex) {
190
+ const error = {
191
+ message: 'Failed to resize image',
192
+ schema: ModuleErrorSchema,
193
+ url,
194
+ };
195
+ this.cache.set(url, error);
196
+ return error;
197
+ }
56
198
  })));
57
199
  }
58
200
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Witness.js","sourceRoot":"","sources":["../../../src/Witness/Witness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAyB,oBAAoB,EAAE,MAAM,6CAA6C,CAAA;AAEzG,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAA;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAClC,OAAO,OAAO,MAAM,gBAAgB,CAAA;AACpC,OAAO,KAAK,MAAM,QAAQ,CAAA;AAE1B,OAAO,EAAE,iCAAiC,EAAE,MAAM,UAAU,CAAA;AAG5D,MAAM,EAAE,GAAG,QAAQ,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;AAE1C,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,IAAgB,EAAE,EAAE;IACvD,MAAM,aAAa,CAAC,eAAe,CAAA;IACnC,IAAI,aAAa,CAAC,WAAW,CAAC,UAAU,EAAE;QACxC,IAAI;YACF,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,CAAA;SAC1B;QAAC,OAAO,EAAE,EAAE;YACX,aAAa,CAAC,WAAW,CAAC,SAAS,GAAG,KAAK,CAAA;SAC5C;KACF;IACD,mDAAmD;IACnD,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAA;AACzD,CAAC,CAAA;AAED,MAAM,OAAO,qBAAiG,SAAQ,eAAwB;IAC5I,MAAM,CAAU,aAAa,GAAG,CAAC,iCAAiC,CAAC,CAAA;IAEhD,KAAK,CAAC,cAAc,CAAC,WAAyB,EAAE;QACjE,OAAO,OAAO,CACZ,MAAM,OAAO,CAAC,GAAG,CACf,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;YAC7B,uCAAuC;YACvC,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,8BAA8B,CAAC,CAAA;YACzE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE;gBAC3C,YAAY,EAAE,aAAa;aAC5B,CAAC,CAAA;YACF,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE;gBACnD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;gBAClD,MAAM,KAAK,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBAC1D,EAAE,CAAC,KAAK,CAAC;yBACN,OAAO,CAAC,EAAE,CAAC;yBACX,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC;yBAChB,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;wBACjC,IAAI,KAAK,EAAE;4BACT,MAAM,CAAC,KAAK,CAAC,CAAA;yBACd;6BAAM;4BACL,OAAO,CAAC,MAAM,CAAC,CAAA;yBAChB;oBACH,CAAC,CAAC,CAAA;gBACN,CAAC,CAAC,CAAA;gBACF,MAAM,MAAM,GAA0B;oBACpC,MAAM,EAAE,oBAAoB;oBAC5B,UAAU,EAAE,MAAM,cAAc,CAAC,KAAK,CAAC;oBACvC,SAAS,EAAE,GAAG;oBACd,GAAG,EAAE,yBAAyB,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;iBACzD,CAAA;gBACD,OAAO,MAAM,CAAA;aACd;QACH,CAAC,CAAC,CACH,CACF,CAAA;IACH,CAAC"}
1
+ {"version":3,"file":"Witness.js","sourceRoot":"","sources":["../../../src/Witness/Witness.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,12 @@ export declare const ImageThumbnailPlugin: () => import("@xyo-network/payloadset
36
36
  schema: "network.xyo.payload.set";
37
37
  }) | undefined;
38
38
  } & {
39
+ encoding?: "PNG" | undefined;
40
+ height?: number | undefined;
41
+ maxCacheEntries?: number | undefined;
42
+ quality?: number | undefined;
39
43
  schema: "network.xyo.image.thumbnail.witness.config";
44
+ width?: number | undefined;
40
45
  }, "schema"> & {
41
46
  schema: "network.xyo.image.thumbnail.witness.config";
42
47
  }, "schema"> & {
@@ -1 +1 @@
1
- {"version":3,"file":"Plugin.d.ts","sourceRoot":"","sources":["../../src/Plugin.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAEjD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAS9B,CAAA"}
1
+ {"version":3,"file":"Plugin.d.ts","sourceRoot":"","sources":["../../src/Plugin.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAEjD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAS9B,CAAA"}
@@ -2,6 +2,11 @@ import { WitnessConfig } from '@xyo-network/witness';
2
2
  export declare const ImageThumbnailWitnessConfigSchema: "network.xyo.image.thumbnail.witness.config";
3
3
  export type ImageThumbnailWitnessConfigSchema = typeof ImageThumbnailWitnessConfigSchema;
4
4
  export type ImageThumbnailWitnessConfig = WitnessConfig<{
5
+ encoding?: 'PNG';
6
+ height?: number;
7
+ maxCacheEntries?: number;
8
+ quality?: number;
5
9
  schema: ImageThumbnailWitnessConfigSchema;
10
+ width?: number;
6
11
  }>;
7
12
  //# sourceMappingURL=Config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../../src/Witness/Config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,eAAO,MAAM,iCAAiC,8CAAoD,CAAA;AAClG,MAAM,MAAM,iCAAiC,GAAG,OAAO,iCAAiC,CAAA;AAExF,MAAM,MAAM,2BAA2B,GAAG,aAAa,CAAC;IACtD,MAAM,EAAE,iCAAiC,CAAA;CAC1C,CAAC,CAAA"}
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../../src/Witness/Config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEpD,eAAO,MAAM,iCAAiC,8CAAoD,CAAA;AAClG,MAAM,MAAM,iCAAiC,GAAG,OAAO,iCAAiC,CAAA;AAExF,MAAM,MAAM,2BAA2B,GAAG,aAAa,CAAC;IACtD,QAAQ,CAAC,EAAE,KAAK,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,iCAAiC,CAAA;IACzC,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC,CAAA"}
@@ -1,10 +1,25 @@
1
- import { ImageThumbnailPayload } from '@xyo-network/image-thumbnail-payload-plugin';
1
+ import { ImageThumbnailErrorPayload, ImageThumbnailPayload } from '@xyo-network/image-thumbnail-payload-plugin';
2
2
  import { UrlPayload } from '@xyo-network/url-payload-plugin';
3
3
  import { AbstractWitness } from '@xyo-network/witness';
4
+ import { LRUCache } from 'lru-cache';
4
5
  import { ImageThumbnailWitnessParams } from './Params';
5
6
  export declare const binaryToSha256: (data: Uint8Array) => Promise<string>;
6
7
  export declare class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams = ImageThumbnailWitnessParams> extends AbstractWitness<TParams> {
7
8
  static configSchemas: "network.xyo.image.thumbnail.witness.config"[];
8
- protected observeHandler(payloads?: UrlPayload[]): Promise<ImageThumbnailPayload[]>;
9
+ private _cache?;
10
+ get cache(): LRUCache<string, ImageThumbnailErrorPayload | (import("@xyo-network/payload-model").SchemaFields & import("@xyo-network/payload-model").PayloadFields & {
11
+ schema: "network.xyo.image.thumbnail";
12
+ sourceHash: string;
13
+ sourceUrl: string;
14
+ url: string;
15
+ } & {
16
+ schema: "network.xyo.image.thumbnail";
17
+ }), unknown>;
18
+ get encoding(): "PNG";
19
+ get height(): number;
20
+ get maxCacheEntries(): number;
21
+ get quality(): number;
22
+ get width(): number;
23
+ protected observeHandler(payloads?: UrlPayload[]): Promise<(ImageThumbnailPayload | ImageThumbnailErrorPayload)[]>;
9
24
  }
10
25
  //# sourceMappingURL=Witness.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Witness.d.ts","sourceRoot":"","sources":["../../../src/Witness/Witness.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAwB,MAAM,6CAA6C,CAAA;AACzG,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAOtD,OAAO,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAA;AAItD,eAAO,MAAM,cAAc,SAAgB,UAAU,oBAWpD,CAAA;AAED,qBAAa,qBAAqB,CAAC,OAAO,SAAS,2BAA2B,GAAG,2BAA2B,CAAE,SAAQ,eAAe,CAAC,OAAO,CAAC;IAC5I,OAAgB,aAAa,iDAAsC;cAE1C,cAAc,CAAC,QAAQ,GAAE,UAAU,EAAO,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC;CAmCvG"}
1
+ {"version":3,"file":"Witness.d.ts","sourceRoot":"","sources":["../../../src/Witness/Witness.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,qBAAqB,EAAwB,MAAM,6CAA6C,CAAA;AAErI,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAKtD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAKpC,OAAO,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAA;AAMtD,eAAO,MAAM,cAAc,SAAgB,UAAU,oBAWpD,CAAA;AAuCD,qBAAa,qBAAqB,CAAC,OAAO,SAAS,2BAA2B,GAAG,2BAA2B,CAAE,SAAQ,eAAe,CAAC,OAAO,CAAC;IAC5I,OAAgB,aAAa,iDAAsC;IAEnE,OAAO,CAAC,MAAM,CAAC,CAAsE;IAErF,IAAI,KAAK;;;;;;;iBAGR;IAED,IAAI,QAAQ,UAEX;IAED,IAAI,MAAM,WAET;IAED,IAAI,eAAe,WAElB;IAED,IAAI,OAAO,WAEV;IAED,IAAI,KAAK,WAER;cAEwB,cAAc,CAAC,QAAQ,GAAE,UAAU,EAAO,GAAG,OAAO,CAAC,CAAC,qBAAqB,GAAG,0BAA0B,CAAC,EAAE,CAAC;CAwHtI"}
package/package.json CHANGED
@@ -10,17 +10,18 @@
10
10
  "url": "https://github.com/XYOracleNetwork/sdk-xyo-client-js/issues"
11
11
  },
12
12
  "dependencies": {
13
- "@xyo-network/axios": "~2.70.9",
14
- "@xyo-network/core": "~2.70.9",
15
- "@xyo-network/image-thumbnail-payload-plugin": "~2.70.7",
16
- "@xyo-network/module": "~2.70.9",
17
- "@xyo-network/payload-model": "~2.70.9",
18
- "@xyo-network/payloadset-plugin": "~2.70.9",
19
- "@xyo-network/url-payload-plugin": "~2.70.9",
20
- "@xyo-network/witness": "~2.70.9",
13
+ "@xyo-network/axios": "~2.70.11",
14
+ "@xyo-network/core": "~2.70.11",
15
+ "@xyo-network/image-thumbnail-payload-plugin": "~2.70.9",
16
+ "@xyo-network/module": "~2.70.11",
17
+ "@xyo-network/payload-model": "~2.70.11",
18
+ "@xyo-network/payloadset-plugin": "~2.70.11",
19
+ "@xyo-network/url-payload-plugin": "~2.70.11",
20
+ "@xyo-network/witness": "~2.70.11",
21
21
  "gm": "^1.25.0",
22
22
  "hash-wasm": "^4.9.0",
23
23
  "lodash": "^4.17.21",
24
+ "lru-cache": "^10.0.0",
24
25
  "sha.js": "^2.4.11"
25
26
  },
26
27
  "devDependencies": {
@@ -63,5 +64,5 @@
63
64
  },
64
65
  "sideEffects": false,
65
66
  "types": "dist/types/index.d.ts",
66
- "version": "2.70.7"
67
+ "version": "2.70.9"
67
68
  }
@@ -5,5 +5,10 @@ export const ImageThumbnailWitnessConfigSchema = `${ImageThumbnailSchema}.witnes
5
5
  export type ImageThumbnailWitnessConfigSchema = typeof ImageThumbnailWitnessConfigSchema
6
6
 
7
7
  export type ImageThumbnailWitnessConfig = WitnessConfig<{
8
+ encoding?: 'PNG'
9
+ height?: number
10
+ maxCacheEntries?: number
11
+ quality?: number
8
12
  schema: ImageThumbnailWitnessConfigSchema
13
+ width?: number
9
14
  }>
@@ -1,16 +1,22 @@
1
- import { axios } from '@xyo-network/axios'
1
+ import { axios, AxiosError, AxiosResponse } from '@xyo-network/axios'
2
2
  import { PayloadHasher } from '@xyo-network/core'
3
- import { ImageThumbnailPayload, ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin'
3
+ import { ImageThumbnailErrorPayload, ImageThumbnailPayload, ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin'
4
+ import { isPayload, ModuleError, ModuleErrorSchema } from '@xyo-network/payload-model'
4
5
  import { UrlPayload } from '@xyo-network/url-payload-plugin'
5
6
  import { AbstractWitness } from '@xyo-network/witness'
6
7
  import { subClass } from 'gm'
7
8
  import { sha256 } from 'hash-wasm'
8
9
  import compact from 'lodash/compact'
10
+ import isBuffer from 'lodash/isBuffer'
11
+ import { LRUCache } from 'lru-cache'
9
12
  import shajs from 'sha.js'
13
+ import Url from 'url-parse'
10
14
 
11
15
  import { ImageThumbnailWitnessConfigSchema } from './Config'
12
16
  import { ImageThumbnailWitnessParams } from './Params'
13
17
 
18
+ //TODO: Break this into two Witnesses?
19
+
14
20
  const gm = subClass({ imageMagick: '7+' })
15
21
 
16
22
  export const binaryToSha256 = async (data: Uint8Array) => {
@@ -26,25 +32,165 @@ export const binaryToSha256 = async (data: Uint8Array) => {
26
32
  return shajs('sha256').update(data).digest().toString()
27
33
  }
28
34
 
35
+ const checkIpfsUrl = (urlToCheck: string) => {
36
+ const url = new Url(urlToCheck)
37
+ let protocol = url.protocol
38
+ let host = url.host
39
+ let path = url.pathname
40
+ const query = url.query
41
+ if (protocol === 'ipfs:') {
42
+ protocol = 'https:'
43
+ host = 'cloudflare-ipfs.com'
44
+ path = url.host === 'ipfs' ? `ipfs${path}` : `ipfs/${url.host}${path}`
45
+ const root = `${protocol}//${host}/${path}`
46
+ return query?.length > 0 ? `root?${query}` : root
47
+ } else {
48
+ return urlToCheck
49
+ }
50
+ }
51
+
52
+ const checkDataUrl = (url: string): [Buffer | undefined, ImageThumbnailErrorPayload | undefined] => {
53
+ if (url.startsWith('data:image')) {
54
+ const data = url.split(',')[1]
55
+ if (data) {
56
+ const buffer = Buffer.from(Uint8Array.from(atob(data), (c) => c.charCodeAt(0)))
57
+ console.log(`data buffer: ${buffer.length}`)
58
+ return [buffer, undefined]
59
+ } else {
60
+ const error: ImageThumbnailErrorPayload = {
61
+ message: 'Invalid data Url',
62
+ schema: ModuleErrorSchema,
63
+ url,
64
+ }
65
+ return [undefined, error]
66
+ }
67
+ } else {
68
+ return [undefined, undefined]
69
+ }
70
+ }
71
+
29
72
  export class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams = ImageThumbnailWitnessParams> extends AbstractWitness<TParams> {
30
73
  static override configSchemas = [ImageThumbnailWitnessConfigSchema]
31
74
 
32
- protected override async observeHandler(payloads: UrlPayload[] = []): Promise<ImageThumbnailPayload[]> {
33
- return compact(
75
+ private _cache?: LRUCache<string, ImageThumbnailPayload | ImageThumbnailErrorPayload>
76
+
77
+ get cache() {
78
+ this._cache = this._cache ?? new LRUCache<string, ImageThumbnailPayload | ImageThumbnailErrorPayload>({ max: this.maxCacheEntries })
79
+ return this._cache
80
+ }
81
+
82
+ get encoding() {
83
+ return this.config.encoding ?? 'PNG'
84
+ }
85
+
86
+ get height() {
87
+ return this.config.height ?? 128
88
+ }
89
+
90
+ get maxCacheEntries() {
91
+ return this.config.maxCacheEntries ?? 5000
92
+ }
93
+
94
+ get quality() {
95
+ return this.config.quality ?? 50
96
+ }
97
+
98
+ get width() {
99
+ return this.config.width ?? 128
100
+ }
101
+
102
+ protected override async observeHandler(payloads: UrlPayload[] = []): Promise<(ImageThumbnailPayload | ImageThumbnailErrorPayload)[]> {
103
+ const responsePairs = compact(
34
104
  await Promise.all(
35
- payloads.map(async ({ url }) => {
105
+ payloads.map<Promise<[string, ImageThumbnailPayload | ImageThumbnailErrorPayload | AxiosResponse | Buffer]>>(async ({ url }) => {
106
+ const cachedResult = this.cache.get(url)
107
+ if (cachedResult) {
108
+ return [url, cachedResult]
109
+ }
110
+ //if it is a data URL, return a Buffer
111
+ const [dataBuffer, dataError] = checkDataUrl(url)
112
+
113
+ if (dataBuffer) {
114
+ return [url, dataBuffer]
115
+ }
116
+
117
+ if (dataError) {
118
+ return [url, dataError]
119
+ }
120
+
36
121
  //if it is ipfs, go through cloud flair
37
- const mutatedUrl = url.replace('ipfs://', 'https://cloudflare-ipfs.com/')
38
- const response = await axios.get(mutatedUrl, {
39
- responseType: 'arraybuffer',
40
- })
41
- if (response.status >= 200 && response.status < 300) {
42
- const bytes = Buffer.from(response.data, 'binary')
122
+ const mutatedUrl = checkIpfsUrl(url)
123
+
124
+ try {
125
+ return [
126
+ url,
127
+ await axios.get(mutatedUrl, {
128
+ responseType: 'arraybuffer',
129
+ }),
130
+ ]
131
+ } catch (ex) {
132
+ const axiosError = ex as AxiosError
133
+ if (axiosError.isAxiosError) {
134
+ //selectively pick fields from AxiosError
135
+ const errorPayload: ImageThumbnailErrorPayload = {
136
+ code: axiosError.code,
137
+ message: axiosError.message,
138
+ schema: ModuleErrorSchema,
139
+ status: axiosError.status,
140
+ url,
141
+ }
142
+ return [url, errorPayload]
143
+ } else {
144
+ throw ex
145
+ }
146
+ }
147
+ }),
148
+ ),
149
+ )
150
+ return compact(
151
+ await Promise.all(
152
+ responsePairs.map(async ([url, urlResult]) => {
153
+ if (isPayload(urlResult)) {
154
+ this.cache.set(url, urlResult)
155
+ return urlResult
156
+ }
157
+
158
+ let sourceBuffer: Buffer
159
+
160
+ if (isBuffer(urlResult)) {
161
+ sourceBuffer = urlResult as Buffer
162
+ } else {
163
+ const response = urlResult as AxiosResponse
164
+
165
+ if (response.status >= 200 && response.status < 300) {
166
+ const contentType = response.headers['content-type']?.toString()
167
+ if (contentType.split('/')[0] !== 'image') {
168
+ const error: ImageThumbnailErrorPayload = {
169
+ message: `Invalid file type: ${contentType}`,
170
+ schema: ModuleErrorSchema,
171
+ url,
172
+ }
173
+ this.cache.set(url, error)
174
+ return error
175
+ }
176
+ sourceBuffer = Buffer.from(response.data, 'binary')
177
+ } else {
178
+ const error: ImageThumbnailErrorPayload = {
179
+ schema: ModuleErrorSchema,
180
+ status: response.status,
181
+ url,
182
+ }
183
+ this.cache.set(url, error)
184
+ return error
185
+ }
186
+ }
187
+ try {
43
188
  const thumb = await new Promise<Buffer>((resolve, reject) => {
44
- gm(bytes)
45
- .quality(50)
46
- .resize(128, 128)
47
- .toBuffer('PNG', (error, buffer) => {
189
+ gm(sourceBuffer)
190
+ .quality(this.quality)
191
+ .resize(this.width, this.height)
192
+ .flatten()
193
+ .toBuffer(this.encoding, (error, buffer) => {
48
194
  if (error) {
49
195
  reject(error)
50
196
  } else {
@@ -54,11 +200,20 @@ export class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams =
54
200
  })
55
201
  const result: ImageThumbnailPayload = {
56
202
  schema: ImageThumbnailSchema,
57
- sourceHash: await binaryToSha256(bytes),
203
+ sourceHash: await binaryToSha256(sourceBuffer),
58
204
  sourceUrl: url,
59
205
  url: `data:image/png;base64,${thumb.toString('base64')}`,
60
206
  }
207
+ this.cache.set(url, result)
61
208
  return result
209
+ } catch (ex) {
210
+ const error: ImageThumbnailErrorPayload = {
211
+ message: 'Failed to resize image',
212
+ schema: ModuleErrorSchema,
213
+ url,
214
+ }
215
+ this.cache.set(url, error)
216
+ return error
62
217
  }
63
218
  }),
64
219
  ),