streamby-core 0.38.0 → 0.40.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +108 -1
  3. package/dist/adapters/database/connectionManager.js +55 -0
  4. package/dist/mcp/tools.js +41 -0
  5. package/dist/middleware/createRouter.js +11 -1
  6. package/dist/middleware/routes/ai.js +3 -3
  7. package/dist/middleware/routes/deliverable.d.ts +4 -2
  8. package/dist/middleware/routes/deliverable.js +16 -7
  9. package/dist/middleware/routes/export.js +16 -0
  10. package/dist/middleware/routes/media.js +7 -6
  11. package/dist/middleware/routes/project.js +36 -10
  12. package/dist/middleware/routes/renderFarm.js +4 -77
  13. package/dist/middleware/routes/storage.js +13 -27
  14. package/dist/middleware/routes/worker.d.ts +7 -0
  15. package/dist/middleware/routes/worker.js +125 -0
  16. package/dist/middleware/routes/workerCallback.d.ts +5 -0
  17. package/dist/middleware/routes/workerCallback.js +31 -0
  18. package/dist/services/aiProcessor.js +11 -11
  19. package/dist/services/apiTokenPermissions.js +8 -7
  20. package/dist/services/executor.js +10 -10
  21. package/dist/services/jobQueue.d.ts +6 -4
  22. package/dist/services/jobQueue.js +28 -16
  23. package/dist/services/jobStore.d.ts +27 -0
  24. package/dist/services/jobStore.js +177 -0
  25. package/dist/services/mediaProcessor.js +16 -16
  26. package/dist/services/vfxProcessor.js +12 -12
  27. package/dist/services/workerJobs.d.ts +73 -0
  28. package/dist/services/workerJobs.js +459 -0
  29. package/dist/services/workers.d.ts +78 -0
  30. package/dist/services/workers.js +179 -0
  31. package/dist/types/index.d.ts +52 -2
  32. package/dist/utils/assetCategories.d.ts +4 -0
  33. package/dist/utils/assetCategories.js +21 -0
  34. package/dist/utils/builtinAccess.d.ts +1 -1
  35. package/dist/utils/builtinAccess.js +4 -3
  36. package/dist/utils/privateAddress.d.ts +10 -0
  37. package/dist/utils/privateAddress.js +85 -0
  38. package/dist/utils/sanitize.d.ts +1 -0
  39. package/dist/utils/sanitize.js +14 -0
  40. package/package.json +1 -1
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.summarizeProjectWorker = exports.projectWorkers = exports.PrivateEndpointError = exports.WorkerError = exports.DEFAULT_WORKER_TIMEOUT_SECONDS = exports.WORKER_PROVIDERS = exports.STORAGE_CATEGORIES = void 0;
7
+ exports.availableWorkers = availableWorkers;
8
+ exports.resolveWorker = resolveWorker;
9
+ exports.checkWorkerEndpoint = checkWorkerEndpoint;
10
+ exports.assertPublicEndpoint = assertPublicEndpoint;
11
+ exports.newWorkerConnection = newWorkerConnection;
12
+ const crypto_1 = __importDefault(require("crypto"));
13
+ const auth_1 = require("../utils/auth");
14
+ const builtinAccess_1 = require("../utils/builtinAccess");
15
+ const encryption_1 = require("../utils/encryption");
16
+ const privateAddress_1 = require("../utils/privateAddress");
17
+ // Processing workers: external services StreamBy sends jobs to (see workerJobs). A project
18
+ // can use the host's built-in ones (StreamByConfig.workers, gated by canUseBuiltin) and its
19
+ // own, connected from the project. A project's workers are kept in its
20
+ // `renderFarmConnections` list (render farms are workers too); each one's credential is
21
+ // encrypted, and it's used only while whoever connected it is still in the project.
22
+ exports.STORAGE_CATEGORIES = ['images', 'audios', 'videos', '3d-models'];
23
+ exports.WORKER_PROVIDERS = ['streamby-worker', 'flamenco', 'deadline', 'rebusfarm', 'sheepit', 'custom'];
24
+ exports.DEFAULT_WORKER_TIMEOUT_SECONDS = 3600;
25
+ class WorkerError extends Error {
26
+ constructor(message, status = 400) {
27
+ super(message);
28
+ this.status = status;
29
+ }
30
+ }
31
+ exports.WorkerError = WorkerError;
32
+ /** A worker endpoint at a local or private address: never sent to, whatever the attempt. */
33
+ class PrivateEndpointError extends WorkerError {
34
+ }
35
+ exports.PrivateEndpointError = PrivateEndpointError;
36
+ const listOf = (value) => (Array.isArray(value) ? value.filter((v) => typeof v === 'string') : []);
37
+ const projectWorkers = (project) => Array.isArray(project?.renderFarmConnections) ? project.renderFarmConnections : [];
38
+ exports.projectWorkers = projectWorkers;
39
+ const builtinSummary = (worker) => ({
40
+ source: 'builtin', id: worker.id, name: worker.name, provider: 'streamby-worker',
41
+ kinds: worker.kinds ?? [], categories: worker.categories ?? [], autoKind: worker.autoKind ?? null,
42
+ description: null, hasCredential: !!worker.credential, createdBy: null,
43
+ });
44
+ const projectSummary = (worker) => ({
45
+ source: 'project', id: worker.id, name: worker.name, provider: worker.provider,
46
+ kinds: listOf(worker.kinds), categories: listOf(worker.categories),
47
+ autoKind: worker.autoKind ?? null, description: worker.description ?? null,
48
+ hasCredential: !!worker.encryptedCredential, createdBy: worker.createdBy ?? null,
49
+ });
50
+ /** The workers a project's member can send jobs to: the project's own, and the host's they may use. */
51
+ async function availableWorkers(project, auth, config) {
52
+ const builtins = [];
53
+ for (const worker of config.workers ?? []) {
54
+ if (await (0, builtinAccess_1.assertBuiltinAccess)(auth, worker.id, config, 'worker'))
55
+ builtins.push(builtinSummary(worker));
56
+ }
57
+ return [...builtins, ...(0, exports.projectWorkers)(project).map(projectSummary)];
58
+ }
59
+ /** A worker of the project, or of the host, ready to be sent a job as `auth`. */
60
+ async function resolveWorker(project, ref, auth, config) {
61
+ if (ref.source === 'builtin') {
62
+ const worker = (config.workers ?? []).find(w => w.id === ref.id);
63
+ if (!worker)
64
+ throw new WorkerError('Worker not found', 404);
65
+ if (!(await (0, builtinAccess_1.assertBuiltinAccess)(auth, worker.id, config, 'worker')))
66
+ throw new WorkerError('This worker is not available to you', 403);
67
+ return {
68
+ ref: { source: 'builtin', id: worker.id, name: worker.name },
69
+ endpoint: worker.endpoint, credential: worker.credential ?? null,
70
+ kinds: worker.kinds ?? [], categories: worker.categories ?? [], autoKind: worker.autoKind ?? null,
71
+ timeoutSeconds: worker.timeoutSeconds ?? exports.DEFAULT_WORKER_TIMEOUT_SECONDS,
72
+ };
73
+ }
74
+ if (ref.source === 'project') {
75
+ const worker = (0, exports.projectWorkers)(project).find(w => w.id === ref.id);
76
+ if (!worker)
77
+ throw new WorkerError('Worker not found', 404);
78
+ // Tied to whoever connected it: once they leave the project, its credential isn't used.
79
+ if (worker.createdBy && !(0, auth_1.activeMemberOf)(project, worker.createdBy)) {
80
+ throw new WorkerError('Whoever connected this worker is no longer in the project: connect it again', 409);
81
+ }
82
+ return {
83
+ ref: { source: 'project', id: worker.id, name: worker.name },
84
+ endpoint: worker.apiUrl, credential: worker.encryptedCredential ? (0, encryption_1.decrypt)(worker.encryptedCredential) : null,
85
+ kinds: listOf(worker.kinds), categories: listOf(worker.categories),
86
+ autoKind: worker.autoKind ?? null, timeoutSeconds: worker.timeoutSeconds ?? exports.DEFAULT_WORKER_TIMEOUT_SECONDS,
87
+ };
88
+ }
89
+ throw new WorkerError("A worker's source is 'builtin' or 'project'");
90
+ }
91
+ // A project's worker is reached from StreamBy's server: only over https, and never at a local
92
+ // or private address (unless the host allows it, for development). The address a name
93
+ // resolves to is checked too, when it's connected and right before each job is sent.
94
+ function checkWorkerEndpoint(value, config) {
95
+ let url;
96
+ try {
97
+ url = new URL(String(value));
98
+ }
99
+ catch {
100
+ throw new WorkerError('endpoint must be a URL');
101
+ }
102
+ const local = config.workerOptions?.allowLocalEndpoints === true;
103
+ if (url.username || url.password)
104
+ throw new WorkerError("endpoint can't carry credentials: give them as credential");
105
+ if (!local && url.protocol !== 'https:')
106
+ throw new WorkerError('endpoint must use https');
107
+ if (!local && (0, privateAddress_1.isPrivateHostName)(url.hostname))
108
+ throw new WorkerError("endpoint can't be a local or private address");
109
+ if (!['https:', 'http:'].includes(url.protocol))
110
+ throw new WorkerError('endpoint must use https');
111
+ return url.toString();
112
+ }
113
+ /**
114
+ * Throws if a project's worker endpoint resolves, now, to a local or private address (a
115
+ * public-looking name pointing at the server's own network).
116
+ */
117
+ async function assertPublicEndpoint(endpoint, config) {
118
+ if (config.workerOptions?.allowLocalEndpoints === true)
119
+ return;
120
+ let host;
121
+ try {
122
+ host = new URL(endpoint).hostname;
123
+ }
124
+ catch {
125
+ throw new WorkerError('endpoint must be a URL');
126
+ }
127
+ let isPrivate;
128
+ try {
129
+ isPrivate = await (0, privateAddress_1.resolvesToPrivateAddress)(host);
130
+ }
131
+ catch {
132
+ throw new WorkerError(`The worker's host (${host}) couldn't be resolved`, 400);
133
+ }
134
+ if (isPrivate)
135
+ throw new PrivateEndpointError("The worker's endpoint resolves to a local or private address", 400);
136
+ }
137
+ /** A project's new worker, checked and with its credential encrypted. */
138
+ function newWorkerConnection(input, projectId, userId, config) {
139
+ const name = typeof input.name === 'string' ? input.name.trim() : '';
140
+ if (!name)
141
+ throw new WorkerError('A worker needs a name');
142
+ const provider = input.provider === undefined ? 'streamby-worker' : input.provider;
143
+ if (!exports.WORKER_PROVIDERS.includes(provider))
144
+ throw new WorkerError(`provider must be one of ${exports.WORKER_PROVIDERS.join(', ')}`);
145
+ const endpoint = checkWorkerEndpoint(input.endpoint, config);
146
+ const kinds = listOf(input.kinds).map(k => k.trim()).filter(Boolean);
147
+ if (!kinds.length || kinds.length !== listOf(input.kinds).length)
148
+ throw new WorkerError('kinds must list what the worker does (e.g. gltf-optimize)');
149
+ const categories = listOf(input.categories);
150
+ if (!categories.length || !categories.every(c => exports.STORAGE_CATEGORIES.includes(c))) {
151
+ throw new WorkerError(`categories must list asset categories it takes: ${exports.STORAGE_CATEGORIES.join(', ')}`);
152
+ }
153
+ const autoKind = input.autoKind === undefined || input.autoKind === null || input.autoKind === '' ? null : input.autoKind;
154
+ if (autoKind !== null && (typeof autoKind !== 'string' || !kinds.includes(autoKind)))
155
+ throw new WorkerError('autoKind must be one of its kinds, or null');
156
+ const timeoutSeconds = input.timeoutSeconds === undefined ? undefined : Number(input.timeoutSeconds);
157
+ if (timeoutSeconds !== undefined && !(Number.isInteger(timeoutSeconds) && timeoutSeconds >= 60 && timeoutSeconds <= 7 * 86400)) {
158
+ throw new WorkerError('timeoutSeconds must be a whole number of seconds, from 60 to a week');
159
+ }
160
+ const credential = typeof input.credential === 'string' && input.credential ? input.credential : null;
161
+ if (credential && !(0, encryption_1.isEncryptionKeySet)())
162
+ throw new WorkerError('Credentials cannot be stored: no encryption key is set', 500);
163
+ return {
164
+ id: crypto_1.default.randomUUID(),
165
+ projectId,
166
+ name,
167
+ provider: provider,
168
+ apiUrl: endpoint,
169
+ ...(credential ? { encryptedCredential: (0, encryption_1.encrypt)(credential) } : {}),
170
+ kinds: [...new Set(kinds)],
171
+ categories: [...new Set(categories)],
172
+ autoKind: autoKind,
173
+ ...(timeoutSeconds !== undefined ? { timeoutSeconds } : {}),
174
+ ...(typeof input.description === 'string' && input.description.trim() ? { description: input.description.trim() } : {}),
175
+ createdBy: userId,
176
+ createdAt: new Date(),
177
+ };
178
+ }
179
+ exports.summarizeProjectWorker = projectSummary;
@@ -149,7 +149,14 @@ export interface StreamByConfig {
149
149
  websocket?: {
150
150
  server: WebSocketServer;
151
151
  };
152
- canUseBuiltin?: (auth: Auth, builtinId: string, kind: 'database' | 'storage') => boolean | Promise<boolean>;
152
+ canUseBuiltin?: (auth: Auth, builtinId: string, kind: 'database' | 'storage' | 'worker') => boolean | Promise<boolean>;
153
+ workers?: WorkerConfig[];
154
+ workerOptions?: {
155
+ callbackBaseUrl?: string;
156
+ maxAttempts?: number;
157
+ sweepIntervalMs?: number;
158
+ allowLocalEndpoints?: boolean;
159
+ };
153
160
  oauthProviders?: {
154
161
  jira?: OAuthClientConfig;
155
162
  google?: OAuthClientConfig;
@@ -320,7 +327,7 @@ export interface QcReportRecord {
320
327
  overallPassed: boolean;
321
328
  generatedAt: Date;
322
329
  }
323
- export type JobType = 'ingest' | 'transcode' | 'caption' | 'thumbnail' | 'render' | 'format-convert' | 'lod' | 'distribute' | 'qc' | 'transcription' | 'upscale' | 'generate-asset';
330
+ export type JobType = 'ingest' | 'transcode' | 'caption' | 'thumbnail' | 'render' | 'format-convert' | 'lod' | 'distribute' | 'qc' | 'transcription' | 'upscale' | 'generate-asset' | 'process';
324
331
  export type AiProvider = 'openai' | 'deepgram' | 'assemblyai' | 'stabilityai' | 'meshy' | 'elevenlabs' | 'custom';
325
332
  export interface PipelineSuggestion {
326
333
  rationale: string;
@@ -439,7 +446,50 @@ export interface JobRecord {
439
446
  error?: string;
440
447
  createdAt: Date;
441
448
  updatedAt: Date;
449
+ worker?: WorkerRef | null;
450
+ kind?: string | null;
451
+ profile?: Record<string, unknown> | null;
452
+ versionId?: string | null;
453
+ resultMode?: WorkerResultMode | null;
454
+ attempts?: number;
455
+ maxAttempts?: number;
456
+ nextAttemptAt?: Date | null;
457
+ leaseUntil?: Date | null;
458
+ callbackHash?: string | null;
459
+ upload?: {
460
+ storageKey: string;
461
+ fileName: string;
462
+ contentType: string;
463
+ } | null;
464
+ report?: Record<string, unknown> | null;
465
+ result?: {
466
+ assetId: string;
467
+ versionId: string;
468
+ mode: WorkerResultMode;
469
+ } | null;
470
+ }
471
+ export type WorkerResultMode = 'version' | 'derived';
472
+ export type WorkerSource = 'builtin' | 'project';
473
+ export interface WorkerRef {
474
+ source: WorkerSource;
475
+ id: string;
476
+ name: string;
442
477
  }
478
+ /**
479
+ * A processing worker a host offers every project (`StreamByConfig.workers`). StreamBy sends
480
+ * it jobs (see the README's worker contract) and keeps what it makes.
481
+ */
482
+ export interface WorkerConfig {
483
+ id: string;
484
+ name: string;
485
+ endpoint: string;
486
+ credential?: string;
487
+ kinds: string[];
488
+ categories: StorageCategoryName[];
489
+ autoKind?: string | null;
490
+ timeoutSeconds?: number;
491
+ }
492
+ export type StorageCategoryName = 'images' | 'audios' | 'videos' | '3d-models';
443
493
  export interface AssetMetadataRecord {
444
494
  assetId: string;
445
495
  projectId: string;
@@ -0,0 +1,4 @@
1
+ export type StorageCategory = 'images' | 'audios' | 'videos' | '3d-models';
2
+ export declare const VALID_CATEGORIES: StorageCategory[];
3
+ /** Whether a file (its content type, or for 3D models its extension) belongs in a category. */
4
+ export declare function validateContentType(contentType: string, filename: string, category: StorageCategory): boolean;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // The categories assets are kept in, and what each one takes.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.VALID_CATEGORIES = void 0;
5
+ exports.validateContentType = validateContentType;
6
+ exports.VALID_CATEGORIES = ['images', 'audios', 'videos', '3d-models'];
7
+ const VALID_3D_EXTENSIONS = new Set(['.glb', '.gltf', '.obj', '.fbx', '.stl', '.ply']);
8
+ /** Whether a file (its content type, or for 3D models its extension) belongs in a category. */
9
+ function validateContentType(contentType, filename, category) {
10
+ if (category === 'images')
11
+ return contentType.startsWith('image/');
12
+ if (category === 'audios')
13
+ return contentType.startsWith('audio/');
14
+ if (category === 'videos')
15
+ return contentType.startsWith('video/');
16
+ if (category === '3d-models') {
17
+ const ext = filename.split('.').pop()?.toLowerCase();
18
+ return ext ? VALID_3D_EXTENSIONS.has(`.${ext}`) : false;
19
+ }
20
+ return false;
21
+ }
@@ -1,5 +1,5 @@
1
1
  import { Auth, StreamByConfig } from '../types';
2
- export declare function assertBuiltinAccess(auth: Auth, builtinId: string, config: StreamByConfig, kind: 'database' | 'storage'): Promise<boolean>;
2
+ export declare function assertBuiltinAccess(auth: Auth, builtinId: string, config: StreamByConfig, kind: 'database' | 'storage' | 'worker'): Promise<boolean>;
3
3
  export declare function isBuiltinStorageId(id: string, config: StreamByConfig): boolean;
4
4
  export declare function isBuiltinDb(id: string, config: StreamByConfig): boolean;
5
5
  export declare const BUILTIN_STORAGE_DISPLAY: Record<string, string>;
@@ -4,9 +4,10 @@ exports.BUILTIN_DB_DISPLAY = exports.BUILTIN_STORAGE_DISPLAY = void 0;
4
4
  exports.assertBuiltinAccess = assertBuiltinAccess;
5
5
  exports.isBuiltinStorageId = isBuiltinStorageId;
6
6
  exports.isBuiltinDb = isBuiltinDb;
7
- // Gates access to a built-in database/storage provider. Delegates to config.canUseBuiltin
8
- // when the mounting app implements it (e.g. Nhexa-API checking user_subscriptions);
9
- // defaults to allow when absent, preserving pre-BYOC behavior for deploys without gating.
7
+ // Gates access to a built-in database, storage provider or worker. Delegates to
8
+ // config.canUseBuiltin when the mounting app implements it (e.g. Nhexa-API checking
9
+ // user_subscriptions); defaults to allow when absent, preserving pre-BYOC behavior for deploys
10
+ // without gating.
10
11
  async function assertBuiltinAccess(auth, builtinId, config, kind) {
11
12
  if (!config.canUseBuiltin)
12
13
  return true;
@@ -0,0 +1,10 @@
1
+ /** Whether an IP address (v4 or v6) is a local, private or otherwise non-public one. */
2
+ export declare function isPrivateIp(ip: string): boolean;
3
+ /** Whether a URL's host, as written, is a local name or a non-public IP address. */
4
+ export declare function isPrivateHostName(hostname: string): boolean;
5
+ /**
6
+ * Whether a host, resolved now, reaches a non-public address: a name can resolve to one even
7
+ * if it doesn't look like it, and resolve differently later, so it's checked right before each
8
+ * request.
9
+ */
10
+ export declare function resolvesToPrivateAddress(hostname: string): Promise<boolean>;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPrivateIp = isPrivateIp;
4
+ exports.isPrivateHostName = isPrivateHostName;
5
+ exports.resolvesToPrivateAddress = resolvesToPrivateAddress;
6
+ const dns_1 = require("dns");
7
+ const net_1 = require("net");
8
+ // Whether an address is one StreamBy's server must not be made to reach on someone's behalf: a
9
+ // loopback, private, link-local, shared, benchmarking, multicast or reserved one (anything a
10
+ // request from the server could use to reach its own network).
11
+ const ipv4Parts = (ip) => ip.split('.').map(Number);
12
+ function isPrivateIpv4(ip) {
13
+ const [a, b] = ipv4Parts(ip);
14
+ return a === 0 // 0.0.0.0/8 ("this" network)
15
+ || a === 10 // 10.0.0.0/8
16
+ || a === 127 // 127.0.0.0/8 loopback
17
+ || (a === 100 && b >= 64 && b <= 127) // 100.64.0.0/10 shared (CGNAT)
18
+ || (a === 169 && b === 254) // 169.254.0.0/16 link-local (cloud metadata)
19
+ || (a === 172 && b >= 16 && b <= 31) // 172.16.0.0/12
20
+ || (a === 192 && b === 0 && ipv4Parts(ip)[2] === 0) // 192.0.0.0/24 protocol assignments
21
+ || (a === 192 && b === 168) // 192.168.0.0/16
22
+ || (a === 198 && (b === 18 || b === 19)) // 198.18.0.0/15 benchmarking
23
+ || a >= 224; // 224.0.0.0/4 multicast, and 240.0.0.0/4 reserved
24
+ }
25
+ // An IPv6 address's 8 groups, expanded (an embedded IPv4 written as dotted quad included).
26
+ function ipv6Groups(ip) {
27
+ let text = ip.toLowerCase().replace(/%.*$/, '');
28
+ const dotted = text.match(/(\d+\.\d+\.\d+\.\d+)$/);
29
+ if (dotted) {
30
+ const [a, b, c, d] = ipv4Parts(dotted[1]);
31
+ text = text.slice(0, -dotted[1].length) + `${((a << 8) | b).toString(16)}:${((c << 8) | d).toString(16)}`;
32
+ }
33
+ const [head, tail] = text.split('::');
34
+ const left = head ? head.split(':') : [];
35
+ const right = tail !== undefined && tail ? tail.split(':') : [];
36
+ const missing = 8 - left.length - right.length;
37
+ if (tail === undefined ? left.length !== 8 : missing < 0)
38
+ return null;
39
+ return [...left, ...Array(tail === undefined ? 0 : missing).fill('0'), ...right].map(group => parseInt(group, 16));
40
+ }
41
+ function isPrivateIpv6(ip) {
42
+ const groups = ipv6Groups(ip);
43
+ if (!groups || groups.some(Number.isNaN))
44
+ return true; // unreadable: not reached
45
+ if (groups.every(g => g === 0))
46
+ return true; // :: unspecified
47
+ if (groups.slice(0, 7).every(g => g === 0) && groups[7] === 1)
48
+ return true; // ::1 loopback
49
+ // ::ffff:a.b.c.d (IPv4-mapped): judged as the IPv4 address it carries.
50
+ if (groups.slice(0, 5).every(g => g === 0) && groups[5] === 0xffff) {
51
+ return isPrivateIpv4(`${groups[6] >> 8}.${groups[6] & 0xff}.${groups[7] >> 8}.${groups[7] & 0xff}`);
52
+ }
53
+ return (groups[0] & 0xfe00) === 0xfc00 // fc00::/7 unique local (e.g. fdaa::, Fly's network)
54
+ || (groups[0] & 0xffc0) === 0xfe80 // fe80::/10 link-local
55
+ || (groups[0] & 0xff00) === 0xff00; // ff00::/8 multicast
56
+ }
57
+ /** Whether an IP address (v4 or v6) is a local, private or otherwise non-public one. */
58
+ function isPrivateIp(ip) {
59
+ const bare = ip.replace(/^\[|\]$/g, '');
60
+ if ((0, net_1.isIP)(bare) === 4)
61
+ return isPrivateIpv4(bare);
62
+ if ((0, net_1.isIP)(bare) === 6)
63
+ return isPrivateIpv6(bare);
64
+ return true;
65
+ }
66
+ const LOCAL_NAME = /^(localhost|.*\.localhost|.*\.local|.*\.internal)$/i;
67
+ /** Whether a URL's host, as written, is a local name or a non-public IP address. */
68
+ function isPrivateHostName(hostname) {
69
+ const bare = hostname.replace(/^\[|\]$/g, '');
70
+ return (0, net_1.isIP)(bare) ? isPrivateIp(bare) : LOCAL_NAME.test(bare);
71
+ }
72
+ /**
73
+ * Whether a host, resolved now, reaches a non-public address: a name can resolve to one even
74
+ * if it doesn't look like it, and resolve differently later, so it's checked right before each
75
+ * request.
76
+ */
77
+ async function resolvesToPrivateAddress(hostname) {
78
+ const bare = hostname.replace(/^\[|\]$/g, '');
79
+ if (isPrivateHostName(bare))
80
+ return true;
81
+ if ((0, net_1.isIP)(bare))
82
+ return false;
83
+ const addresses = await dns_1.promises.lookup(bare, { all: true });
84
+ return !addresses.length || addresses.some(({ address }) => isPrivateIp(address));
85
+ }
@@ -14,5 +14,6 @@ export declare function sanitizeIntegrationConnection<T extends object>(conn: T)
14
14
  export declare function sanitizeIntegrationConnections<T extends object>(conns: T[] | undefined): (T & {
15
15
  hasCredential: true;
16
16
  })[];
17
+ export declare function exportSummaries(exports: any[] | undefined): any[];
17
18
  export declare function sanitizeProject(project: any): any;
18
19
  export declare function sanitizeUserIntegration(integration: any): any;
@@ -4,6 +4,7 @@ exports.sanitizeConnection = sanitizeConnection;
4
4
  exports.sanitizeConnections = sanitizeConnections;
5
5
  exports.sanitizeIntegrationConnection = sanitizeIntegrationConnection;
6
6
  exports.sanitizeIntegrationConnections = sanitizeIntegrationConnections;
7
+ exports.exportSummaries = exportSummaries;
7
8
  exports.sanitizeProject = sanitizeProject;
8
9
  exports.sanitizeUserIntegration = sanitizeUserIntegration;
9
10
  function sanitizeConnection(conn) {
@@ -23,6 +24,19 @@ function sanitizeIntegrationConnection(conn) {
23
24
  function sanitizeIntegrationConnections(conns) {
24
25
  return (conns ?? []).map(sanitizeIntegrationConnection);
25
26
  }
27
+ // A project's exports as they're listed: what each one is, not its schema nor its credentials.
28
+ // The same in the project (GET /projects/:id) and in its own list (GET /projects/:id/exports).
29
+ function exportSummaries(exports) {
30
+ return (exports || []).map((e) => ({
31
+ id: e.id,
32
+ name: e.name,
33
+ description: e.description,
34
+ method: e.method,
35
+ private: e.private,
36
+ useConnections: e.useConnections,
37
+ useCredentials: e.useCredentials,
38
+ }));
39
+ }
26
40
  function sanitizeProject(project) {
27
41
  if (!project)
28
42
  return project;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamby-core",
3
- "version": "0.38.0",
3
+ "version": "0.40.0",
4
4
  "description": "StreamBy middleware framework for media storage management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",