@testsmith/api-spector 0.0.7 → 0.0.9

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/out/main/index.js CHANGED
@@ -25,19 +25,19 @@ const electron = require("electron");
25
25
  const path = require("path");
26
26
  const fs = require("fs");
27
27
  const promises = require("fs/promises");
28
- const scriptRunner = require("./chunks/script-runner-CmdLWmKN.js");
29
- const undici = require("undici");
30
- const crypto = require("crypto");
28
+ const requestHandler = require("./chunks/request-handler-9MdHOWVf.js");
31
29
  const uuid = require("uuid");
32
30
  const jsYaml = require("js-yaml");
31
+ const undici = require("undici");
33
32
  const JSZip = require("jszip");
34
- const mockServer = require("./chunks/mock-server-ZB7zpXh9.js");
33
+ const mockServer = require("./chunks/mock-server-C0RlwZf_.js");
35
34
  const http = require("http");
36
35
  const WebSocket = require("ws");
37
36
  const https = require("https");
38
37
  const Ajv = require("ajv");
39
- require("vm");
38
+ require("crypto");
40
39
  require("dayjs");
40
+ require("vm");
41
41
  require("tv4");
42
42
  require("jsonpath-plus");
43
43
  require("@xmldom/xmldom");
@@ -69,7 +69,7 @@ function registerFileHandlers(ipc) {
69
69
  const wsPath = result.filePaths[0];
70
70
  workspaceDir = path.dirname(wsPath);
71
71
  workspaceFile = wsPath;
72
- await scriptRunner.loadGlobals(workspaceDir);
72
+ await requestHandler.loadGlobals(workspaceDir);
73
73
  await saveLastWorkspacePath(wsPath);
74
74
  const raw = await promises.readFile(wsPath, "utf8");
75
75
  return { workspace: JSON.parse(raw), workspacePath: wsPath };
@@ -83,7 +83,7 @@ function registerFileHandlers(ipc) {
83
83
  if (result.canceled || !result.filePath) return null;
84
84
  workspaceDir = path.dirname(result.filePath);
85
85
  workspaceFile = result.filePath;
86
- await scriptRunner.loadGlobals(workspaceDir);
86
+ await requestHandler.loadGlobals(workspaceDir);
87
87
  await promises.mkdir(path.join(workspaceDir, "collections"), { recursive: true });
88
88
  await promises.mkdir(path.join(workspaceDir, "environments"), { recursive: true });
89
89
  const gitignore = "# api Spector — never commit secrets\n*.secrets\n.env.local\n";
@@ -147,10 +147,10 @@ function registerFileHandlers(ipc) {
147
147
  await promises.writeFile(result.filePath, content, "utf8");
148
148
  return true;
149
149
  });
150
- ipc.handle("globals:get", () => scriptRunner.getGlobals());
150
+ ipc.handle("globals:get", () => requestHandler.getGlobals());
151
151
  ipc.handle("globals:set", async (_e, patch) => {
152
- scriptRunner.setGlobals(patch);
153
- await scriptRunner.persistGlobals();
152
+ requestHandler.setGlobals(patch);
153
+ await requestHandler.persistGlobals();
154
154
  });
155
155
  ipc.handle("file:closeWorkspace", async () => {
156
156
  workspaceDir = null;
@@ -164,7 +164,7 @@ function registerFileHandlers(ipc) {
164
164
  try {
165
165
  workspaceDir = path.dirname(wsPath);
166
166
  workspaceFile = wsPath;
167
- await scriptRunner.loadGlobals(workspaceDir);
167
+ await requestHandler.loadGlobals(workspaceDir);
168
168
  const raw = await promises.readFile(wsPath, "utf8");
169
169
  return { workspace: JSON.parse(raw), workspacePath: wsPath };
170
170
  } catch {
@@ -175,471 +175,6 @@ function registerFileHandlers(ipc) {
175
175
  function getWorkspaceDir() {
176
176
  return workspaceDir;
177
177
  }
178
- async function buildAuthHeaders(auth, vars) {
179
- const headers = {};
180
- if (auth.type === "bearer") {
181
- let token = auth.token ?? "";
182
- if (!token && auth.tokenSecretRef) token = await scriptRunner.getSecret(auth.tokenSecretRef) ?? "";
183
- token = scriptRunner.interpolate(token, vars);
184
- if (token) headers["Authorization"] = `Bearer ${token}`;
185
- }
186
- if (auth.type === "basic") {
187
- let password = auth.password ?? "";
188
- if (!password && auth.passwordSecretRef) password = await scriptRunner.getSecret(auth.passwordSecretRef) ?? "";
189
- password = scriptRunner.interpolate(password, vars);
190
- const username = scriptRunner.interpolate(auth.username ?? "", vars);
191
- headers["Authorization"] = `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`;
192
- }
193
- if (auth.type === "apikey" && auth.apiKeyIn === "header") {
194
- let value = auth.apiKeyValue ?? "";
195
- if (!value && auth.apiKeySecretRef) value = await scriptRunner.getSecret(auth.apiKeySecretRef) ?? "";
196
- value = scriptRunner.interpolate(value, vars);
197
- headers[auth.apiKeyName ?? "X-API-Key"] = value;
198
- }
199
- if (auth.type === "oauth2") {
200
- const now = Date.now();
201
- if (auth.oauth2CachedToken && auth.oauth2TokenExpiry && auth.oauth2TokenExpiry > now + 5e3) {
202
- headers["Authorization"] = `Bearer ${auth.oauth2CachedToken}`;
203
- }
204
- }
205
- return headers;
206
- }
207
- async function buildApiKeyParam(auth, vars) {
208
- if (auth.type !== "apikey" || auth.apiKeyIn !== "query") return null;
209
- let value = auth.apiKeyValue ?? "";
210
- if (!value && auth.apiKeySecretRef) value = await scriptRunner.getSecret(auth.apiKeySecretRef) ?? "";
211
- value = scriptRunner.interpolate(value, vars);
212
- return { key: auth.apiKeyName ?? "apikey", value };
213
- }
214
- function parseDigestChallenge(wwwAuth) {
215
- const extract = (key) => {
216
- const m = new RegExp(`${key}="([^"]*)"`, "i").exec(wwwAuth);
217
- return m ? m[1] : "";
218
- };
219
- const extractUnquoted = (key) => {
220
- const m = new RegExp(`${key}=([^,\\s]+)`, "i").exec(wwwAuth);
221
- return m ? m[1] : "";
222
- };
223
- return {
224
- realm: extract("realm"),
225
- nonce: extract("nonce"),
226
- qop: extract("qop") || extractUnquoted("qop") || void 0,
227
- algorithm: extract("algorithm") || extractUnquoted("algorithm") || "MD5",
228
- opaque: extract("opaque") || void 0
229
- };
230
- }
231
- function md5(s) {
232
- return crypto.createHash("md5").update(s).digest("hex");
233
- }
234
- function buildDigestAuthHeader(challenge, username, password, method, uri) {
235
- const { realm, nonce, qop, algorithm, opaque } = challenge;
236
- const algo = (algorithm ?? "MD5").toUpperCase();
237
- const ha1 = algo === "MD5-SESS" ? md5(`${md5(`${username}:${realm}:${password}`)}:${nonce}:`) : md5(`${username}:${realm}:${password}`);
238
- const ha2 = md5(`${method}:${uri}`);
239
- let response;
240
- let nc;
241
- let cnonce;
242
- if (qop === "auth" || qop === "auth-int") {
243
- nc = "00000001";
244
- cnonce = crypto.randomBytes(8).toString("hex");
245
- response = md5(`${ha1}:${nonce}:${nc}:${cnonce}:${qop}:${ha2}`);
246
- } else {
247
- response = md5(`${ha1}:${nonce}:${ha2}`);
248
- }
249
- let header = `Digest username="${username}", realm="${realm}", nonce="${nonce}", uri="${uri}", response="${response}"`;
250
- if (qop) header += `, qop=${qop}`;
251
- if (nc) header += `, nc=${nc}`;
252
- if (cnonce) header += `, cnonce="${cnonce}"`;
253
- if (opaque) header += `, opaque="${opaque}"`;
254
- if (algo !== "MD5") header += `, algorithm=${algo}`;
255
- return header;
256
- }
257
- async function performDigestAuth(url, method, auth, vars, fetchFn) {
258
- const probeResp = await fetchFn(url, { method, headers: {} });
259
- if (probeResp.status !== 401) return null;
260
- const wwwAuth = probeResp.headers.get("www-authenticate") ?? "";
261
- if (!wwwAuth.toLowerCase().startsWith("digest")) return null;
262
- const challenge = parseDigestChallenge(wwwAuth);
263
- let password = auth.password ?? "";
264
- if (!password && auth.passwordSecretRef) password = await scriptRunner.getSecret(auth.passwordSecretRef) ?? "";
265
- password = scriptRunner.interpolate(password, vars);
266
- const username = scriptRunner.interpolate(auth.username ?? "", vars);
267
- let uri = "/";
268
- try {
269
- uri = new URL(url).pathname + (new URL(url).search ?? "");
270
- } catch {
271
- }
272
- return buildDigestAuthHeader(challenge, username, password, method, uri);
273
- }
274
- async function performNtlmRequest(_url, _method, _auth, _vars) {
275
- throw new Error(
276
- 'NTLM auth is not yet implemented. Add "httpntlm" to package.json dependencies and implement performNtlmRequest in auth-builder.ts.'
277
- );
278
- }
279
- async function fetchOAuth2Token(auth, vars) {
280
- const flow = auth.oauth2Flow ?? "client_credentials";
281
- if (flow === "authorization_code") {
282
- throw new Error("authorization_code flow requires the oauth2:startFlow IPC call from the renderer.");
283
- }
284
- if (flow === "implicit") {
285
- throw new Error("implicit flow cannot be performed server-side — tokens must be obtained via the browser redirect.");
286
- }
287
- const tokenUrl = scriptRunner.interpolate(auth.oauth2TokenUrl ?? "", vars);
288
- if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required.");
289
- const clientId = scriptRunner.interpolate(auth.oauth2ClientId ?? "", vars);
290
- let clientSecret = auth.oauth2ClientSecret ?? "";
291
- if (!clientSecret && auth.oauth2ClientSecretRef) {
292
- clientSecret = await scriptRunner.getSecret(auth.oauth2ClientSecretRef) ?? "";
293
- }
294
- clientSecret = scriptRunner.interpolate(clientSecret, vars);
295
- const params = new URLSearchParams();
296
- params.set("grant_type", flow === "password" ? "password" : "client_credentials");
297
- params.set("client_id", clientId);
298
- params.set("client_secret", clientSecret);
299
- if (auth.oauth2Scopes) params.set("scope", auth.oauth2Scopes);
300
- if (flow === "password") {
301
- let password = auth.password ?? "";
302
- if (!password && auth.passwordSecretRef) password = await scriptRunner.getSecret(auth.passwordSecretRef) ?? "";
303
- password = scriptRunner.interpolate(password, vars);
304
- params.set("username", scriptRunner.interpolate(auth.username ?? "", vars));
305
- params.set("password", password);
306
- }
307
- const { fetch: nodeFetch } = await import("undici");
308
- const resp = await nodeFetch(tokenUrl, {
309
- method: "POST",
310
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
311
- body: params.toString()
312
- });
313
- if (!resp.ok) {
314
- const body = await resp.text();
315
- throw new Error(`OAuth 2.0 token request failed (${resp.status}): ${body}`);
316
- }
317
- const json = await resp.json();
318
- const accessToken = String(json["access_token"] ?? "");
319
- if (!accessToken) throw new Error("OAuth 2.0: token response missing access_token.");
320
- const expiresIn = Number(json["expires_in"] ?? 3600);
321
- const expiresAt = Date.now() + expiresIn * 1e3;
322
- return {
323
- accessToken,
324
- expiresAt,
325
- refreshToken: json["refresh_token"] ? String(json["refresh_token"]) : void 0
326
- };
327
- }
328
- function maskPii(data, patterns) {
329
- if (!patterns.length) return data;
330
- try {
331
- const obj = JSON.parse(data);
332
- const masked = maskObject(obj, patterns);
333
- return JSON.stringify(masked);
334
- } catch {
335
- return data;
336
- }
337
- }
338
- function maskObject(obj, patterns) {
339
- if (Array.isArray(obj)) return obj.map((item) => maskObject(item, patterns));
340
- if (obj && typeof obj === "object") {
341
- const result = {};
342
- for (const [k, v] of Object.entries(obj)) {
343
- if (patterns.some((p) => k.toLowerCase().includes(p.toLowerCase()))) {
344
- result[k] = "[REDACTED]";
345
- } else {
346
- result[k] = maskObject(v, patterns);
347
- }
348
- }
349
- return result;
350
- }
351
- return obj;
352
- }
353
- function maskHeaders(headers, patterns) {
354
- if (!patterns.length) return headers;
355
- const alwaysMask = ["authorization", "cookie", "set-cookie"];
356
- const result = {};
357
- for (const [k, v] of Object.entries(headers)) {
358
- const lower = k.toLowerCase();
359
- if (alwaysMask.includes(lower) || patterns.some((p) => lower.includes(p.toLowerCase()))) {
360
- result[k] = "[REDACTED]";
361
- } else {
362
- result[k] = v;
363
- }
364
- }
365
- return result;
366
- }
367
- async function buildDispatcher$1(proxy, tls) {
368
- const connectOpts = {};
369
- let hasTls = false;
370
- if (tls) {
371
- hasTls = true;
372
- if (tls.rejectUnauthorized !== void 0) {
373
- connectOpts["rejectUnauthorized"] = tls.rejectUnauthorized;
374
- }
375
- if (tls.caCertPath) {
376
- try {
377
- connectOpts["ca"] = await promises.readFile(tls.caCertPath);
378
- } catch {
379
- }
380
- }
381
- if (tls.clientCertPath) {
382
- try {
383
- connectOpts["cert"] = await promises.readFile(tls.clientCertPath);
384
- } catch {
385
- }
386
- }
387
- if (tls.clientKeyPath) {
388
- try {
389
- connectOpts["key"] = await promises.readFile(tls.clientKeyPath);
390
- } catch {
391
- }
392
- }
393
- }
394
- if (proxy?.url) {
395
- const proxyUri = proxy.auth ? proxy.url.replace("://", `://${encodeURIComponent(proxy.auth.username)}:${encodeURIComponent(proxy.auth.password)}@`) : proxy.url;
396
- return new undici.ProxyAgent({
397
- uri: proxyUri,
398
- ...hasTls ? { connect: connectOpts } : {}
399
- });
400
- }
401
- if (hasTls) {
402
- return new undici.Agent({ connect: connectOpts });
403
- }
404
- return void 0;
405
- }
406
- function registerRequestHandler(ipc) {
407
- ipc.handle("request:send", async (_e, payload) => {
408
- const {
409
- request: req,
410
- environment,
411
- collectionVars,
412
- globals: payloadGlobals,
413
- proxy,
414
- tls,
415
- piiMaskPatterns = []
416
- } = payload;
417
- const start = Date.now();
418
- const liveGlobals = scriptRunner.getGlobals();
419
- const mergedGlobals = { ...payloadGlobals, ...liveGlobals };
420
- const envVars = await scriptRunner.buildEnvVars(environment);
421
- let localVars = {};
422
- const decryptionWarnings = [];
423
- if (environment) {
424
- const masterKeySet = Boolean(process.env["API_SPECTOR_MASTER_KEY"]);
425
- for (const v of environment.variables) {
426
- if (!v.enabled || !v.secret || !v.secretEncrypted) continue;
427
- if (!masterKeySet) {
428
- decryptionWarnings.push(`[warn] Secret "${v.key}" was not decrypted: API_SPECTOR_MASTER_KEY is not set. Use the master password modal or export the variable in your shell.`);
429
- } else if (envVars[v.key] === void 0) {
430
- decryptionWarnings.push(`[warn] Secret "${v.key}" could not be decrypted: wrong password or corrupted data.`);
431
- }
432
- }
433
- }
434
- let vars = scriptRunner.mergeVars(envVars, collectionVars, mergedGlobals, localVars);
435
- let preScriptMeta = { consoleOutput: [] };
436
- let updatedCollectionVars = { ...collectionVars };
437
- let updatedEnvVars = { ...envVars };
438
- let updatedGlobals = { ...mergedGlobals };
439
- if (req.preRequestScript?.trim()) {
440
- const result = await scriptRunner.runScript(req.preRequestScript, {
441
- envVars: { ...envVars },
442
- collectionVars: { ...collectionVars },
443
- globals: { ...mergedGlobals },
444
- localVars: {}
445
- });
446
- preScriptMeta = { error: result.error, consoleOutput: result.consoleOutput };
447
- localVars = result.updatedLocalVars;
448
- updatedEnvVars = result.updatedEnvVars;
449
- updatedCollectionVars = result.updatedCollectionVars;
450
- updatedGlobals = result.updatedGlobals;
451
- scriptRunner.patchGlobals(result.updatedGlobals);
452
- await scriptRunner.persistGlobals();
453
- vars = scriptRunner.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars);
454
- }
455
- let response;
456
- let sentRequest = { method: req.method, url: "", headers: {} };
457
- const resolvedUrl = scriptRunner.buildUrl(req.url, req.params, vars);
458
- const secretValues = /* @__PURE__ */ new Set();
459
- if (environment) {
460
- for (const v of environment.variables) {
461
- if (!v.enabled) continue;
462
- if ((v.secret || v.envRef) && envVars[v.key]) {
463
- secretValues.add(envVars[v.key]);
464
- }
465
- }
466
- }
467
- function redactSecrets(s) {
468
- if (!secretValues.size) return s;
469
- let result = s;
470
- for (const secret of secretValues) {
471
- if (secret) result = result.split(secret).join("[*****]");
472
- }
473
- return result;
474
- }
475
- function redactSentRequest(sr) {
476
- const headers = {};
477
- for (const [k, v] of Object.entries(sr.headers)) {
478
- headers[k] = redactSecrets(v);
479
- }
480
- return {
481
- method: sr.method,
482
- url: redactSecrets(sr.url),
483
- headers,
484
- body: sr.body !== void 0 ? redactSecrets(sr.body) : void 0
485
- };
486
- }
487
- try {
488
- const dispatcher = await buildDispatcher$1(proxy, tls);
489
- if (req.auth.type === "oauth2") {
490
- const now = Date.now();
491
- const tokenMissing = !req.auth.oauth2CachedToken;
492
- const tokenExpired = req.auth.oauth2TokenExpiry ? req.auth.oauth2TokenExpiry <= now + 5e3 : true;
493
- if (tokenMissing || tokenExpired) {
494
- const result = await fetchOAuth2Token(req.auth, vars);
495
- req.auth.oauth2CachedToken = result.accessToken;
496
- req.auth.oauth2TokenExpiry = result.expiresAt;
497
- }
498
- }
499
- const authHeaders = await buildAuthHeaders(req.auth, vars);
500
- const apiKeyParam = await buildApiKeyParam(req.auth, vars);
501
- let finalUrl = resolvedUrl;
502
- if (apiKeyParam) {
503
- const sep = finalUrl.includes("?") ? "&" : "?";
504
- finalUrl += `${sep}${encodeURIComponent(apiKeyParam.key)}=${encodeURIComponent(apiKeyParam.value)}`;
505
- }
506
- const buildHeaders2 = () => {
507
- const h = new undici.Headers();
508
- for (const header of req.headers) {
509
- if (header.enabled && header.key) {
510
- h.set(scriptRunner.interpolate(header.key, vars), scriptRunner.interpolate(header.value, vars));
511
- }
512
- }
513
- for (const [k, v] of Object.entries(authHeaders)) h.set(k, v);
514
- return h;
515
- };
516
- let body;
517
- if (req.body.mode === "json" && req.body.json) {
518
- body = scriptRunner.interpolate(req.body.json, vars);
519
- } else if (req.body.mode === "form" && req.body.form) {
520
- body = req.body.form.filter((p) => p.enabled && p.key).map((p) => `${encodeURIComponent(scriptRunner.interpolate(p.key, vars))}=${encodeURIComponent(scriptRunner.interpolate(p.value, vars))}`).join("&");
521
- } else if (req.body.mode === "raw" && req.body.raw) {
522
- body = scriptRunner.interpolate(req.body.raw, vars);
523
- } else if (req.body.mode === "graphql" && req.body.graphql) {
524
- const gql = req.body.graphql;
525
- const gqlBody = { query: scriptRunner.interpolate(gql.query, vars) };
526
- const rawVars = gql.variables?.trim();
527
- if (rawVars) {
528
- try {
529
- gqlBody.variables = JSON.parse(scriptRunner.interpolate(rawVars, vars));
530
- } catch {
531
- }
532
- }
533
- if (gql.operationName?.trim()) gqlBody.operationName = gql.operationName.trim();
534
- body = JSON.stringify(gqlBody);
535
- } else if (req.body.mode === "soap" && req.body.soap) {
536
- const soap = req.body.soap;
537
- body = scriptRunner.interpolate(soap.envelope, vars);
538
- }
539
- const methodHasBody = !["GET", "HEAD"].includes(req.method);
540
- const doFetch = async (overrideHeaders) => {
541
- const h = overrideHeaders ?? buildHeaders2();
542
- if (body !== void 0) {
543
- if (!h.has("content-type")) {
544
- if (req.body.mode === "json" || req.body.mode === "graphql") h.set("Content-Type", "application/json");
545
- else if (req.body.mode === "form") h.set("Content-Type", "application/x-www-form-urlencoded");
546
- else if (req.body.mode === "raw") h.set("Content-Type", req.body.rawContentType ?? "text/plain");
547
- else if (req.body.mode === "soap") h.set("Content-Type", "text/xml; charset=utf-8");
548
- }
549
- if (req.body.mode === "soap" && req.body.soap?.soapAction && !h.has("soapaction")) {
550
- h.set("SOAPAction", req.body.soap.soapAction);
551
- }
552
- }
553
- const capturedHeaders = {};
554
- h.forEach((value, key) => {
555
- capturedHeaders[key] = value;
556
- });
557
- sentRequest = { method: req.method, url: finalUrl, headers: capturedHeaders, body: methodHasBody ? body : void 0 };
558
- return undici.fetch(finalUrl, {
559
- method: req.method,
560
- headers: h,
561
- body: methodHasBody ? body : void 0,
562
- dispatcher
563
- });
564
- };
565
- let fetchResp;
566
- if (req.auth.type === "ntlm") {
567
- await performNtlmRequest(finalUrl, req.method, req.auth, vars);
568
- fetchResp = await doFetch();
569
- } else if (req.auth.type === "digest") {
570
- const probeFetch = async (url, init) => {
571
- return undici.fetch(url, {
572
- ...init,
573
- dispatcher
574
- });
575
- };
576
- const digestHeader = await performDigestAuth(finalUrl, req.method, req.auth, vars, probeFetch);
577
- const h = buildHeaders2();
578
- if (digestHeader) h.set("Authorization", digestHeader);
579
- fetchResp = await doFetch(h);
580
- } else {
581
- fetchResp = await doFetch();
582
- }
583
- const responseBody = await fetchResp.text();
584
- const durationMs = Date.now() - start;
585
- const rawResponseHeaders = {};
586
- fetchResp.headers.forEach((value, key) => {
587
- rawResponseHeaders[key] = value;
588
- });
589
- const maskedBody = maskPii(responseBody, piiMaskPatterns);
590
- const maskedHeaders = maskHeaders(rawResponseHeaders, piiMaskPatterns);
591
- response = {
592
- status: fetchResp.status,
593
- statusText: fetchResp.statusText,
594
- headers: maskedHeaders,
595
- body: maskedBody,
596
- bodySize: Buffer.byteLength(responseBody, "utf8"),
597
- durationMs
598
- };
599
- } catch (err) {
600
- response = {
601
- status: 0,
602
- statusText: "Error",
603
- headers: {},
604
- body: "",
605
- bodySize: 0,
606
- durationMs: Date.now() - start,
607
- error: err instanceof Error ? err.message : String(err)
608
- };
609
- }
610
- let postTestResults = [];
611
- let postConsole = [];
612
- let postError;
613
- if (req.postRequestScript?.trim() && !response.error) {
614
- const result = await scriptRunner.runScript(req.postRequestScript, {
615
- envVars: { ...updatedEnvVars },
616
- collectionVars: { ...updatedCollectionVars },
617
- globals: { ...updatedGlobals },
618
- localVars: { ...localVars },
619
- response
620
- });
621
- postTestResults = result.testResults;
622
- postConsole = result.consoleOutput;
623
- postError = result.error;
624
- updatedEnvVars = result.updatedEnvVars;
625
- updatedCollectionVars = result.updatedCollectionVars;
626
- updatedGlobals = result.updatedGlobals;
627
- scriptRunner.patchGlobals(result.updatedGlobals);
628
- await scriptRunner.persistGlobals();
629
- }
630
- const scriptResult = {
631
- testResults: postTestResults,
632
- consoleOutput: [...decryptionWarnings, ...preScriptMeta.consoleOutput, ...postConsole],
633
- updatedEnvVars,
634
- updatedCollectionVars,
635
- updatedGlobals,
636
- resolvedUrl,
637
- preScriptError: preScriptMeta.error,
638
- postScriptError: postError
639
- };
640
- return { response, scriptResult, sentRequest: redactSentRequest(sentRequest) };
641
- });
642
- }
643
178
  const POSTMAN_RULES = [
644
179
  // Variables — environment
645
180
  [/\bpm\.environment\.get\(/g, "sp.environment.get("],
@@ -2699,13 +2234,13 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2699
2234
  resolvedUrl: "",
2700
2235
  status: "running"
2701
2236
  };
2702
- let vars = scriptRunner.mergeVars(envVars, collectionVars, globals, localVars);
2237
+ let vars = requestHandler.mergeVars(envVars, collectionVars, globals, localVars);
2703
2238
  let updatedEnvVars = { ...envVars };
2704
2239
  let updatedCollectionVars = { ...collectionVars };
2705
2240
  let updatedGlobals = { ...globals };
2706
2241
  let preScriptError;
2707
2242
  if (req.preRequestScript?.trim()) {
2708
- const r = await scriptRunner.runScript(req.preRequestScript, {
2243
+ const r = await requestHandler.runScript(req.preRequestScript, {
2709
2244
  envVars: { ...envVars },
2710
2245
  collectionVars: { ...collectionVars },
2711
2246
  globals: { ...globals },
@@ -2716,11 +2251,11 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2716
2251
  updatedEnvVars = r.updatedEnvVars;
2717
2252
  updatedCollectionVars = r.updatedCollectionVars;
2718
2253
  updatedGlobals = r.updatedGlobals;
2719
- scriptRunner.patchGlobals(r.updatedGlobals);
2720
- await scriptRunner.persistGlobals();
2721
- vars = scriptRunner.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars);
2254
+ requestHandler.patchGlobals(r.updatedGlobals);
2255
+ await requestHandler.persistGlobals();
2256
+ vars = requestHandler.mergeVars(updatedEnvVars, updatedCollectionVars, updatedGlobals, localVars);
2722
2257
  }
2723
- const resolvedUrl = scriptRunner.buildUrl(req.url, req.params, vars);
2258
+ const resolvedUrl = requestHandler.buildUrl(req.url, req.params, vars);
2724
2259
  base.resolvedUrl = resolvedUrl;
2725
2260
  const start = Date.now();
2726
2261
  try {
@@ -2729,23 +2264,23 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2729
2264
  const tokenMissing = !req.auth.oauth2CachedToken;
2730
2265
  const tokenExpired = req.auth.oauth2TokenExpiry ? req.auth.oauth2TokenExpiry <= now + 5e3 : true;
2731
2266
  if (tokenMissing || tokenExpired) {
2732
- const result = await fetchOAuth2Token(req.auth, vars);
2267
+ const result = await requestHandler.fetchOAuth2Token(req.auth, vars);
2733
2268
  req.auth.oauth2CachedToken = result.accessToken;
2734
2269
  req.auth.oauth2TokenExpiry = result.expiresAt;
2735
2270
  }
2736
2271
  }
2737
- const authHeaders = await buildAuthHeaders(req.auth, vars);
2272
+ const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
2738
2273
  const headers = new undici.Headers();
2739
2274
  for (const h of req.headers) {
2740
- if (h.enabled && h.key) headers.set(scriptRunner.interpolate(h.key, vars), scriptRunner.interpolate(h.value, vars));
2275
+ if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
2741
2276
  }
2742
2277
  for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
2743
2278
  let body;
2744
2279
  if (req.body.mode === "json" && req.body.json) {
2745
- body = scriptRunner.interpolate(req.body.json, vars);
2280
+ body = requestHandler.interpolate(req.body.json, vars);
2746
2281
  if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
2747
2282
  } else if (req.body.mode === "raw" && req.body.raw) {
2748
- body = scriptRunner.interpolate(req.body.raw, vars);
2283
+ body = requestHandler.interpolate(req.body.raw, vars);
2749
2284
  if (!headers.has("content-type")) headers.set("Content-Type", req.body.rawContentType ?? "text/plain");
2750
2285
  }
2751
2286
  const methodHasBody = !["GET", "HEAD"].includes(req.method);
@@ -2757,14 +2292,14 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2757
2292
  });
2758
2293
  let fetchResp;
2759
2294
  if (req.auth.type === "ntlm") {
2760
- await performNtlmRequest(resolvedUrl, req.method, req.auth, vars);
2295
+ await requestHandler.performNtlmRequest(resolvedUrl, req.method, req.auth, vars);
2761
2296
  fetchResp = await doFetch(headers);
2762
2297
  } else if (req.auth.type === "digest") {
2763
2298
  const probeFetch = (url, init) => undici.fetch(url, {
2764
2299
  ...init,
2765
2300
  dispatcher
2766
2301
  });
2767
- const digestHeader = await performDigestAuth(resolvedUrl, req.method, req.auth, vars, probeFetch);
2302
+ const digestHeader = await requestHandler.performDigestAuth(resolvedUrl, req.method, req.auth, vars, probeFetch);
2768
2303
  if (digestHeader) headers.set("Authorization", digestHeader);
2769
2304
  fetchResp = await doFetch(headers);
2770
2305
  } else {
@@ -2776,8 +2311,8 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2776
2311
  fetchResp.headers.forEach((v, k) => {
2777
2312
  rawRespHeaders[k] = v;
2778
2313
  });
2779
- const maskedBody = maskPii(responseBody, piiMaskPatterns);
2780
- const maskedHeaders = maskHeaders(rawRespHeaders, piiMaskPatterns);
2314
+ const maskedBody = requestHandler.maskPii(responseBody, piiMaskPatterns);
2315
+ const maskedHeaders = requestHandler.maskHeaders(rawRespHeaders, piiMaskPatterns);
2781
2316
  const response = {
2782
2317
  status: fetchResp.status,
2783
2318
  statusText: fetchResp.statusText,
@@ -2790,7 +2325,7 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2790
2325
  let consoleOutput = [];
2791
2326
  let postScriptError;
2792
2327
  if (req.postRequestScript?.trim()) {
2793
- const r = await scriptRunner.runScript(req.postRequestScript, {
2328
+ const r = await requestHandler.runScript(req.postRequestScript, {
2794
2329
  envVars: updatedEnvVars,
2795
2330
  collectionVars: updatedCollectionVars,
2796
2331
  globals: updatedGlobals,
@@ -2803,8 +2338,8 @@ async function executeOne(req, collectionVars, envVars, globals, localVars, disp
2803
2338
  updatedEnvVars = r.updatedEnvVars;
2804
2339
  updatedCollectionVars = r.updatedCollectionVars;
2805
2340
  updatedGlobals = r.updatedGlobals;
2806
- scriptRunner.patchGlobals(r.updatedGlobals);
2807
- await scriptRunner.persistGlobals();
2341
+ requestHandler.patchGlobals(r.updatedGlobals);
2342
+ await requestHandler.persistGlobals();
2808
2343
  }
2809
2344
  const allPassed = testResults.every((t) => t.passed);
2810
2345
  const status = postScriptError ? "error" : testResults.length > 0 ? allPassed ? "passed" : "failed" : "passed";
@@ -2853,8 +2388,8 @@ const sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
2853
2388
  function registerRunnerHandler(ipc) {
2854
2389
  ipc.handle("runner:start", async (event, payload) => {
2855
2390
  const { items, environment, globals: payloadGlobals, proxy, tls, piiMaskPatterns = [], requestDelay = 0 } = payload;
2856
- const envVars = await scriptRunner.buildEnvVars(environment);
2857
- const liveGlobals = scriptRunner.getGlobals();
2391
+ const envVars = await requestHandler.buildEnvVars(environment);
2392
+ const liveGlobals = requestHandler.getGlobals();
2858
2393
  const globals = { ...payloadGlobals, ...liveGlobals };
2859
2394
  const dispatcher = await buildDispatcher(proxy, tls);
2860
2395
  const summary = { total: items.length, passed: 0, failed: 0, errors: 0, durationMs: 0 };
@@ -2920,14 +2455,14 @@ function registerOAuth2Handlers(ipc) {
2920
2455
  ipc.handle("oauth2:startFlow", async (_e, auth, vars) => {
2921
2456
  const port = auth.oauth2RedirectPort ?? 9876;
2922
2457
  const redirectUri = `http://localhost:${port}/callback`;
2923
- const authUrl = scriptRunner.interpolate(auth.oauth2AuthUrl ?? "", vars);
2924
- const tokenUrl = scriptRunner.interpolate(auth.oauth2TokenUrl ?? "", vars);
2925
- const clientId = scriptRunner.interpolate(auth.oauth2ClientId ?? "", vars);
2458
+ const authUrl = requestHandler.interpolate(auth.oauth2AuthUrl ?? "", vars);
2459
+ const tokenUrl = requestHandler.interpolate(auth.oauth2TokenUrl ?? "", vars);
2460
+ const clientId = requestHandler.interpolate(auth.oauth2ClientId ?? "", vars);
2926
2461
  let clientSecret = auth.oauth2ClientSecret ?? "";
2927
2462
  if (!clientSecret && auth.oauth2ClientSecretRef) {
2928
- clientSecret = await scriptRunner.getSecret(auth.oauth2ClientSecretRef) ?? "";
2463
+ clientSecret = await requestHandler.getSecret(auth.oauth2ClientSecretRef) ?? "";
2929
2464
  }
2930
- clientSecret = scriptRunner.interpolate(clientSecret, vars);
2465
+ clientSecret = requestHandler.interpolate(clientSecret, vars);
2931
2466
  if (!authUrl) throw new Error("OAuth 2.0: authUrl is required for authorization_code flow.");
2932
2467
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for authorization_code flow.");
2933
2468
  if (!clientId) throw new Error("OAuth 2.0: clientId is required.");
@@ -3002,13 +2537,13 @@ function registerOAuth2Handlers(ipc) {
3002
2537
  };
3003
2538
  });
3004
2539
  ipc.handle("oauth2:refreshToken", async (_e, auth, vars, refreshToken) => {
3005
- const tokenUrl = scriptRunner.interpolate(auth.oauth2TokenUrl ?? "", vars);
3006
- const clientId = scriptRunner.interpolate(auth.oauth2ClientId ?? "", vars);
2540
+ const tokenUrl = requestHandler.interpolate(auth.oauth2TokenUrl ?? "", vars);
2541
+ const clientId = requestHandler.interpolate(auth.oauth2ClientId ?? "", vars);
3007
2542
  let clientSecret = auth.oauth2ClientSecret ?? "";
3008
2543
  if (!clientSecret && auth.oauth2ClientSecretRef) {
3009
- clientSecret = await scriptRunner.getSecret(auth.oauth2ClientSecretRef) ?? "";
2544
+ clientSecret = await requestHandler.getSecret(auth.oauth2ClientSecretRef) ?? "";
3010
2545
  }
3011
- clientSecret = scriptRunner.interpolate(clientSecret, vars);
2546
+ clientSecret = requestHandler.interpolate(clientSecret, vars);
3012
2547
  if (!tokenUrl) throw new Error("OAuth 2.0: tokenUrl is required for refresh.");
3013
2548
  const { fetch: nodeFetch } = await import("undici");
3014
2549
  const params = new URLSearchParams();
@@ -3456,21 +2991,21 @@ function validateConsumerResponse(contract, actualStatus, actualHeaders, bodyTex
3456
2991
  return violations;
3457
2992
  }
3458
2993
  async function executeContract(req, vars) {
3459
- const url = scriptRunner.buildUrl(req.url, req.params, vars);
2994
+ const url = requestHandler.buildUrl(req.url, req.params, vars);
3460
2995
  const start = Date.now();
3461
2996
  try {
3462
2997
  const headers = new undici.Headers();
3463
2998
  for (const h of req.headers) {
3464
- if (h.enabled && h.key) headers.set(scriptRunner.interpolate(h.key, vars), scriptRunner.interpolate(h.value, vars));
2999
+ if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
3465
3000
  }
3466
- const authHeaders = await buildAuthHeaders(req.auth, vars);
3001
+ const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
3467
3002
  for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
3468
3003
  let body;
3469
3004
  if (req.body.mode === "json" && req.body.json) {
3470
- body = scriptRunner.interpolate(req.body.json, vars);
3005
+ body = requestHandler.interpolate(req.body.json, vars);
3471
3006
  if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
3472
3007
  } else if (req.body.mode === "raw" && req.body.raw) {
3473
- body = scriptRunner.interpolate(req.body.raw, vars);
3008
+ body = requestHandler.interpolate(req.body.raw, vars);
3474
3009
  }
3475
3010
  const resp = await undici.fetch(url, {
3476
3011
  method: req.method,
@@ -3625,7 +3160,7 @@ function validateRequestAgainstSpec(spec, req, envVars, requestBaseUrl) {
3625
3160
  const jsonContent = content["application/json"];
3626
3161
  if (jsonContent?.["schema"]) {
3627
3162
  try {
3628
- const data = JSON.parse(scriptRunner.interpolate(req.body.json, vars));
3163
+ const data = JSON.parse(requestHandler.interpolate(req.body.json, vars));
3629
3164
  const schema = resolveSchema(spec, jsonContent["schema"]);
3630
3165
  const validate = ajv.compile(schema);
3631
3166
  if (!validate(data)) {
@@ -3761,18 +3296,18 @@ function getProviderResponseSchema(spec, req, envVars, statusCode, requestBaseUr
3761
3296
  return null;
3762
3297
  }
3763
3298
  async function executeRequest(req, vars) {
3764
- const url = scriptRunner.buildUrl(req.url, req.params, vars);
3299
+ const url = requestHandler.buildUrl(req.url, req.params, vars);
3765
3300
  const start = Date.now();
3766
3301
  try {
3767
3302
  const headers = new undici.Headers();
3768
3303
  for (const h of req.headers) {
3769
- if (h.enabled && h.key) headers.set(scriptRunner.interpolate(h.key, vars), scriptRunner.interpolate(h.value, vars));
3304
+ if (h.enabled && h.key) headers.set(requestHandler.interpolate(h.key, vars), requestHandler.interpolate(h.value, vars));
3770
3305
  }
3771
- const authHeaders = await buildAuthHeaders(req.auth, vars);
3306
+ const authHeaders = await requestHandler.buildAuthHeaders(req.auth, vars);
3772
3307
  for (const [k, v] of Object.entries(authHeaders)) headers.set(k, v);
3773
3308
  let body;
3774
3309
  if (req.body.mode === "json" && req.body.json) {
3775
- body = scriptRunner.interpolate(req.body.json, vars);
3310
+ body = requestHandler.interpolate(req.body.json, vars);
3776
3311
  if (!headers.has("content-type")) headers.set("Content-Type", "application/json");
3777
3312
  }
3778
3313
  const resp = await undici.fetch(url, {
@@ -3798,7 +3333,7 @@ async function runBidirectional(requests, envVars, collectionVars = {}, specUrl,
3798
3333
  (r) => r.contract && (r.contract.statusCode !== void 0 || r.contract.bodySchema || r.contract.headers?.length)
3799
3334
  );
3800
3335
  const results = await Promise.all(contractRequests.map(async (req) => {
3801
- const url = scriptRunner.buildUrl(req.url, req.params, vars);
3336
+ const url = requestHandler.buildUrl(req.url, req.params, vars);
3802
3337
  const violations = [];
3803
3338
  const expectedStatus = req.contract.statusCode ?? 200;
3804
3339
  const consumerSchema = req.contract.bodySchema ? (() => {
@@ -3984,10 +3519,10 @@ function createWindow() {
3984
3519
  });
3985
3520
  }
3986
3521
  electron.app.whenReady().then(async () => {
3987
- await scriptRunner.initSecretStore(electron.app.getPath("userData"));
3522
+ await requestHandler.initSecretStore(electron.app.getPath("userData"));
3988
3523
  registerFileHandlers(electron.ipcMain);
3989
- registerRequestHandler(electron.ipcMain);
3990
- scriptRunner.registerSecretHandlers(electron.ipcMain);
3524
+ requestHandler.registerRequestHandler(electron.ipcMain);
3525
+ requestHandler.registerSecretHandlers(electron.ipcMain);
3991
3526
  registerImportHandlers(electron.ipcMain);
3992
3527
  registerGenerateHandlers(electron.ipcMain);
3993
3528
  registerRunnerHandler(electron.ipcMain);