socket-function 0.140.0 → 0.142.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.
@@ -54,6 +54,7 @@ export interface CallType<FncT extends FncType = FncType, FncName extends string
54
54
  functionName: FncName;
55
55
  args: unknown[];
56
56
  }
57
+ // NOTE: If you are in an HTTP call, and want the request, use getCurrentHTTPRequest at the start of your endpoint.
57
58
  export interface FullCallType<FncT extends FncType = FncType, FncName extends string = string> extends CallType<FncT, FncName> {
58
59
  nodeId: string;
59
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.140.0",
3
+ "version": "0.142.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -9,6 +9,7 @@ import { cacheLimited, lazy } from "../src/caching";
9
9
  import { formatNumber } from "../src/formatting/format";
10
10
  import { requireMain } from "./require";
11
11
  import path from "path";
12
+ import { getContentTypeFromBuffer, getExtContentType } from "./extMapper";
12
13
 
13
14
  const COMPRESS_CACHE_SIZE = 1024 * 1024 * 128;
14
15
 
@@ -89,6 +90,13 @@ let beforeEntryText: (string | (() => Promise<string>))[] = [];
89
90
  function injectHTMLBeforeStartup(text: string | (() => Promise<string>)) {
90
91
  beforeEntryText.push(text);
91
92
  }
93
+ let staticRoots: string[] = [];
94
+ function addStaticRoot(root: string) {
95
+ if (!root.endsWith("/")) {
96
+ root += "/";
97
+ }
98
+ staticRoots.push(root);
99
+ }
92
100
 
93
101
  type GetModulesResult = ReturnType<RequireControllerBase["getModules"]> extends Promise<infer T> ? T : never;
94
102
  export type GetModulesArgs = Parameters<RequireControllerBase["getModules"]>;
@@ -106,12 +114,54 @@ class RequireControllerBase {
106
114
  requireCalls?: string[];
107
115
  cacheTime?: number;
108
116
  }) {
117
+ let { requireCalls, cacheTime } = config || {};
118
+ let httpRequest = getCurrentHTTPRequest();
119
+ let headers: Record<string, string> = {
120
+ "Content-Type": "text/html"
121
+ };
122
+ if (cacheTime) {
123
+ headers["Cache-Control"] = `max-age=${Math.floor(cacheTime / 1000)}`;
124
+ }
125
+ if (httpRequest) {
126
+ let urlObj = new URL("https://" + httpRequest.headers.host + httpRequest.url);
127
+ if (urlObj.pathname !== "/" && !requireCalls?.length) {
128
+ if (urlObj.pathname.includes("..")) {
129
+ throw new Error(`Invalid path, may not have ".." in the path, ${urlObj.pathname}`);
130
+ }
131
+
132
+
133
+ let result: Buffer | undefined;
134
+ for (let root of staticRoots) {
135
+ let resolved = root + urlObj.pathname;
136
+ if (fs.existsSync(resolved)) {
137
+ let rootResolved = path.resolve(resolved);
138
+ let finalResolved = path.resolve(rootResolved);
139
+ if (!finalResolved.startsWith(root)) {
140
+ throw new Error(`Invalid access, did not stay in namespace: ${JSON.stringify(root)}, but escaped: ${JSON.stringify(finalResolved)}`);
141
+ }
142
+ result = await fs.promises.readFile(resolved);
143
+ break;
144
+ }
145
+ }
146
+ if (result) {
147
+ let contentType = getContentTypeFromBuffer(result);
148
+ if (!contentType) {
149
+ let ext = path.extname(urlObj.pathname);
150
+ contentType = getExtContentType(ext);
151
+ }
152
+ headers["Content-Type"] = contentType;
153
+ return setHTTPResultHeaders(result, headers);
154
+ }
155
+
156
+ throw new Error(`Static file not found, ${urlObj.pathname}, have static roots: ${JSON.stringify(staticRoots)}`);
157
+ }
158
+
159
+ }
109
160
  if (!this.rootResolvePath) {
110
161
  let dir = path.resolve(".");
111
162
  dir = dir.replaceAll("\\", "/");
112
163
  this.rootResolvePath = dir;
113
164
  }
114
- let { requireCalls, cacheTime } = config || {};
115
165
  let result = resolvedHTMLFile();
116
166
  if (beforeEntryText.length > 0) {
117
167
  let resolved: string[] = [];
@@ -142,12 +192,6 @@ class RequireControllerBase {
142
192
  } else {
143
193
  result = result.replace(ENTRY_TEMPLATE, "");
144
194
  }
145
- let headers: Record<string, string> = {
146
- "Content-Type": "text/html"
147
- };
148
- if (cacheTime) {
149
- headers["Cache-Control"] = `max-age=${Math.floor(cacheTime / 1000)}`;
150
- }
151
195
  return setHTTPResultHeaders(Buffer.from(result), headers);
152
196
  }
153
197
 
@@ -405,6 +449,7 @@ export const RequireController = SocketFunction.register(
405
449
  statics: {
406
450
  injectHTMLBeforeStartup,
407
451
  addMapGetModules,
452
+ addStaticRoot,
408
453
  }
409
454
  }
410
455
  );
