cynx-lib 1.3.40 → 1.3.42

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,61 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var session_exports = {};
19
+ __export(session_exports, {
20
+ default: () => session_default,
21
+ deriveDeviceName: () => deriveDeviceName,
22
+ genSessionToken: () => genSessionToken
23
+ });
24
+ module.exports = __toCommonJS(session_exports);
25
+ var import_uid = require("./uid.js");
26
+ function genSessionToken(byteLength = 32) {
27
+ const bytes = (0, import_uid.getRandomBytes)(byteLength);
28
+ let hex = "";
29
+ for (let i = 0; i < bytes.length; i++) {
30
+ hex += bytes[i].toString(16).padStart(2, "0");
31
+ }
32
+ return `st_${hex}`;
33
+ }
34
+ function deriveDeviceName(req, userAgent = "") {
35
+ const headers = req?.headers || {};
36
+ const fromHeader = headers["x-device-name"] || headers["x-pc-name"] || headers["x-computer-name"] || "";
37
+ if (typeof fromHeader === "string" && fromHeader.trim()) {
38
+ return fromHeader.trim().slice(0, 120);
39
+ }
40
+ const fromBody = req?.body?.deviceName || req?.body?.device_name || req?.body?.pcName || req?.body?.pc_name;
41
+ if (typeof fromBody === "string" && fromBody.trim()) {
42
+ return fromBody.trim().slice(0, 120);
43
+ }
44
+ const ua = userAgent || headers["user-agent"] || "";
45
+ if (/windows/i.test(ua)) return "Windows PC";
46
+ if (/macintosh|mac os/i.test(ua)) return "Mac";
47
+ if (/iphone/i.test(ua)) return "iPhone";
48
+ if (/ipad/i.test(ua)) return "iPad";
49
+ if (/android/i.test(ua)) return "Android Device";
50
+ if (/linux/i.test(ua)) return "Linux PC";
51
+ return "Desktop PC";
52
+ }
53
+ var session_default = {
54
+ genSessionToken,
55
+ deriveDeviceName
56
+ };
57
+ // Annotate the CommonJS export names for ESM import in node:
58
+ 0 && (module.exports = {
59
+ deriveDeviceName,
60
+ genSessionToken
61
+ });
@@ -0,0 +1,344 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var storage_exports = {};
19
+ __export(storage_exports, {
20
+ StorageClient: () => StorageClient,
21
+ createStorageClient: () => createStorageClient,
22
+ default: () => storage_default,
23
+ formatBytes: () => formatBytes,
24
+ getCdnUrl: () => getCdnUrl,
25
+ getFileTypeCategory: () => getFileTypeCategory
26
+ });
27
+ module.exports = __toCommonJS(storage_exports);
28
+ const isNode = typeof process !== "undefined" && Boolean(process.versions?.node);
29
+ function getDefaultBaseUrl() {
30
+ if (isNode && process.env?.STORAGE_SERVER_URL) {
31
+ return process.env.STORAGE_SERVER_URL;
32
+ }
33
+ if (typeof window !== "undefined" && window.location?.origin) {
34
+ return window.location.origin;
35
+ }
36
+ return "";
37
+ }
38
+ function getDefaultApiKey() {
39
+ if (isNode && (process.env?.STORAGE_SERVER_API_KEY || process.env?.STORAGE_API_KEY)) {
40
+ return process.env.STORAGE_SERVER_API_KEY || process.env.STORAGE_API_KEY;
41
+ }
42
+ return "";
43
+ }
44
+ function formatBytes(bytes) {
45
+ const num = Number(bytes) || 0;
46
+ if (num === 0) return "0 B";
47
+ const k = 1024;
48
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
49
+ const i = Math.floor(Math.log(num) / Math.log(k));
50
+ const val = (num / Math.pow(k, i)).toFixed(2);
51
+ return `${val} ${sizes[i]}`;
52
+ }
53
+ function getFileTypeCategory(mimeType = "", filename = "") {
54
+ const mt = (mimeType || "").toLowerCase();
55
+ const fn = (filename || "").toLowerCase();
56
+ if (mt.startsWith("image/") || /\.(jpg|jpeg|png|gif|webp|bmp|svg|avif|ico)$/i.test(fn)) {
57
+ return "image";
58
+ }
59
+ if (mt === "application/pdf" || /\.pdf$/i.test(fn)) {
60
+ return "pdf";
61
+ }
62
+ if (mt.startsWith("video/") || /\.(mp4|webm|mkv|mov|avi|flv|wmv|m4v)$/i.test(fn)) {
63
+ return "video";
64
+ }
65
+ if (mt.startsWith("audio/") || /\.(mp3|wav|ogg|m4a|aac|flac)$/i.test(fn)) {
66
+ return "audio";
67
+ }
68
+ if (mt.includes("word") || mt.includes("excel") || mt.includes("spreadsheet") || mt.includes("presentation") || mt.includes("csv") || /\.(doc|docx|xls|xlsx|csv|ppt|pptx|txt|rtf|md)$/i.test(fn)) {
69
+ return "document";
70
+ }
71
+ if (mt.includes("zip") || mt.includes("tar") || mt.includes("compressed") || /\.(zip|tar|gz|7z|rar)$/i.test(fn)) {
72
+ return "archive";
73
+ }
74
+ return "other";
75
+ }
76
+ function getCdnUrl(fileId, filename = "", baseUrl = "") {
77
+ if (!fileId) return "";
78
+ const base = (baseUrl || getDefaultBaseUrl() || "").replace(/\/+$/, "");
79
+ const cleanId = String(fileId).trim();
80
+ const suffix = filename ? `/${encodeURIComponent(filename)}` : "";
81
+ return `${base}/cdn/${encodeURIComponent(cleanId)}${suffix}`;
82
+ }
83
+ class StorageClient {
84
+ /**
85
+ * @param {object} [options={}]
86
+ * @param {string} [options.baseUrl] Base URL of the storage server (e.g. "https://storage.domain.com" or "http://localhost:5000")
87
+ * @param {string} [options.apiKey] API Key with upload/read/delete permissions
88
+ * @param {number} [options.timeout=30000] Request timeout in milliseconds
89
+ * @param {Function} [options.fetch] Custom fetch implementation (optional)
90
+ */
91
+ constructor(options = {}) {
92
+ this.baseUrl = (options.baseUrl || getDefaultBaseUrl() || "").replace(/\/+$/, "");
93
+ this.apiKey = options.apiKey || getDefaultApiKey() || "";
94
+ this.timeout = options.timeout || 3e4;
95
+ this.fetchFn = options.fetch || (typeof globalThis !== "undefined" ? globalThis.fetch?.bind(globalThis) : null);
96
+ }
97
+ /**
98
+ * Update configuration options.
99
+ * @param {object} options
100
+ */
101
+ configure(options = {}) {
102
+ if (options.baseUrl !== void 0) this.baseUrl = String(options.baseUrl || "").replace(/\/+$/, "");
103
+ if (options.apiKey !== void 0) this.apiKey = String(options.apiKey || "");
104
+ if (options.timeout !== void 0) this.timeout = Number(options.timeout) || 3e4;
105
+ if (options.fetch !== void 0) this.fetchFn = options.fetch;
106
+ return this;
107
+ }
108
+ /**
109
+ * Internal HTTP request helper.
110
+ * @private
111
+ */
112
+ async _request(path, { method = "GET", headers = {}, body, apiKey, signal, timeout } = {}) {
113
+ if (!this.fetchFn) {
114
+ throw new Error("[cynx/storage] No fetch implementation available in current environment.");
115
+ }
116
+ const url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
117
+ const key = apiKey !== void 0 ? apiKey : this.apiKey;
118
+ const reqHeaders = { ...headers };
119
+ if (key) {
120
+ reqHeaders["x-api-key"] = key;
121
+ }
122
+ const abortCtrl = typeof AbortController !== "undefined" ? new AbortController() : null;
123
+ const reqTimeout = timeout || this.timeout;
124
+ let timer = null;
125
+ if (abortCtrl && reqTimeout > 0) {
126
+ timer = setTimeout(() => abortCtrl.abort(new Error(`Storage request timeout after ${reqTimeout}ms`)), reqTimeout);
127
+ }
128
+ const activeSignal = signal || abortCtrl?.signal;
129
+ try {
130
+ const response = await this.fetchFn(url, {
131
+ method,
132
+ headers: reqHeaders,
133
+ body,
134
+ signal: activeSignal
135
+ });
136
+ if (timer) clearTimeout(timer);
137
+ if (!response.ok) {
138
+ let errData = null;
139
+ try {
140
+ errData = await response.json();
141
+ } catch {
142
+ errData = { error: response.statusText || `HTTP ${response.status}` };
143
+ }
144
+ const error = new Error(errData.error || errData.message || `Storage Error: ${response.status}`);
145
+ error.status = response.status;
146
+ error.response = errData;
147
+ throw error;
148
+ }
149
+ return await response.json();
150
+ } catch (err) {
151
+ if (timer) clearTimeout(timer);
152
+ throw err;
153
+ }
154
+ }
155
+ /**
156
+ * Upload one or multiple files to the storage server.
157
+ *
158
+ * @param {object} params
159
+ * @param {File|Blob|Buffer|ArrayBuffer|Array<File|Blob|Buffer|object>} [params.files] File or array of files to upload
160
+ * @param {File|Blob|Buffer|ArrayBuffer} [params.file] Single file alias
161
+ * @param {string} [params.folder] Target subfolder in cloud storage
162
+ * @param {string} [params.entityType] Associated entity type (e.g. 'company', 'employee', 'inspection')
163
+ * @param {string|number} [params.entityId] Associated entity ID
164
+ * @param {string} [params.apiKey] Override API Key for this upload
165
+ * @param {AbortSignal} [params.signal] AbortSignal for cancellation
166
+ * @returns {Promise<{ files: Array<object>, file: object|null }>} Formatted upload result
167
+ */
168
+ async upload({
169
+ files,
170
+ file,
171
+ folder,
172
+ entityType,
173
+ entityId,
174
+ entity_type,
175
+ entity_id,
176
+ apiKey,
177
+ signal
178
+ } = {}) {
179
+ if (typeof FormData === "undefined") {
180
+ throw new Error("[cynx/storage] FormData is not available in the current environment.");
181
+ }
182
+ const formData = new FormData();
183
+ const rawFiles = files ? Array.isArray(files) ? files : [files] : file ? [file] : [];
184
+ if (rawFiles.length === 0) {
185
+ throw new Error("[cynx/storage] No file provided for upload.");
186
+ }
187
+ for (let i = 0; i < rawFiles.length; i++) {
188
+ const item = rawFiles[i];
189
+ if (!item) continue;
190
+ if (typeof File !== "undefined" && item instanceof File) {
191
+ formData.append("files", item, item.name);
192
+ } else if (typeof Blob !== "undefined" && item instanceof Blob) {
193
+ const name = item.name || `file_${Date.now()}_${i}`;
194
+ formData.append("files", item, name);
195
+ } else if (isNode && (Buffer.isBuffer(item) || item instanceof Uint8Array || item instanceof ArrayBuffer)) {
196
+ const name = item.name || item.filename || `file_${Date.now()}_${i}`;
197
+ const blob = new Blob([item], { type: item.type || item.mimeType || "application/octet-stream" });
198
+ formData.append("files", blob, name);
199
+ } else if (item && typeof item === "object" && item.data) {
200
+ const name = item.name || item.filename || `file_${Date.now()}_${i}`;
201
+ const blob = item.data instanceof Blob ? item.data : new Blob([item.data], { type: item.type || item.mimeType || "application/octet-stream" });
202
+ formData.append("files", blob, name);
203
+ } else {
204
+ formData.append("files", item);
205
+ }
206
+ }
207
+ if (folder) formData.append("folder", folder);
208
+ const resolvedEntityType = entityType || entity_type;
209
+ const resolvedEntityId = entityId || entity_id;
210
+ if (resolvedEntityType) formData.append("entityType", resolvedEntityType);
211
+ if (resolvedEntityId) formData.append("entityId", String(resolvedEntityId));
212
+ const res = await this._request("/api/v1/upload", {
213
+ method: "POST",
214
+ body: formData,
215
+ apiKey,
216
+ signal
217
+ });
218
+ const fileList = res?.files ? res.files : Array.isArray(res) ? res : res ? [res] : [];
219
+ return {
220
+ files: fileList,
221
+ file: fileList[0] || null,
222
+ ...res
223
+ };
224
+ }
225
+ /**
226
+ * Get metadata and CDN URLs for a specific file by its ID.
227
+ *
228
+ * @param {string} fileId ID of the file (e.g. "sf_abc123")
229
+ * @param {object} [options]
230
+ * @param {string} [options.apiKey] Override API Key
231
+ * @param {AbortSignal} [options.signal] AbortSignal
232
+ * @returns {Promise<object>} File metadata record
233
+ */
234
+ async getFile(fileId, options = {}) {
235
+ if (!fileId) throw new Error("[cynx/storage] fileId is required.");
236
+ const cleanId = String(fileId).trim().replace(/\.[^/.]+$/, "");
237
+ return this._request(`/api/v1/files/${encodeURIComponent(cleanId)}`, {
238
+ method: "GET",
239
+ ...options
240
+ });
241
+ }
242
+ /**
243
+ * Get all files attached to a specific entity.
244
+ *
245
+ * @param {string} entityType Entity type (e.g. "company", "employee")
246
+ * @param {string|number} entityId Entity identifier
247
+ * @param {object} [options]
248
+ * @param {string} [options.apiKey] Override API Key
249
+ * @param {AbortSignal} [options.signal] AbortSignal
250
+ * @returns {Promise<Array<object>>} List of file metadata records
251
+ */
252
+ async getEntityFiles(entityType, entityId, options = {}) {
253
+ if (!entityType || entityId === void 0 || entityId === null) {
254
+ throw new Error("[cynx/storage] entityType and entityId are required.");
255
+ }
256
+ const res = await this._request(
257
+ `/api/v1/entity/${encodeURIComponent(entityType)}/${encodeURIComponent(entityId)}`,
258
+ { method: "GET", ...options }
259
+ );
260
+ return Array.isArray(res) ? res : [];
261
+ }
262
+ /**
263
+ * Delete a single file by ID.
264
+ *
265
+ * @param {string} fileId ID of the file to delete
266
+ * @param {object} [options]
267
+ * @param {string} [options.apiKey] Override API Key
268
+ * @returns {Promise<{ message: string, fileId: string }>}
269
+ */
270
+ async deleteFile(fileId, options = {}) {
271
+ if (!fileId) throw new Error("[cynx/storage] fileId is required.");
272
+ const cleanId = String(fileId).trim().replace(/\.[^/.]+$/, "");
273
+ return this._request(`/api/v1/files/${encodeURIComponent(cleanId)}`, {
274
+ method: "DELETE",
275
+ ...options
276
+ });
277
+ }
278
+ /**
279
+ * Delete all files belonging to a specific entity.
280
+ *
281
+ * @param {string} entityType Entity type
282
+ * @param {string|number} entityId Entity identifier
283
+ * @param {object} [options]
284
+ * @param {string} [options.apiKey] Override API Key
285
+ * @returns {Promise<{ message: string, deletedCount: number }>}
286
+ */
287
+ async deleteEntityFiles(entityType, entityId, options = {}) {
288
+ if (!entityType || entityId === void 0 || entityId === null) {
289
+ throw new Error("[cynx/storage] entityType and entityId are required.");
290
+ }
291
+ return this._request(
292
+ `/api/v1/entity/${encodeURIComponent(entityType)}/${encodeURIComponent(entityId)}`,
293
+ { method: "DELETE", ...options }
294
+ );
295
+ }
296
+ /**
297
+ * Generate a public CDN streaming/viewing URL for a file.
298
+ *
299
+ * @param {string} fileId File ID
300
+ * @param {string} [filename] Optional clean filename for browser save/view
301
+ * @returns {string} Complete CDN URL
302
+ */
303
+ getCdnUrl(fileId, filename = "") {
304
+ return getCdnUrl(fileId, filename, this.baseUrl);
305
+ }
306
+ /**
307
+ * Download a file from CDN as a standard Fetch Response.
308
+ *
309
+ * @param {string} fileId File ID
310
+ * @param {object} [options]
311
+ * @param {string} [options.filename]
312
+ * @param {AbortSignal} [options.signal]
313
+ * @returns {Promise<Response>}
314
+ */
315
+ async download(fileId, options = {}) {
316
+ if (!this.fetchFn) {
317
+ throw new Error("[cynx/storage] No fetch implementation available.");
318
+ }
319
+ const url = this.getCdnUrl(fileId, options.filename);
320
+ const res = await this.fetchFn(url, { signal: options.signal });
321
+ if (!res.ok) {
322
+ throw new Error(`Failed to download file: HTTP ${res.status} ${res.statusText}`);
323
+ }
324
+ return res;
325
+ }
326
+ }
327
+ function createStorageClient(options = {}) {
328
+ return new StorageClient(options);
329
+ }
330
+ var storage_default = {
331
+ StorageClient,
332
+ createStorageClient,
333
+ getCdnUrl,
334
+ getFileTypeCategory,
335
+ formatBytes
336
+ };
337
+ // Annotate the CommonJS export names for ESM import in node:
338
+ 0 && (module.exports = {
339
+ StorageClient,
340
+ createStorageClient,
341
+ formatBytes,
342
+ getCdnUrl,
343
+ getFileTypeCategory
344
+ });