@stubber/virtual-worker 1.0.1 → 1.0.2

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/.env_dev CHANGED
@@ -1,14 +1 @@
1
- # when running inside docker and connecting to tag running on host machine
2
- # WORKER_CONNECT_URL=http://172.17.0.1:7450/orgs/7cf3b2dc-5a49-51dd-8b4d-812a8ba6f233
3
- # when running using npm run dev and connecting to tag running on host machine
4
- # WORKER_CONNECT_URL=http://localhost:7450/orgs/7cf3b2dc-5a49-51dd-8b4d-812a8ba6f233
5
- # when used in docker or with npm run, connect to the devmaster tag
6
- WORKER_CONNECT_URL="https://virtual-workers.dev.stubber.com/orgs/7cf3b2dc-5a49-51dd-8b4d-812a8ba6f233"
7
-
8
- WORKER_SIGNATURE=069c293dc0d71dd42b32b122da62b2deae08c12c67a85224d14a7dd8c4907f21
9
-
10
- WORKER_WORKERUUID=b0bf29a8-5ebb-4b79-b464-9d35686ebc7d
11
-
12
- WORKER_GROUPS=insurance-worker,b0bf29a8-5ebb-4b79-b464-9d35686ebc7d
13
-
14
- NODE_ENV=development
1
+ FILESERVER_URL="https://uploads.secure-link.services.dev.stubber.com/api/v1"
package/.env_devmaster CHANGED
@@ -1,7 +1 @@
1
- WORKER_CONNECT_URL="https://virtual-workers.dev.stubber.com/orgs/7cf3b2dc-5a49-51dd-8b4d-812a8ba6f233"
2
-
3
- WORKER_SIGNATURE=069c293dc0d71dd42b32b122da62b2deae08c12c67a85224d14a7dd8c4907f21
4
-
5
- WORKER_WORKERUUID=b0bf29a8-5ebb-4b79-b464-9d35686ebc7d
6
-
7
- WORKER_GROUPS=insurance-worker,b0bf29a8-5ebb-4b79-b464-9d35686ebc7d
1
+ FILESERVER_URL="https://uploads.secure-link.services.dev.stubber.com/api/v1"
@@ -5,10 +5,20 @@ import { create_success } from "#app/functions/create_success.js";
5
5
  import { writeFile } from "node:fs/promises";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
8
+ import { config } from "#root/config/main.js";
9
+
10
+ /**
11
+ * @typedef FileInfoObject
12
+ * @property {string} fileuuid - The UUID of the file
13
+ * @property {string} filename - The name of the file
14
+ * @property {string} originalname - The original name of the file
15
+ * @property {string} contentType - The content type of the file
16
+ * @property {string} f_id - The file ID added by form fields
17
+ */
8
18
 
9
19
  /**
10
20
  * @typedef DownloadFilesParams
11
- * @property {Array<string|{fileuuid:string}>} files - The files to download
21
+ * @property {Array<string|FileInfoObject>} files - The files to download
12
22
  */
13
23
 
14
24
  /**
@@ -23,11 +33,11 @@ import { join } from "node:path";
23
33
  * @param {DownloadFilesParams} params
24
34
  */
