plugin-file-preview-auth 1.2.3 → 1.2.6

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.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import React from 'react';
10
+ import type { Application } from '@nocobase/client';
11
+ export declare const FILE_PREVIEW_WORK_CONTEXT_TYPE = "file-preview";
12
+ export declare function createFilePreviewWorkContext(file: any): {
13
+ type: string;
14
+ uid: string;
15
+ title: string;
16
+ content: {
17
+ source: string;
18
+ file: {
19
+ id: any;
20
+ uid: any;
21
+ url: any;
22
+ preview: any;
23
+ filename: any;
24
+ name: any;
25
+ title: any;
26
+ extname: any;
27
+ mimetype: any;
28
+ size: any;
29
+ path: any;
30
+ storageId: any;
31
+ };
32
+ };
33
+ };
34
+ export declare const AIFilePreviewAction: React.FC<{
35
+ file: any;
36
+ }>;
37
+ export declare function registerFilePreviewAIWorkContext(app: Application): void;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Plugin } from '@nocobase/client';
10
+ export declare class PluginFilePreviewAuthClient extends Plugin {
11
+ load(): Promise<void>;
12
+ }
13
+ export default PluginFilePreviewAuthClient;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ export declare function useT(): (str: string) => string;
10
+ export declare function tStr(key: string): string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ export * from './server';
10
+ export { default } from './server';
@@ -0,0 +1,13 @@
1
+ declare const _default: {
2
+ name: string;
3
+ fields: ({
4
+ name: string;
5
+ type: string;
6
+ defaultValue: string;
7
+ } | {
8
+ name: string;
9
+ type: string;
10
+ defaultValue?: undefined;
11
+ })[];
12
+ };
13
+ export default _default;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * DUMMY COLLECTION
3
+ * This collection is created to prevent NocoBase workflow/ACL from throwing the error:
4
+ * '[Workflow pre-action]: collection "filePreviewAuth" not found'
5
+ *
6
+ * Since 'filePreviewAuth' is registered as a resourcer in plugin.ts, NocoBase
7
+ * implicitly looks for a collection with the same name.
8
+ * We set dumpRules: 'skip' and avoid timestamps to ensure this collection
9
+ * is completely ignored during backups/migrations and doesn't pollute the actual DB.
10
+ */
11
+ declare const _default: {
12
+ name: string;
13
+ dumpRules: string;
14
+ autoGenId: boolean;
15
+ createdAt: boolean;
16
+ updatedAt: boolean;
17
+ fields: {
18
+ name: string;
19
+ type: string;
20
+ }[];
21
+ };
22
+ export default _default;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import type { Context } from '@nocobase/actions';
10
+ interface AttachmentLike {
11
+ id?: string | number;
12
+ filename?: string;
13
+ name?: string;
14
+ mimetype?: string;
15
+ extname?: string;
16
+ url?: string;
17
+ storageId?: number;
18
+ size?: number;
19
+ meta?: Record<string, any>;
20
+ [key: string]: any;
21
+ }
22
+ interface InternalParseResult {
23
+ text: string;
24
+ handled: boolean;
25
+ }
26
+ export interface InternalParserHandler {
27
+ name: string;
28
+ supports(attachment: AttachmentLike): boolean;
29
+ parse(attachment: AttachmentLike, ctx: Context): Promise<InternalParseResult>;
30
+ }
31
+ /**
32
+ * Server-side Excel parser handler.
33
+ *
34
+ * Uses SheetJS (xlsx) — the same library already bundled by this plugin for
35
+ * client-side XLSX preview — to extract plain text from .xlsx/.xls files on
36
+ * the server.
37
+ *
38
+ * Each worksheet is converted to CSV and separated by a section header so LLMs
39
+ * can distinguish sheet boundaries:
40
+ *
41
+ * === Sheet: Sheet1 ===
42
+ * col1,col2,col3
43
+ * val1,val2,val3
44
+ *
45
+ * === Sheet: Sheet2 ===
46
+ * ...
47
+ *
48
+ * This handler is registered into plugin-document-parser's InternalParserRegistry
49
+ * by PluginFilePreviewAuthServer with `prepend: true` so it takes priority over
50
+ * any other handlers that might be registered later.
51
+ *
52
+ * File bytes are fetched via plugin-document-parser's public `fetchFileBuffer`
53
+ * helper, which transparently handles both S3 URLs and local file paths.
54
+ */
55
+ export declare class ExcelParserHandler implements InternalParserHandler {
56
+ readonly name = "file-preview-auth-excel-parser";
57
+ supports(attachment: AttachmentLike): boolean;
58
+ parse(attachment: AttachmentLike, ctx: Context): Promise<InternalParseResult>;
59
+ }
60
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ export { default } from './plugin';
@@ -0,0 +1,34 @@
1
+ export interface OcrRect {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ unit: string;
7
+ }
8
+ export interface OcrWordItem {
9
+ id: string;
10
+ key: string;
11
+ value: string;
12
+ page: number;
13
+ confidence: number;
14
+ rect: OcrRect;
15
+ status: string;
16
+ }
17
+ export declare class TesseractRunner {
18
+ private log;
19
+ constructor(app: any);
20
+ /**
21
+ * Run Tesseract OCR on a file (PDF or Image).
22
+ * Generates word-level coordinates and text.
23
+ */
24
+ runOcr(filePath: string, attachmentId: number): Promise<{
25
+ pages: Array<{
26
+ page_number: number;
27
+ items: OcrWordItem[];
28
+ }>;
29
+ }>;
30
+ private executeTesseract;
31
+ private parseTsv;
32
+ private convertPdfToImages;
33
+ private generateMockOcrData;
34
+ }
@@ -0,0 +1,26 @@
1
+ export declare class TesseractWorker {
2
+ private app;
3
+ private db;
4
+ private log;
5
+ private runner;
6
+ private isRunning;
7
+ private pollTimer;
8
+ private redisKey;
9
+ constructor(app: any);
10
+ /**
11
+ * Start the background worker.
12
+ */
13
+ start(): Promise<void>;
14
+ /**
15
+ * Stop the background worker.
16
+ */
17
+ stop(): void;
18
+ /**
19
+ * Enqueue a new OCR job.
20
+ */
21
+ enqueue(attachmentId: number): Promise<boolean>;
22
+ private getRedisClient;
23
+ private listenRedisQueue;
24
+ private startDbPolling;
25
+ private processJob;
26
+ }
@@ -158,7 +158,7 @@ class TesseractWorker {
158
158
  if (!fileManager) {
159
159
  throw new Error("File manager plugin is not active.");
160
160
  }
161
- const storageModel = fileManager.storagesCache.get(attachment.storageId);
161
+ const storageModel = getStorageFromCache(fileManager.storagesCache, attachment.storageId);
162
162
  if (!storageModel || storageModel.type !== "local") {
163
163
  this.log.info(`[TesseractWorker] Non-local storage detected or virtual file. Using fallback.`);
164
164
  }
@@ -185,6 +185,25 @@ class TesseractWorker {
185
185
  }
186
186
  }