@@ -0,0 +1,236 @@
1
+ export function getExtContentType(ext: string): string {
2
+ // Images
3
+ if (ext === ".svg") return "image/svg+xml";
4
+ if (ext === ".ico") return "image/x-icon";
5
+ if (ext === ".png") return "image/png";
6
+ if (ext === ".jpg") return "image/jpeg";
7
+ if (ext === ".jpeg") return "image/jpeg";
8
+ if (ext === ".gif") return "image/gif";
9
+ if (ext === ".webp") return "image/webp";
10
+ if (ext === ".avif") return "image/avif";
11
+ if (ext === ".heic") return "image/heic";
12
+ if (ext === ".heif") return "image/heif";
13
+ if (ext === ".bmp") return "image/bmp";
14
+ if (ext === ".tiff") return "image/tiff";
15
+ if (ext === ".tif") return "image/tiff";
16
+ if (ext === ".jxl") return "image/jxl";
17
+
18
+ // Audio
19
+ if (ext === ".mp3") return "audio/mpeg";
20
+ if (ext === ".wav") return "audio/wav";
21
+ if (ext === ".ogg") return "audio/ogg";
22
+ if (ext === ".m4a") return "audio/mp4";
23
+ if (ext === ".aac") return "audio/aac";
24
+ if (ext === ".flac") return "audio/flac";
25
+ if (ext === ".wma") return "audio/x-ms-wma";
26
+ if (ext === ".opus") return "audio/opus";
27
+
28
+ // Video
29
+ if (ext === ".mp4") return "video/mp4";
30
+ if (ext === ".webm") return "video/webm";
31
+ if (ext === ".mkv") return "video/x-matroska";
32
+ if (ext === ".avi") return "video/x-msvideo";
33
+ if (ext === ".mov") return "video/quicktime";
34
+ if (ext === ".wmv") return "video/x-ms-wmv";
35
+ if (ext === ".flv") return "video/x-flv";
36
+ if (ext === ".m4v") return "video/x-m4v";
37
+
38
+ // Web files
39
+ if (ext === ".html") return "text/html";
40
+ if (ext === ".htm") return "text/html";
41
+ if (ext === ".css") return "text/css";
42
+ if (ext === ".js") return "text/javascript";
43
+ if (ext === ".mjs") return "text/javascript";
44
+ if (ext === ".json") return "application/json";
45
+ if (ext === ".xml") return "application/xml";
46
+ if (ext === ".rss") return "application/rss+xml";
47
+ if (ext === ".atom") return "application/atom+xml";
48
+
49
+ // Documents
50
+ if (ext === ".pdf") return "application/pdf";
51
+ if (ext === ".doc") return "application/msword";
52
+ if (ext === ".docx") return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
53
+ if (ext === ".xls") return "application/vnd.ms-excel";
54
+ if (ext === ".xlsx") return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
55
+ if (ext === ".ppt") return "application/vnd.ms-powerpoint";
56
+ if (ext === ".pptx") return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
57
+ if (ext === ".odt") return "application/vnd.oasis.opendocument.text";
58
+ if (ext === ".ods") return "application/vnd.oasis.opendocument.spreadsheet";
59
+ if (ext === ".odp") return "application/vnd.oasis.opendocument.presentation";
60
+ if (ext === ".rtf") return "application/rtf";
61
+
62
+ // Text files
63
+ if (ext === ".txt") return "text/plain";
64
+ if (ext === ".md") return "text/markdown";
65
+ if (ext === ".csv") return "text/csv";
66
+ if (ext === ".log") return "text/plain";
67
+
68
+ // Programming files
69
+ if (ext === ".ts") return "text/typescript";
70
+ if (ext === ".tsx") return "text/typescript";
71
+ if (ext === ".jsx") return "text/javascript";
72
+ if (ext === ".py") return "text/x-python";
73
+ if (ext === ".java") return "text/x-java-source";
74
+ if (ext === ".c") return "text/x-c";
75
+ if (ext === ".cpp") return "text/x-c++";
76
+ if (ext === ".h") return "text/x-c";
77
+ if (ext === ".hpp") return "text/x-c++";
78
+ if (ext === ".cs") return "text/x-csharp";
79
+ if (ext === ".php") return "text/x-php";
80
+ if (ext === ".rb") return "text/x-ruby";
81
+ if (ext === ".go") return "text/x-go";
82
+ if (ext === ".rs") return "text/x-rust";
83
+ if (ext === ".swift") return "text/x-swift";
84
+ if (ext === ".kt") return "text/x-kotlin";
85
+ if (ext === ".scala") return "text/x-scala";
86
+ if (ext === ".sh") return "text/x-shellscript";
87
+ if (ext === ".bat") return "text/x-msdos-batch";
88
+ if (ext === ".ps1") return "text/x-powershell";
89
+
90
+ // Archives
91
+ if (ext === ".zip") return "application/zip";
92
+ if (ext === ".rar") return "application/vnd.rar";
93
+ if (ext === ".tar") return "application/x-tar";
94
+ if (ext === ".gz") return "application/gzip";
95
+ if (ext === ".bz2") return "application/x-bzip2";
96
+ if (ext === ".7z") return "application/x-7z-compressed";
97
+ if (ext === ".xz") return "application/x-xz";
98
+
99
+ // Fonts
100
+ if (ext === ".ttf") return "font/ttf";
101
+ if (ext === ".otf") return "font/otf";
102
+ if (ext === ".woff") return "font/woff";
103
+ if (ext === ".woff2") return "font/woff2";
104
+ if (ext === ".eot") return "application/vnd.ms-fontobject";
105
+
106
+ // Other common formats
107
+ if (ext === ".epub") return "application/epub+zip";
108
+ if (ext === ".mobi") return "application/x-mobipocket-ebook";
109
+ if (ext === ".apk") return "application/vnd.android.package-archive";
110
+ if (ext === ".dmg") return "application/x-apple-diskimage";
111
+ if (ext === ".iso") return "application/x-iso9660-image";
112
+ if (ext === ".exe") return "application/x-msdownload";
113
+ if (ext === ".msi") return "application/x-msi";
114
+ if (ext === ".deb") return "application/x-debian-package";
115
+ if (ext === ".rpm") return "application/x-rpm";
116
+
117
+ console.warn(`Unknown extension, ${ext}`);
118
+ return "text/plain";
119
+ }
120
+
121
+ export function getContentTypeFromBuffer(buffer: Buffer): string | undefined {
122
+ if (buffer.length < 4) return undefined;
123
+
124
+ // Helper function to check if buffer starts with specific bytes
125
+ const startsWith = (bytes: number[]): boolean => {
126
+ if (buffer.length < bytes.length) return false;
127
+ for (let i = 0; i < bytes.length; i++) {
128
+ if (buffer[i] !== bytes[i]) return false;
129
+ }
130
+ return true;
131
+ };
132
+
133
+ // Helper function to check bytes at specific offset
134
+ const hasAtOffset = (offset: number, bytes: number[]): boolean => {
135
+ if (buffer.length < offset + bytes.length) return false;
136
+ for (let i = 0; i < bytes.length; i++) {
137
+ if (buffer[offset + i] !== bytes[i]) return false;
138
+ }
139
+ return true;
140
+ };
141
+
142
+ // Images
143
+ if (startsWith([0xFF, 0xD8, 0xFF])) return "image/jpeg";
144
+ if (startsWith([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) return "image/png";
145
+ if (startsWith([0x47, 0x49, 0x46, 0x38])) return "image/gif";
146
+ if (startsWith([0x42, 0x4D])) return "image/bmp";
147
+ if (startsWith([0x49, 0x49, 0x2A, 0x00]) || startsWith([0x4D, 0x4D, 0x00, 0x2A])) return "image/tiff";
148
+ if (startsWith([0x00, 0x00, 0x01, 0x00])) return "image/x-icon";
149
+ if (buffer.toString("ascii", 0, 4) === "<svg" || buffer.toString("ascii", 0, 5) === "<?xml") {
150
+ if (buffer.toString("utf8").includes("<svg")) return "image/svg+xml";
151
+ }
152
+
153
+ // WebP and modern image formats
154
+ if (startsWith([0x52, 0x49, 0x46, 0x46]) && hasAtOffset(8, [0x57, 0x45, 0x42, 0x50])) return "image/webp";
155
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70, 0x61, 0x76, 0x69, 0x66])) return "image/avif";
156
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70, 0x68, 0x65, 0x69, 0x63])) return "image/heic";
157
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70, 0x6D, 0x69, 0x66, 0x31])) return "image/heif";
158
+
159
+ // Audio
160
+ if (startsWith([0xFF, 0xFB]) || startsWith([0xFF, 0xF3]) || startsWith([0xFF, 0xF2])) return "audio/mpeg";
161
+ if (startsWith([0x49, 0x44, 0x33])) return "audio/mpeg"; // MP3 with ID3
162
+ if (startsWith([0x52, 0x49, 0x46, 0x46]) && hasAtOffset(8, [0x57, 0x41, 0x56, 0x45])) return "audio/wav";
163
+ if (startsWith([0x4F, 0x67, 0x67, 0x53])) return "audio/ogg";
164
+ if (startsWith([0x66, 0x4C, 0x61, 0x43])) return "audio/flac";
165
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41])) return "audio/mp4";
166
+
167
+ // Video
168
+ if (hasAtOffset(4, [0x66, 0x74, 0x79, 0x70])) {
169
+ const ftyp = buffer.toString("ascii", 8, 12);
170
+ if (ftyp.startsWith("mp4") || ftyp.startsWith("isom") || ftyp.startsWith("M4V")) return "video/mp4";
171
+ if (ftyp.startsWith("qt")) return "video/quicktime";
172
+ }
173
+ if (startsWith([0x1A, 0x45, 0xDF, 0xA3])) return "video/webm"; // Also mkv
174
+ if (startsWith([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11])) return "video/x-ms-wmv";
175
+ if (startsWith([0x46, 0x4C, 0x56, 0x01])) return "video/x-flv";
176
+ if (startsWith([0x52, 0x49, 0x46, 0x46]) && hasAtOffset(8, [0x41, 0x56, 0x49, 0x20])) return "video/x-msvideo";
177
+
178
+ // Documents
179
+ if (startsWith([0x25, 0x50, 0x44, 0x46])) return "application/pdf";
180
+ if (startsWith([0x50, 0x4B, 0x03, 0x04]) || startsWith([0x50, 0x4B, 0x05, 0x06]) || startsWith([0x50, 0x4B, 0x07, 0x08])) {
181
+ // ZIP-based formats - need to check internal structure
182
+ const content = buffer.toString("ascii", 30, 100);
183
+ if (content.includes("word/")) return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
184
+ if (content.includes("xl/")) return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
185
+ if (content.includes("ppt/")) return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
186
+ if (content.includes("META-INF/")) {
187
+ if (content.includes("content.xml")) return "application/vnd.oasis.opendocument.text";
188
+ }
189
+ return "application/zip";
190
+ }
191
+ if (startsWith([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) {
192
+ // Microsoft Office legacy formats
193
+ return "application/msword"; // Could also be Excel or PowerPoint
194
+ }
195
+ if (startsWith([0x7B, 0x5C, 0x72, 0x74, 0x66])) return "application/rtf";
196
+
197
+ // Archives
198
+ if (startsWith([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07])) return "application/vnd.rar";
199
+ if (startsWith([0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C])) return "application/x-7z-compressed";
200
+ if (startsWith([0x1F, 0x8B])) return "application/gzip";
201
+ if (startsWith([0x42, 0x5A, 0x68])) return "application/x-bzip2";
202
+ if (startsWith([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) return "application/x-xz";
203
+ if (startsWith([0x75, 0x73, 0x74, 0x61, 0x72])) return "application/x-tar";
204
+
205
+ // Executables
206
+ if (startsWith([0x4D, 0x5A])) return "application/x-msdownload"; // Windows PE
207
+ if (startsWith([0x7F, 0x45, 0x4C, 0x46])) return "application/x-executable"; // Linux ELF
208
+ if (startsWith([0xCA, 0xFE, 0xBA, 0xBE]) || startsWith([0xFE, 0xED, 0xFA, 0xCE])) return "application/x-mach-binary"; // macOS binary
209
+ if (startsWith([0x50, 0x4B, 0x03, 0x04]) && buffer.toString("ascii", 30, 50).includes("AndroidManifest.xml")) return "application/vnd.android.package-archive";
210
+
211
+ // Fonts
212
+ if (startsWith([0x00, 0x01, 0x00, 0x00])) return "font/ttf";
213
+ if (startsWith([0x4F, 0x54, 0x54, 0x4F])) return "font/otf";
214
+ if (startsWith([0x77, 0x4F, 0x46, 0x46])) return "font/woff";
215
+ if (startsWith([0x77, 0x4F, 0x46, 0x32])) return "font/woff2";
216
+
217
+ // Web files (text-based, check for common patterns)
218
+ const textStart = buffer.toString("utf8", 0, Math.min(100, buffer.length)).toLowerCase();
219
+ if (textStart.includes("<!doctype html") || textStart.includes("<html")) return "text/html";
220
+ if (textStart.includes("<?xml")) return "application/xml";
221
+ if (textStart.startsWith("{") || textStart.startsWith("[")) {
222
+ try {
223
+ JSON.parse(buffer.toString("utf8"));
224
+ return "application/json";
225
+ } catch {
226
+ // Not JSON
227
+ }
228
+ }
229
+
230
+ // Other formats
231
+ if (startsWith([0x25, 0x21, 0x50, 0x53])) return "application/postscript";
232
+ if (startsWith([0x38, 0x42, 0x50, 0x53])) return "image/vnd.adobe.photoshop";
233
+ if (startsWith([0x49, 0x49, 0x2A, 0x00, 0x10, 0x00, 0x00, 0x00, 0x43, 0x52])) return "image/x-canon-cr2";
234
+
235
+ return undefined;
236
+ }
@@ -109,10 +109,6 @@ export async function httpCallHandler(request: http.IncomingMessage, response: h
109
109
  if (!urlBase) {
110
110
  throw new Error("Missing URL");
111
111
  }
112
- if (urlBase === "/favicon.ico") {
113
- response.end();
114
- return;
115
- }
116
112
 
117
113
  let protocol = "https";
118
114
  let url = protocol + "://" + request.headers.host + request.url;