25
35
  export const download_files = async (params, _stubber) => {
26
- // todo: how will fileserver work now that we dont have sockets
27
- const socket = _stubber?.stubber_context.socket;
28
- if (!socket) {
29
- return create_error_conceptual({ message: "Socket not available", details: {} });
36
+ const fileserver_url = config?.fileserver_url;
37
+ if (!fileserver_url) {
38
+ return create_error_conceptual({ message: "Fileserver URL not configured", details: {} });
30
39
  }
40
+ console.log("Fileserver URL:", fileserver_url);
31
41
 
32
42
  const { files } = params || {};
33
43
  if (!files) {
@@ -37,44 +47,50 @@ export const download_files = async (params, _stubber) => {
37
47
  return create_error_conceptual({ message: "Files must be a non-empty array", details: { files } });
38
48
  }
39
49
 
40
- const uuids = [];
50
+ /**
51
+ * @type {Array<{fileuuid:string, filename:string}>}
52
+ */
53
+ const files_to_download = [];
41
54
  for (const file of files) {
42
55
  if (typeof file === "string") {
43
- uuids.push(file);
56
+ files_to_download.push({ fileuuid: file, filename: file });
44
57
  } else if (file && typeof file === "object" && typeof file.fileuuid === "string") {
45
- uuids.push(file.fileuuid);
58
+ files_to_download.push({
59
+ fileuuid: file.fileuuid,
60
+ filename: file.filename || file.originalname || file.fileuuid,
61
+ });
46
62
  } else {
47
63
  return create_error_conceptual({ message: "Invalid file entry in files array", details: { entry: file } });
48
64
  }
49
65
  }
50
66
 
51
- return new Promise((resolve) => {
52
- const downloaded_files = [];
67
+ const downloaded_files = [];
53
68
 
54
- socket.emit("download_files", uuids, async (response) => {
55
- if (response.success) {
56
- /** @type {ResponseFile[]} */
57
- const files = response.payload;
69
+ for (const { fileuuid, filename } of files_to_download) {
70
+ const file_url = `${fileserver_url}/file/uuid/${encodeURIComponent(fileuuid)}`;
58
71
 
59
- for (const file of files) {
60
- if (file.status == 200) {
61
- // todo: support passing download location in params
62
- const tmp_dir = tmpdir();
63
- const file_path = join(tmp_dir, file.fileuuid);
72
+ try {
73
+ const response = await fetch(file_url);
64
74
 
65
- await writeFile(file_path, file.data);
75
+ if (!response.ok) {
76
+ console.log("Error downloading file:", { fileuuid, status: response.status, status_text: response.statusText });
77
+ continue;
78
+ }
66
79
 
67
- downloaded_files.push(file_path);
68
- } else {
69
- console.log("Error downloading file:", file);
70
- }
71
- }
80
+ const array_buffer = await response.arrayBuffer();
81
+ const data = Buffer.from(array_buffer);
72
82
 
73
- resolve(create_success({ message: "Files downloaded successfully", payload: { downloaded_files } }));
74
- } else {
75
- console.log("Error downloading files:", response.error);
76
- resolve(create_error_technical(response.error));
77
- }
78
- });
79
- });
83
+ // todo: support passing download location in params
84
+ const tmp_dir = tmpdir();
85
+ const file_path = join(tmp_dir, filename);
86
+
87
+ await writeFile(file_path, data);
88
+
89
+ downloaded_files.push(file_path);
90
+ } catch (error) {
91
+ console.log("Error downloading file:", { fileuuid, error });
92
+ }
93
+ }
94
+
95
+ return create_success({ message: "Files downloaded successfully", payload: { downloaded_files } });
80
96
  };
@@ -1,8 +1,14 @@
1
1
  import { create_error_conceptual } from "#app/functions/create_error_conceptual.js";
2
2
  import { create_error_technical } from "#app/functions/create_error_technical.js";
3
3
  import { create_success } from "#app/functions/create_success.js";
4
+ import { config } from "#root/config/main.js";
4
5
  // eslint-disable-next-line id-match
5
6
  import { readFile } from "node:fs/promises";
7
+ // eslint-disable-next-line id-match
8
+ import { Blob } from "buffer";
9
+ import mime from "mime-types";
10
+ // eslint-disable-next-line id-match
11
+ import { fileTypeFromBuffer } from "file-type";
6
12
 
7
13
  /**
8
14
  * Upload files to the server
@@ -10,10 +16,15 @@ import { readFile } from "node:fs/promises";
10
16
  * @param {Array<string>} params.files - The path to files to upload
11
17
  */
12
18
  export const upload_files = async (params, _stubber) => {
13
- /** @type {import("socket.io-client").Socket} */
14
- const socket = _stubber?.stubber_context.socket;
15
- if (!socket) {
16
- return create_error_conceptual({ message: "Socket not available", details: {} });
19
+ const fileserver_url = config?.fileserver_url;
20
+ if (!fileserver_url) {
21
+ return create_error_conceptual({ message: "Fileserver URL not configured", details: {} });
22
+ }
23
+ console.log("Fileserver URL:", fileserver_url);
24
+
25
+ const orguuid = _stubber?.orguuid;
26
+ if (!orguuid) {
27
+ return create_error_conceptual({ message: "orguuid not available", details: {} });
17
28
  }
18
29
 
19
30
  const { files } = params || {};
@@ -24,34 +35,74 @@ export const upload_files = async (params, _stubber) => {
24
35
  });
25
36
  }
26
37
 
27
- const uploaded_files = [];
38
+ const form_data = new FormData();
39
+ const endpoint = `${fileserver_url}/upload?orguuid=${encodeURIComponent(orguuid)}`;
28
40
 
29
41
  for (const filepath of files) {
30
42
  const filename = filepath.split("/").pop();
31
- const file = await readFile(filepath); // Buffer
32
43
 
33
- // Wrap socket.emit in a promise so we can await it
34
- const file_info = await emit_upload(socket, { filepath, filename }, file);
44
+ if (!filename) {
45
+ return create_error_conceptual({
46
+ message: "Unable to determine filename from path",
47
+ details: { filepath },
48
+ });
49
+ }
50
+
51
+ try {
52
+ let mimetype = mime.lookup(filename) || "application/octet-stream";
35
53
 
36
- if (file_info) {
37
- uploaded_files.push(file_info);
54
+ const file_buffer = await readFile(filepath);
55
+
56
+ const type = await fileTypeFromBuffer(file_buffer);
57
+ if (type?.mime) {
58
+ mimetype = type.mime; // trust buffer over extension
59
+ }
60
+
61
+ const blob = new Blob([file_buffer], { type: mimetype });
62
+ form_data.append("file", blob, filename);
63
+ } catch (error) {
64
+ return create_error_technical(error);
38
65
  }
39
66
  }
40
67
 
41
- return create_success({ message: "Files uploaded successfully", payload: { uploaded_files } });
42
- };
68
+ try {
69
+ const response = await fetch(endpoint, {
70
+ method: "POST",
71
+ body: form_data,
72
+ });
43
73
 
44
- /**
45
- * Wraps a socket.emit call in a Promise
46
- */
47
- function emit_upload(socket, metadata, buffer) {
48
- return new Promise((resolve, reject) => {
49
- socket.emit("upload_file", metadata, buffer, (response) => {
50
- if (!response.success) {
51
- reject(new Error(response));
52
- } else {
53
- resolve(response.payload);
74
+ if (!response.ok) {
75
+ let error_payload = null;
76
+ try {
77
+ error_payload = await response.json();
78
+ } catch (_) {
79
+ error_payload = { raw_error: await response.text() };
54
80
  }
81
+
82
+ return create_error_conceptual({
83
+ message: "Failed to upload files",
84
+ details: { status: response.status, error_payload },
85
+ });
86
+ }
87
+
88
+ let payload = null;
89
+ try {
90
+ payload = await response.json();
91
+ } catch (error) {
92
+ return create_error_technical(error);
93
+ }
94
+
95
+ if (payload?.data?.uploaded_files) {
96
+ payload = {
97
+ uploaded_files: payload.data.uploaded_files,
98
+ };
99
+ }
100
+
101
+ return create_success({
102
+ message: "Files uploaded successfully",
103
+ payload,
55
104
  });
56
- });
57
- }
105
+ } catch (error) {
106
+ return create_error_technical(error);
107
+ }
108
+ };
@@ -3,7 +3,7 @@ import { task_schema } from "#lib/interfaces/http/routers/stubber_task_schema.js
3
3
  import * as z from "zod";
4
4
  import { worker_status } from "./virtual_worker_status.js";
5
5
 
6
- const command_schema = z.object({
6
+ const command_schema = z.looseObject({
7
7
  commandtype: z.string().min(1),
8
8
  params: z.record(z.string(), z.any()).optional(),
9
9
  continue_on_error: z.boolean().optional(),
@@ -11,11 +11,8 @@ const command_schema = z.object({
11
11
  conditions: z.array(z.string()).optional(),
12
12
  });
13
13
 
14
- const params_schema = z.object({
14
+ const params_schema = z.looseObject({
15
15
  commands: z.record(z.string(), command_schema),
16
- worker: z.object({
17
- group: z.string().min(1),
18
- }),
19
16
  });
20
17
 
21
18
  /**
@@ -11,7 +11,7 @@ export const create_error_technical = (error) => {
11
11
  details: {
12
12
  name: error.name,
13
13
  // stack up to the second \n newline
14
- stack: error.stack.split("\n").slice(0, 2).join("\n"),
14
+ stack: error.stack?.split("\n").slice(0, 2).join("\n"),
15
15
  },
16
16
  },
17
17
  };
package/config/main.js ADDED
@@ -0,0 +1,3 @@
1
+ export const config = {
2
+ fileserver_url: process.env.FILESERVER_URL || "https://uploads.secure-link.services.stubber.com/api/v1",
3
+ };
@@ -1,16 +1,16 @@
1
1
  import * as z from "zod";
2
2
 
3
- export const stubber_schema = z.object({
3
+ export const stubber_schema = z.looseObject({
4
4
  orguuid: z.uuid(),
5
5
  });
6
6
 
7
- export const task_schema = z.object({
7
+ export const task_schema = z.looseObject({
8
8
  tasktype: z.string().min(1),
9
9
  task_name: z.string().min(1),
10
10
  params: z.any(),
11
11
  _stubber: stubber_schema,
12
12
  });
13
13
 
14
- export const task_payload_schema = z.object({
14
+ export const task_payload_schema = z.looseObject({
15
15
  task: task_schema,
16
16
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubber/virtual-worker",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Template to easily create a node app and keep development standards",
5
5
  "main": "app.js",
6
6
  "directories": {
@@ -33,8 +33,10 @@
33
33
  "body-parser": "^1.20.3",
34
34
  "dotenv": "^16.0.3",
35
35
  "express": "^4.18.2",
36
+ "file-type": "^21.0.0",
36
37
  "jsonata": "^2.1.0",
37
38
  "lodash-es": "^4.17.21",
39
+ "mime-types": "^3.0.1",
38
40
  "net": "^1.0.2",
39
41
  "playwright": "^1.53.0",
40
42
  "socket.io-client": "^4.8.1",