187
187
  }
188
+ function getStorageFromCache(cache, storageId) {
189
+ if (storageId === void 0 || storageId === null) return void 0;
190
+ let res = cache.get(storageId);
191
+ if (res) return res;
192
+ const strId = String(storageId);
193
+ res = cache.get(strId);
194
+ if (res) return res;
195
+ const numId = Number(storageId);
196
+ if (!isNaN(numId)) {
197
+ res = cache.get(numId);
198
+ if (res) return res;
199
+ }
200
+ for (const [k, v] of cache.entries()) {
201
+ if (String(k) === strId) {
202
+ return v;
203
+ }
204
+ }
205
+ return void 0;
206
+ }
188
207
  // Annotate the CommonJS export names for ESM import in node:
189
208
  0 && (module.exports = {
190
209
  TesseractWorker
@@ -0,0 +1,41 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Plugin } from '@nocobase/server';
10
+ export declare class PluginFilePreviewAuthServer extends Plugin {
11
+ private cache;
12
+ private ocrWorker;
13
+ beforeLoad(): Promise<void>;
14
+ load(): Promise<void>;
15
+ afterEnable(): Promise<void>;
16
+ beforeDisable(): Promise<void>;
17
+ beforeDestroy(): Promise<void>;
18
+ /**
19
+ * Disable NocoBase's built-in Office previewer after plugins have loaded.
20
+ * This keeps this authenticated previewer as the active Office handler without causing a restart loop.
21
+ */
22
+ private disableBuiltinOfficePreviewer;
23
+ private registerDownloadApi;
24
+ private registerAIWorkContext;
25
+ private resolveAttachment;
26
+ private assertCanAccessAttachment;
27
+ private assertAuthenticated;
28
+ private consumeUploadedParseFile;
29
+ private extractUploadedFileText;
30
+ private getMarkItDownParserPlugin;
31
+ private extractAttachmentText;
32
+ private readAttachmentAsText;
33
+ private formatAttachmentWorkContext;
34
+ /**
35
+ * Register Excel handler into plugin-document-parser's InternalParserRegistry.
36
+ * Uses prepend:true so SheetJS takes priority over the AI-loader fallback.
37
+ * Silent no-op when plugin-document-parser is not loaded.
38
+ */
39
+ private registerExcelParser;
40
+ }
41
+ export default PluginFilePreviewAuthServer;
@@ -188,28 +188,44 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
188
188
  await this.assertCanAccessAttachment(ctx, attachment);
189
189
  }
190
190
  try {
191
- const storageModel = fileManager.storagesCache.get(attachment.storageId);
192
- if (storageModel && (storageModel.type === "s3" || storageModel.type === "aws-s3")) {
191
+ let matchedKey = null;
192
+ const rawStorageId = attachment.storageId || getAttachmentValue(attachment, "storageId");
193
+ if (rawStorageId) {
194
+ const strId = String(rawStorageId);
195
+ for (const key of fileManager.storagesCache.keys()) {
196
+ if (String(key) === strId) {
197
+ matchedKey = key;
198
+ break;
199
+ }
200
+ }
201
+ }
202
+ const attachmentObj = typeof attachment.toJSON === "function" ? attachment.toJSON() : { ...attachment };
203
+ if (matchedKey !== null) {
204
+ attachmentObj.storageId = matchedKey;
205
+ }
206
+ const storageModel = getStorageFromCache(fileManager.storagesCache, attachmentObj.storageId);
207
+ if (storageModel && (storageModel.type === "s3" || storageModel.type === "aws-s3" || storageModel.type === "s3-private")) {
193
208
  const StorageTypeClass = fileManager.storageTypes.get(storageModel.type);
194
209
  const storageInstance = new StorageTypeClass(storageModel);
195
- if (storageInstance.client) {
210
+ const s3Client = storageInstance.client || (typeof storageInstance.getS3Client === "function" ? storageInstance.getS3Client() : null);
211
+ if (s3Client) {
196
212
  const { GetObjectCommand } = require("@aws-sdk/client-s3");
197
- const key = storageInstance.getFileKey(attachment);
213
+ const key = storageInstance.getFileKey(attachmentObj);
198
214
  const getCommand = new GetObjectCommand({
199
215
  Bucket: storageModel.options.bucket,
200
216
  Key: key
201
217
  });
202
- const response = await storageInstance.client.send(getCommand);
203
- ctx.type = response.ContentType || attachment.mimetype || "application/octet-stream";
204
- ctx.attachment(attachment.filename);
218
+ const response = await s3Client.send(getCommand);
219
+ ctx.type = response.ContentType || attachmentObj.mimetype || "application/octet-stream";
220
+ ctx.attachment(attachmentObj.filename);
205
221
  ctx.body = response.Body;
206
222
  await next();
207
223
  return;
208
224
  }
209
225
  }
210
- const { stream, contentType } = await fileManager.getFileStream(attachment);
211
- ctx.type = contentType || attachment.mimetype || "application/octet-stream";
212
- ctx.attachment(attachment.filename);
226
+ const { stream, contentType } = await fileManager.getFileStream(attachmentObj);
227
+ ctx.type = contentType || attachmentObj.mimetype || "application/octet-stream";
228
+ ctx.attachment(attachmentObj.filename);
213
229
  ctx.body = stream;
214
230
  } catch (err) {
215
231
  this.log.error(`[FilePreviewAuth] Error fetching stream for URL ${url}: ${err.message}`);
@@ -433,7 +449,22 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
433
449
  if (!(fileManager == null ? void 0 : fileManager.getFileStream)) {
434
450
  return "";
435
451
  }
436
- const { stream } = await fileManager.getFileStream(attachment);
452
+ let matchedKey = null;
453
+ const rawStorageId = attachment.storageId || getAttachmentValue(attachment, "storageId");
454
+ if (rawStorageId) {
455
+ const strId = String(rawStorageId);
456
+ for (const key of fileManager.storagesCache.keys()) {
457
+ if (String(key) === strId) {
458
+ matchedKey = key;
459
+ break;
460
+ }
461
+ }
462
+ }
463
+ const attachmentObj = typeof attachment.toJSON === "function" ? attachment.toJSON() : { ...attachment };
464
+ if (matchedKey !== null) {
465
+ attachmentObj.storageId = matchedKey;
466
+ }
467
+ const { stream } = await fileManager.getFileStream(attachmentObj);
437
468
  const chunks = [];
438
469
  for await (const chunk of stream) {
439
470
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -479,6 +510,25 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
479
510
  }
480
511
  }
481
512
  var plugin_default = PluginFilePreviewAuthServer;
513
+ function getStorageFromCache(cache, storageId) {
514
+ if (storageId === void 0 || storageId === null) return void 0;
515
+ let res = cache.get(storageId);
516
+ if (res) return res;
517
+ const strId = String(storageId);
518
+ res = cache.get(strId);
519
+ if (res) return res;
520
+ const numId = Number(storageId);
521
+ if (!isNaN(numId)) {
522
+ res = cache.get(numId);
523
+ if (res) return res;
524
+ }
525
+ for (const [k, v] of cache.entries()) {
526
+ if (String(k) === strId) {
527
+ return v;
528
+ }
529
+ }
530
+ return void 0;
531
+ }
482
532
  function getAttachmentValue(attachment, key) {
483
533
  if (!attachment) return void 0;
484
534
  if (typeof attachment.get === "function") return attachment.get(key);
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Preview PDF, image, and text files with Bearer token authentication via blob URLs.",
7
7
  "description.vi-VN": "Xem trước file PDF, hình ảnh và văn bản với xác thực Bearer token qua blob URL.",
8
8
  "description.zh-CN": "通过 Bearer 令牌认证和 Blob URL 预览 PDF、图片和文本文件。",
9
- "version": "1.2.3",
9
+ "version": "1.2.6",
10
10
  "main": "dist/server/index.js",
11
11
  "files": [
12
12
  "dist",