@scalar/pre-post-request-scripts 0.4.15 → 0.4.16

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 (35) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/components/ViewLayout/ValueEmitter.vue.d.ts +1 -1
  3. package/dist/components/ViewLayout/ViewLayoutCollapse.vue.js.map +1 -1
  4. package/dist/components/ViewLayout/ViewLayoutCollapse.vue.script.js +1 -1
  5. package/dist/components/ViewLayout/ViewLayoutCollapse.vue.script.js.map +1 -1
  6. package/dist/libs/execute-scripts/build-sandbox-context.d.ts +12 -7
  7. package/dist/libs/execute-scripts/build-sandbox-context.d.ts.map +1 -1
  8. package/dist/libs/execute-scripts/build-sandbox-context.js +6 -5
  9. package/dist/libs/execute-scripts/build-sandbox-context.js.map +1 -1
  10. package/dist/libs/execute-scripts/postman-adapter/in-process-transport.d.ts +7 -0
  11. package/dist/libs/execute-scripts/postman-adapter/in-process-transport.d.ts.map +1 -0
  12. package/dist/libs/execute-scripts/postman-adapter/sandbox-adapter.d.ts +35 -0
  13. package/dist/libs/execute-scripts/postman-adapter/sandbox-adapter.d.ts.map +1 -1
  14. package/dist/libs/execute-scripts/postman-adapter/sandbox-adapter.js +174 -109
  15. package/dist/libs/execute-scripts/postman-adapter/sandbox-adapter.js.map +1 -1
  16. package/dist/libs/execute-scripts/postman-adapter/sandbox-frame-entry.d.ts +2 -0
  17. package/dist/libs/execute-scripts/postman-adapter/sandbox-frame-entry.d.ts.map +1 -0
  18. package/dist/libs/execute-scripts/postman-adapter/sandbox-frame-entry.js +12 -0
  19. package/dist/libs/execute-scripts/postman-adapter/sandbox-frame-entry.js.map +1 -0
  20. package/dist/libs/execute-scripts/postman-adapter/sandbox-frame-server.d.ts +19 -0
  21. package/dist/libs/execute-scripts/postman-adapter/sandbox-frame-server.d.ts.map +1 -0
  22. package/dist/libs/execute-scripts/postman-adapter/sandbox-frame-server.js +187 -0
  23. package/dist/libs/execute-scripts/postman-adapter/sandbox-frame-server.js.map +1 -0
  24. package/dist/libs/execute-scripts/postman-adapter/sandbox-protocol.d.ts +92 -0
  25. package/dist/libs/execute-scripts/postman-adapter/sandbox-protocol.d.ts.map +1 -0
  26. package/dist/libs/execute-scripts/postman-adapter/sandbox-protocol.js +16 -0
  27. package/dist/libs/execute-scripts/postman-adapter/sandbox-protocol.js.map +1 -0
  28. package/dist/plugins/request-scripts/components/TestResults/TestResultIndicator.vue.d.ts.map +1 -1
  29. package/dist/plugins/request-scripts/components/TestResults/TestResultIndicator.vue.js.map +1 -1
  30. package/dist/plugins/request-scripts/components/TestResults/TestResultIndicator.vue.script.js +2 -1
  31. package/dist/plugins/request-scripts/components/TestResults/TestResultIndicator.vue.script.js.map +1 -1
  32. package/dist/plugins/request-scripts/components/TestResults/TestResultItem.vue.js.map +1 -1
  33. package/dist/plugins/request-scripts/components/TestResults/TestResultItem.vue.script.js +1 -1
  34. package/dist/plugins/request-scripts/components/TestResults/TestResultItem.vue.script.js.map +1 -1
  35. package/package.json +11 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @scalar/scripts
2
2
 
3
+ ## 0.4.16
4
+
5
+ ### Patch Changes
6
+
7
+ - [#9291](https://github.com/scalar/scalar/pull/9291): chore: import from `@scalar/components` subpaths instead of the package barrel for better tree-shaking
8
+ - [#9260](https://github.com/scalar/scalar/pull/9260): chore: remove `'unsafe-eval'` from the desktop/web app CSP
9
+
10
+ Pre-request and post-response scripts run through `postman-sandbox`, which relies on `eval`. Instead of allowing `'unsafe-eval'` in the main application CSP, script execution now happens inside an isolated sandbox iframe (`sandbox.html`) that is loaded from a real same-origin URL and carries its own permissive CSP. The host talks to it over `postMessage`, so the main `script-src` no longer needs `'unsafe-eval'`.
11
+
3
12
  ## 0.4.15
4
13
 
5
14
  ## 0.4.14
@@ -9,8 +9,8 @@ declare const __VLS_export: <T>(__VLS_props: NonNullable<Awaited<typeof __VLS_se
9
9
  props: import("vue").PublicProps & __VLS_PrettifyLocal<{
10
10
  value: T;
11
11
  } & {
12
- onUnmount?: (() => any) | undefined;
13
12
  onChange?: ((value: T) => any) | undefined;
13
+ onUnmount?: (() => any) | undefined;
14
14
  }> & (typeof globalThis extends {
15
15
  __VLS_PROPS_FALLBACK: infer P;
16
16
  } ? P : {});
@@ -1 +1 @@
1
- {"version":3,"file":"ViewLayoutCollapse.vue.js","names":[],"sources":["../../../src/components/ViewLayout/ViewLayoutCollapse.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Simple collapsible section component that can be used to wrap content that should be collapsed and expanded\n *\n * Would like to replace with details/summary elements, but they are not supported in all browsers yet?\n */\nexport default {\n name: 'CollapsibleSection',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\nimport { ScalarIcon } from '@scalar/components'\nimport { useId } from 'vue'\n\nimport ValueEmitter from './ValueEmitter.vue'\n\nconst {\n defaultOpen = true,\n itemCount = 0,\n isStatic,\n} = defineProps<{\n /** Whether the disclosure is open by default. */\n defaultOpen?: boolean\n /** Number of items to show in badge when collapsed. */\n itemCount?: number\n /** Whether the disclosure is static and cannot be toggled. */\n isStatic?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst headingId = useId()\n</script>\n\n<template>\n <Disclosure\n v-slot=\"{ open }\"\n as=\"div\"\n class=\"group/collapse text-c-2 focus-within:text-c-1 last:ui-open:border-b-0 border-b\"\n :class=\"isStatic && 'last-of-type:first-of-type:border-b-0'\"\n :defaultOpen=\"defaultOpen\"\n :static=\"isStatic\">\n <!-- We use this hack to emit the slot value back to the parent -->\n <ValueEmitter\n :value=\"open as boolean\"\n @change=\"(value) => emit('update:modelValue', value)\" />\n\n <section\n :aria-labelledby=\"headingId\"\n class=\"contents\">\n <div\n class=\"bg-b-2 flex items-center\"\n :class=\"isStatic && 'rounded-t-lg border border-b-0'\">\n <!-- Main disclosure button that toggles the panel -->\n <DisclosureButton\n class=\"hover:text-c-1 group box-content flex max-h-8 flex-1 items-center gap-2.5 overflow-hidden px-1 py-1.5 text-base font-medium outline-none md:px-1.5 xl:pr-0.5 xl:pl-2\"\n :class=\"isStatic && '!pl-3'\"\n :disabled=\"isStatic\">\n <ScalarIcon\n v-if=\"!isStatic\"\n class=\"text-c-3 group-hover:text-c-1 rounded-px ui-open:rotate-90 ui-not-open:rotate-0 outline-offset-2 group-focus-visible:outline\"\n icon=\"ChevronRight\"\n size=\"md\" />\n\n <!-- Heading with title -->\n <h2\n class=\"text-c-1 m-0 flex flex-1 items-center gap-1.5 leading-[20px]\">\n <span\n :id=\"headingId\"\n class=\"contents\">\n <slot\n name=\"title\"\n :open=\"open\" />\n <span\n v-if=\"!open\"\n class=\"sr-only\">\n (Collapsed)\n </span>\n </span>\n\n <!-- Badge showing item count when collapsed. -->\n <span\n v-if=\"!open && itemCount\"\n class=\"bg-b-2 text-c-2 inline-flex h-5 w-5 items-center justify-center rounded-full border text-xs font-semibold\">\n {{ itemCount }}\n <span class=\"sr-only\">Item{{ itemCount === 1 ? '' : 's' }}</span>\n </span>\n </h2>\n </DisclosureButton>\n\n <!-- Optional actions slot that hides when the panel is closed. -->\n <div\n v-if=\"$slots.actions\"\n class=\"ui-not-open:hidden flex items-center gap-2 pr-0.75\">\n <slot\n name=\"actions\"\n :open=\"open\" />\n </div>\n </div>\n\n <!-- The collapsible content panel. -->\n <DisclosurePanel\n v-bind=\"$attrs\"\n class=\"diclosure-panel h-full max-h-fit rounded-b\">\n <slot :open=\"open\" />\n </DisclosurePanel>\n </section>\n </Disclosure>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ViewLayoutCollapse.vue.js","names":[],"sources":["../../../src/components/ViewLayout/ViewLayoutCollapse.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Simple collapsible section component that can be used to wrap content that should be collapsed and expanded\n *\n * Would like to replace with details/summary elements, but they are not supported in all browsers yet?\n */\nexport default {\n name: 'CollapsibleSection',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { useId } from 'vue'\n\nimport ValueEmitter from './ValueEmitter.vue'\n\nconst {\n defaultOpen = true,\n itemCount = 0,\n isStatic,\n} = defineProps<{\n /** Whether the disclosure is open by default. */\n defaultOpen?: boolean\n /** Number of items to show in badge when collapsed. */\n itemCount?: number\n /** Whether the disclosure is static and cannot be toggled. */\n isStatic?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst headingId = useId()\n</script>\n\n<template>\n <Disclosure\n v-slot=\"{ open }\"\n as=\"div\"\n class=\"group/collapse text-c-2 focus-within:text-c-1 last:ui-open:border-b-0 border-b\"\n :class=\"isStatic && 'last-of-type:first-of-type:border-b-0'\"\n :defaultOpen=\"defaultOpen\"\n :static=\"isStatic\">\n <!-- We use this hack to emit the slot value back to the parent -->\n <ValueEmitter\n :value=\"open as boolean\"\n @change=\"(value) => emit('update:modelValue', value)\" />\n\n <section\n :aria-labelledby=\"headingId\"\n class=\"contents\">\n <div\n class=\"bg-b-2 flex items-center\"\n :class=\"isStatic && 'rounded-t-lg border border-b-0'\">\n <!-- Main disclosure button that toggles the panel -->\n <DisclosureButton\n class=\"hover:text-c-1 group box-content flex max-h-8 flex-1 items-center gap-2.5 overflow-hidden px-1 py-1.5 text-base font-medium outline-none md:px-1.5 xl:pr-0.5 xl:pl-2\"\n :class=\"isStatic && '!pl-3'\"\n :disabled=\"isStatic\">\n <ScalarIcon\n v-if=\"!isStatic\"\n class=\"text-c-3 group-hover:text-c-1 rounded-px ui-open:rotate-90 ui-not-open:rotate-0 outline-offset-2 group-focus-visible:outline\"\n icon=\"ChevronRight\"\n size=\"md\" />\n\n <!-- Heading with title -->\n <h2\n class=\"text-c-1 m-0 flex flex-1 items-center gap-1.5 leading-[20px]\">\n <span\n :id=\"headingId\"\n class=\"contents\">\n <slot\n name=\"title\"\n :open=\"open\" />\n <span\n v-if=\"!open\"\n class=\"sr-only\">\n (Collapsed)\n </span>\n </span>\n\n <!-- Badge showing item count when collapsed. -->\n <span\n v-if=\"!open && itemCount\"\n class=\"bg-b-2 text-c-2 inline-flex h-5 w-5 items-center justify-center rounded-full border text-xs font-semibold\">\n {{ itemCount }}\n <span class=\"sr-only\">Item{{ itemCount === 1 ? '' : 's' }}</span>\n </span>\n </h2>\n </DisclosureButton>\n\n <!-- Optional actions slot that hides when the panel is closed. -->\n <div\n v-if=\"$slots.actions\"\n class=\"ui-not-open:hidden flex items-center gap-2 pr-0.75\">\n <slot\n name=\"actions\"\n :open=\"open\" />\n </div>\n </div>\n\n <!-- The collapsible content panel. -->\n <DisclosurePanel\n v-bind=\"$attrs\"\n class=\"diclosure-panel h-full max-h-fit rounded-b\">\n <slot :open=\"open\" />\n </DisclosurePanel>\n </section>\n </Disclosure>\n</template>\n"],"mappings":""}
@@ -1,7 +1,7 @@
1
1
  import ValueEmitter_default from "./ValueEmitter.vue.js";
2
2
  import { createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeProps, normalizeClass, openBlock, renderSlot, toDisplayString, unref, useId, withCtx } from "vue";
3
3
  import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/vue";
4
- import { ScalarIcon } from "@scalar/components";
4
+ import { ScalarIcon } from "@scalar/components/icon";
5
5
  //#region src/components/ViewLayout/ViewLayoutCollapse.vue?vue&type=script&setup=true&lang.ts
6
6
  var _hoisted_1 = ["aria-labelledby"];
7
7
  var _hoisted_2 = { class: "text-c-1 m-0 flex flex-1 items-center gap-1.5 leading-[20px]" };
@@ -1 +1 @@
1
- {"version":3,"file":"ViewLayoutCollapse.vue.script.js","names":["$slots","$attrs"],"sources":["../../../src/components/ViewLayout/ViewLayoutCollapse.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Simple collapsible section component that can be used to wrap content that should be collapsed and expanded\n *\n * Would like to replace with details/summary elements, but they are not supported in all browsers yet?\n */\nexport default {\n name: 'CollapsibleSection',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\nimport { ScalarIcon } from '@scalar/components'\nimport { useId } from 'vue'\n\nimport ValueEmitter from './ValueEmitter.vue'\n\nconst {\n defaultOpen = true,\n itemCount = 0,\n isStatic,\n} = defineProps<{\n /** Whether the disclosure is open by default. */\n defaultOpen?: boolean\n /** Number of items to show in badge when collapsed. */\n itemCount?: number\n /** Whether the disclosure is static and cannot be toggled. */\n isStatic?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst headingId = useId()\n</script>\n\n<template>\n <Disclosure\n v-slot=\"{ open }\"\n as=\"div\"\n class=\"group/collapse text-c-2 focus-within:text-c-1 last:ui-open:border-b-0 border-b\"\n :class=\"isStatic && 'last-of-type:first-of-type:border-b-0'\"\n :defaultOpen=\"defaultOpen\"\n :static=\"isStatic\">\n <!-- We use this hack to emit the slot value back to the parent -->\n <ValueEmitter\n :value=\"open as boolean\"\n @change=\"(value) => emit('update:modelValue', value)\" />\n\n <section\n :aria-labelledby=\"headingId\"\n class=\"contents\">\n <div\n class=\"bg-b-2 flex items-center\"\n :class=\"isStatic && 'rounded-t-lg border border-b-0'\">\n <!-- Main disclosure button that toggles the panel -->\n <DisclosureButton\n class=\"hover:text-c-1 group box-content flex max-h-8 flex-1 items-center gap-2.5 overflow-hidden px-1 py-1.5 text-base font-medium outline-none md:px-1.5 xl:pr-0.5 xl:pl-2\"\n :class=\"isStatic && '!pl-3'\"\n :disabled=\"isStatic\">\n <ScalarIcon\n v-if=\"!isStatic\"\n class=\"text-c-3 group-hover:text-c-1 rounded-px ui-open:rotate-90 ui-not-open:rotate-0 outline-offset-2 group-focus-visible:outline\"\n icon=\"ChevronRight\"\n size=\"md\" />\n\n <!-- Heading with title -->\n <h2\n class=\"text-c-1 m-0 flex flex-1 items-center gap-1.5 leading-[20px]\">\n <span\n :id=\"headingId\"\n class=\"contents\">\n <slot\n name=\"title\"\n :open=\"open\" />\n <span\n v-if=\"!open\"\n class=\"sr-only\">\n (Collapsed)\n </span>\n </span>\n\n <!-- Badge showing item count when collapsed. -->\n <span\n v-if=\"!open && itemCount\"\n class=\"bg-b-2 text-c-2 inline-flex h-5 w-5 items-center justify-center rounded-full border text-xs font-semibold\">\n {{ itemCount }}\n <span class=\"sr-only\">Item{{ itemCount === 1 ? '' : 's' }}</span>\n </span>\n </h2>\n </DisclosureButton>\n\n <!-- Optional actions slot that hides when the panel is closed. -->\n <div\n v-if=\"$slots.actions\"\n class=\"ui-not-open:hidden flex items-center gap-2 pr-0.75\">\n <slot\n name=\"actions\"\n :open=\"open\" />\n </div>\n </div>\n\n <!-- The collapsible content panel. -->\n <DisclosurePanel\n v-bind=\"$attrs\"\n class=\"diclosure-panel h-full max-h-fit rounded-b\">\n <slot :open=\"open\" />\n </DisclosurePanel>\n </section>\n </Disclosure>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;CAOE,MAAM;;;;;;;;;;;EAwBR,MAAM,OAAO;EAIb,MAAM,YAAY,OAAM;;uBAItB,YAwEa,MAAA,WAAA,EAAA;IAtEX,IAAG;IACH,OAAK,eAAA,CAAC,kFACE,QAAA,YAAQ,wCAAA,CAAA;IACf,aAAa,QAAA;IACb,QAAQ,QAAA;;sBAIiD,EAThD,WAAI,CAOd,YAE0D,sBAAA;KADvD,OAAO;KACP,UAAM,OAAA,OAAA,OAAA,MAAG,UAAU,KAAI,qBAAsB,MAAK;4BAErD,mBA2DU,WAAA;KA1DP,mBAAiB,MAAA,UAAS;KAC3B,OAAM;QACN,mBAgDM,OAAA,EA/CJ,OAAK,eAAA,CAAC,4BACE,QAAA,YAAQ,iCAAA,CAAA,EAAA,EAAA,CAEhB,YAkCmB,MAAA,iBAAA,EAAA;KAjCjB,OAAK,eAAA,CAAC,wKACE,QAAA,YAAQ,QAAA,CAAA;KACf,UAAU,QAAA;;4BAKG,CAAA,CAHL,QAAA,YAAA,WAAA,EADT,YAIc,MAAA,WAAA,EAAA;;MAFZ,OAAM;MACN,MAAK;MACL,MAAK;yCAGP,mBAsBK,MAtBL,YAsBK,CApBH,mBAWO,QAAA;MAVJ,IAAI,MAAA,UAAS;MACd,OAAM;SACN,WAEiB,KAAA,QAAA,SAAA,EAAR,MAAI,CAAA,EAAA,CAEJ,QAAA,WAAA,EADT,mBAIO,QAJP,YAEkB,gBAElB,IAAA,mBAAA,IAAA,KAAA,CAAA,EAAA,GAAA,WAAA,EAAA,CAKO,QAAQ,QAAA,aAAA,WAAA,EADjB,mBAKO,QALP,YAKO,CAAA,gBAAA,gBAFF,QAAA,UAAS,GAAG,KACf,EAAA,EAAA,mBAAiE,QAAjE,YAAsB,SAAI,gBAAG,QAAA,cAAS,IAAA,KAAA,IAAA,EAAA,EAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,CAAA,CAAA;;qCAOpCA,KAAAA,OAAO,WAAA,WAAA,EADf,mBAMM,OANN,YAMM,CAHJ,WAEiB,KAAA,QAAA,WAAA,EAAR,MAAI,CAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,EAAA,EAAA,EAKjB,YAIkB,MAAA,gBAAA,EAJlB,WACUC,KAGQ,QAHF,EACd,OAAM,8CAA4C,CAAA,EAAA;4BAC7B,CAArB,WAAqB,KAAA,QAAA,WAAA,EAAR,MAAI,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"ViewLayoutCollapse.vue.script.js","names":["$slots","$attrs"],"sources":["../../../src/components/ViewLayout/ViewLayoutCollapse.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * Simple collapsible section component that can be used to wrap content that should be collapsed and expanded\n *\n * Would like to replace with details/summary elements, but they are not supported in all browsers yet?\n */\nexport default {\n name: 'CollapsibleSection',\n}\n</script>\n\n<script setup lang=\"ts\">\nimport { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'\nimport { ScalarIcon } from '@scalar/components/icon'\nimport { useId } from 'vue'\n\nimport ValueEmitter from './ValueEmitter.vue'\n\nconst {\n defaultOpen = true,\n itemCount = 0,\n isStatic,\n} = defineProps<{\n /** Whether the disclosure is open by default. */\n defaultOpen?: boolean\n /** Number of items to show in badge when collapsed. */\n itemCount?: number\n /** Whether the disclosure is static and cannot be toggled. */\n isStatic?: boolean\n}>()\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst headingId = useId()\n</script>\n\n<template>\n <Disclosure\n v-slot=\"{ open }\"\n as=\"div\"\n class=\"group/collapse text-c-2 focus-within:text-c-1 last:ui-open:border-b-0 border-b\"\n :class=\"isStatic && 'last-of-type:first-of-type:border-b-0'\"\n :defaultOpen=\"defaultOpen\"\n :static=\"isStatic\">\n <!-- We use this hack to emit the slot value back to the parent -->\n <ValueEmitter\n :value=\"open as boolean\"\n @change=\"(value) => emit('update:modelValue', value)\" />\n\n <section\n :aria-labelledby=\"headingId\"\n class=\"contents\">\n <div\n class=\"bg-b-2 flex items-center\"\n :class=\"isStatic && 'rounded-t-lg border border-b-0'\">\n <!-- Main disclosure button that toggles the panel -->\n <DisclosureButton\n class=\"hover:text-c-1 group box-content flex max-h-8 flex-1 items-center gap-2.5 overflow-hidden px-1 py-1.5 text-base font-medium outline-none md:px-1.5 xl:pr-0.5 xl:pl-2\"\n :class=\"isStatic && '!pl-3'\"\n :disabled=\"isStatic\">\n <ScalarIcon\n v-if=\"!isStatic\"\n class=\"text-c-3 group-hover:text-c-1 rounded-px ui-open:rotate-90 ui-not-open:rotate-0 outline-offset-2 group-focus-visible:outline\"\n icon=\"ChevronRight\"\n size=\"md\" />\n\n <!-- Heading with title -->\n <h2\n class=\"text-c-1 m-0 flex flex-1 items-center gap-1.5 leading-[20px]\">\n <span\n :id=\"headingId\"\n class=\"contents\">\n <slot\n name=\"title\"\n :open=\"open\" />\n <span\n v-if=\"!open\"\n class=\"sr-only\">\n (Collapsed)\n </span>\n </span>\n\n <!-- Badge showing item count when collapsed. -->\n <span\n v-if=\"!open && itemCount\"\n class=\"bg-b-2 text-c-2 inline-flex h-5 w-5 items-center justify-center rounded-full border text-xs font-semibold\">\n {{ itemCount }}\n <span class=\"sr-only\">Item{{ itemCount === 1 ? '' : 's' }}</span>\n </span>\n </h2>\n </DisclosureButton>\n\n <!-- Optional actions slot that hides when the panel is closed. -->\n <div\n v-if=\"$slots.actions\"\n class=\"ui-not-open:hidden flex items-center gap-2 pr-0.75\">\n <slot\n name=\"actions\"\n :open=\"open\" />\n </div>\n </div>\n\n <!-- The collapsible content panel. -->\n <DisclosurePanel\n v-bind=\"$attrs\"\n class=\"diclosure-panel h-full max-h-fit rounded-b\">\n <slot :open=\"open\" />\n </DisclosurePanel>\n </section>\n </Disclosure>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;CAOE,MAAM;;;;;;;;;;;EAwBR,MAAM,OAAO;EAIb,MAAM,YAAY,OAAM;;uBAItB,YAwEa,MAAA,WAAA,EAAA;IAtEX,IAAG;IACH,OAAK,eAAA,CAAC,kFACE,QAAA,YAAQ,wCAAA,CAAA;IACf,aAAa,QAAA;IACb,QAAQ,QAAA;;sBAIiD,EAThD,WAAI,CAOd,YAE0D,sBAAA;KADvD,OAAO;KACP,UAAM,OAAA,OAAA,OAAA,MAAG,UAAU,KAAI,qBAAsB,MAAK;4BAErD,mBA2DU,WAAA;KA1DP,mBAAiB,MAAA,UAAS;KAC3B,OAAM;QACN,mBAgDM,OAAA,EA/CJ,OAAK,eAAA,CAAC,4BACE,QAAA,YAAQ,iCAAA,CAAA,EAAA,EAAA,CAEhB,YAkCmB,MAAA,iBAAA,EAAA;KAjCjB,OAAK,eAAA,CAAC,wKACE,QAAA,YAAQ,QAAA,CAAA;KACf,UAAU,QAAA;;4BAKG,CAAA,CAHL,QAAA,YAAA,WAAA,EADT,YAIc,MAAA,WAAA,EAAA;;MAFZ,OAAM;MACN,MAAK;MACL,MAAK;yCAGP,mBAsBK,MAtBL,YAsBK,CApBH,mBAWO,QAAA;MAVJ,IAAI,MAAA,UAAS;MACd,OAAM;SACN,WAEiB,KAAA,QAAA,SAAA,EAAR,MAAI,CAAA,EAAA,CAEJ,QAAA,WAAA,EADT,mBAIO,QAJP,YAEkB,gBAElB,IAAA,mBAAA,IAAA,KAAA,CAAA,EAAA,GAAA,WAAA,EAAA,CAKO,QAAQ,QAAA,aAAA,WAAA,EADjB,mBAKO,QALP,YAKO,CAAA,gBAAA,gBAFF,QAAA,UAAS,GAAG,KACf,EAAA,EAAA,mBAAiE,QAAjE,YAAsB,SAAI,gBAAG,QAAA,cAAS,IAAA,KAAA,IAAA,EAAA,EAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA,CAAA,CAAA;;qCAOpCA,KAAAA,OAAO,WAAA,WAAA,EADf,mBAMM,OANN,YAMM,CAHJ,WAEiB,KAAA,QAAA,WAAA,EAAR,MAAI,CAAA,CAAA,CAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,EAAA,EAAA,EAKjB,YAIkB,MAAA,gBAAA,EAJlB,WACUC,KAGQ,QAHF,EACd,OAAM,8CAA4C,CAAA,EAAA;4BAC7B,CAArB,WAAqB,KAAA,QAAA,WAAA,EAAR,MAAI,CAAA,CAAA,CAAA"}
@@ -1,15 +1,20 @@
1
- import type { VariablesStore } from '@scalar/workspace-store/request-example';
2
1
  import { VariableScope } from 'postman-collection';
3
- /**
4
- * Builds the Postman sandbox context object for variables (globals, environment,
5
- * collection, data, _variables) from a VariablesStore. Used so pm.variables.get/set
6
- * and precedence match Postman behavior.
7
- */
8
- export declare function buildSandboxContextFromStore(store: VariablesStore): {
2
+ import type { SerializableScopes } from './postman-adapter/sandbox-protocol.js';
3
+ type SandboxVariableContext = {
9
4
  globals: VariableScope;
10
5
  collectionVariables: VariableScope;
11
6
  environment: VariableScope;
12
7
  data: Record<string, string>;
13
8
  _variables: VariableScope;
14
9
  };
10
+ /**
11
+ * Builds the Postman sandbox context object for variables (globals, environment,
12
+ * collection, data, _variables) from plain key/value scopes. Used so pm.variables.get/set
13
+ * and precedence match Postman behavior.
14
+ *
15
+ * This depends on postman-collection (`VariableScope`) but not on postman-sandbox, and is meant
16
+ * to run inside the sandbox iframe after the scopes have crossed the `postMessage` boundary.
17
+ */
18
+ export declare function buildSandboxContextFromScopes(scopes: SerializableScopes): SandboxVariableContext;
19
+ export {};
15
20
  //# sourceMappingURL=build-sandbox-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-sandbox-context.d.ts","sourceRoot":"","sources":["../../../src/libs/execute-scripts/build-sandbox-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,yCAAyC,CAAA;AAC5F,OAAO,EAAgB,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAgBhE;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,cAAc,GAAG;IACnE,OAAO,EAAE,aAAa,CAAA;IACtB,mBAAmB,EAAE,aAAa,CAAA;IAClC,WAAW,EAAE,aAAa,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,UAAU,EAAE,aAAa,CAAA;CAC1B,CASA"}
1
+ {"version":3,"file":"build-sandbox-context.d.ts","sourceRoot":"","sources":["../../../src/libs/execute-scripts/build-sandbox-context.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEhE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAA;AAc5E,KAAK,sBAAsB,GAAG;IAC5B,OAAO,EAAE,aAAa,CAAA;IACtB,mBAAmB,EAAE,aAAa,CAAA;IAClC,WAAW,EAAE,aAAa,CAAA;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,UAAU,EAAE,aAAa,CAAA;CAC1B,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,kBAAkB,GAAG,sBAAsB,CAQhG"}
@@ -1,4 +1,3 @@
1
- import { getVariableScopesFromStore } from "./variables-store.js";
2
1
  import { VariableList, VariableScope } from "postman-collection";
3
2
  //#region src/libs/execute-scripts/build-sandbox-context.ts
4
3
  function toVariableList(entries) {
@@ -10,11 +9,13 @@ function toVariableList(entries) {
10
9
  }
11
10
  /**
12
11
  * Builds the Postman sandbox context object for variables (globals, environment,
13
- * collection, data, _variables) from a VariablesStore. Used so pm.variables.get/set
12
+ * collection, data, _variables) from plain key/value scopes. Used so pm.variables.get/set
14
13
  * and precedence match Postman behavior.
14
+ *
15
+ * This depends on postman-collection (`VariableScope`) but not on postman-sandbox, and is meant
16
+ * to run inside the sandbox iframe after the scopes have crossed the `postMessage` boundary.
15
17
  */
16
- function buildSandboxContextFromStore(store) {
17
- const scopes = getVariableScopesFromStore(store);
18
+ function buildSandboxContextFromScopes(scopes) {
18
19
  return {
19
20
  globals: new VariableScope(toVariableList(scopes.globals)),
20
21
  collectionVariables: new VariableScope(toVariableList(scopes.collectionVariables)),
@@ -24,6 +25,6 @@ function buildSandboxContextFromStore(store) {
24
25
  };
25
26
  }
26
27
  //#endregion
27
- export { buildSandboxContextFromStore };
28
+ export { buildSandboxContextFromScopes };
28
29
 
29
30
  //# sourceMappingURL=build-sandbox-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-sandbox-context.js","names":[],"sources":["../../../src/libs/execute-scripts/build-sandbox-context.ts"],"sourcesContent":["import type { VariableEntry, VariablesStore } from '@scalar/workspace-store/request-example'\nimport { VariableList, VariableScope } from 'postman-collection'\n\nimport { getVariableScopesFromStore } from './variables-store'\n\nfunction toVariableList(entries: VariableEntry[]): VariableList {\n if (entries.length === 0) {\n // @ts-expect-error - VariableList constructor expects a Property<PropertyDefinition>\n return new VariableList(null, [])\n }\n return new VariableList(\n // @ts-expect-error - VariableList constructor expects a Property<PropertyDefinition>\n null,\n entries.map((e) => ({ key: e.key, value: e.value })),\n )\n}\n\n/**\n * Builds the Postman sandbox context object for variables (globals, environment,\n * collection, data, _variables) from a VariablesStore. Used so pm.variables.get/set\n * and precedence match Postman behavior.\n */\nexport function buildSandboxContextFromStore(store: VariablesStore): {\n globals: VariableScope\n collectionVariables: VariableScope\n environment: VariableScope\n data: Record<string, string>\n _variables: VariableScope\n} {\n const scopes = getVariableScopesFromStore(store)\n return {\n globals: new VariableScope(toVariableList(scopes.globals)),\n collectionVariables: new VariableScope(toVariableList(scopes.collectionVariables)),\n environment: new VariableScope(toVariableList(scopes.environment)),\n data: scopes.data,\n _variables: new VariableScope(toVariableList(scopes.local)),\n }\n}\n"],"mappings":";;;AAKA,SAAS,eAAe,SAAwC;AAC9D,KAAI,QAAQ,WAAW,EAErB,QAAO,IAAI,aAAa,MAAM,EAAE,CAAC;AAEnC,QAAO,IAAI,aAET,MACA,QAAQ,KAAK,OAAO;EAAE,KAAK,EAAE;EAAK,OAAO,EAAE;EAAO,EAAE,CACrD;;;;;;;AAQH,SAAgB,6BAA6B,OAM3C;CACA,MAAM,SAAS,2BAA2B,MAAM;AAChD,QAAO;EACL,SAAS,IAAI,cAAc,eAAe,OAAO,QAAQ,CAAC;EAC1D,qBAAqB,IAAI,cAAc,eAAe,OAAO,oBAAoB,CAAC;EAClF,aAAa,IAAI,cAAc,eAAe,OAAO,YAAY,CAAC;EAClE,MAAM,OAAO;EACb,YAAY,IAAI,cAAc,eAAe,OAAO,MAAM,CAAC;EAC5D"}
1
+ {"version":3,"file":"build-sandbox-context.js","names":[],"sources":["../../../src/libs/execute-scripts/build-sandbox-context.ts"],"sourcesContent":["import type { VariableEntry } from '@scalar/workspace-store/request-example'\nimport { VariableList, VariableScope } from 'postman-collection'\n\nimport type { SerializableScopes } from './postman-adapter/sandbox-protocol'\n\nfunction toVariableList(entries: VariableEntry[]): VariableList {\n if (entries.length === 0) {\n // @ts-expect-error - VariableList constructor expects a Property<PropertyDefinition>\n return new VariableList(null, [])\n }\n return new VariableList(\n // @ts-expect-error - VariableList constructor expects a Property<PropertyDefinition>\n null,\n entries.map((e) => ({ key: e.key, value: e.value })),\n )\n}\n\ntype SandboxVariableContext = {\n globals: VariableScope\n collectionVariables: VariableScope\n environment: VariableScope\n data: Record<string, string>\n _variables: VariableScope\n}\n\n/**\n * Builds the Postman sandbox context object for variables (globals, environment,\n * collection, data, _variables) from plain key/value scopes. Used so pm.variables.get/set\n * and precedence match Postman behavior.\n *\n * This depends on postman-collection (`VariableScope`) but not on postman-sandbox, and is meant\n * to run inside the sandbox iframe after the scopes have crossed the `postMessage` boundary.\n */\nexport function buildSandboxContextFromScopes(scopes: SerializableScopes): SandboxVariableContext {\n return {\n globals: new VariableScope(toVariableList(scopes.globals)),\n collectionVariables: new VariableScope(toVariableList(scopes.collectionVariables)),\n environment: new VariableScope(toVariableList(scopes.environment)),\n data: scopes.data,\n _variables: new VariableScope(toVariableList(scopes.local)),\n }\n}\n"],"mappings":";;AAKA,SAAS,eAAe,SAAwC;AAC9D,KAAI,QAAQ,WAAW,EAErB,QAAO,IAAI,aAAa,MAAM,EAAE,CAAC;AAEnC,QAAO,IAAI,aAET,MACA,QAAQ,KAAK,OAAO;EAAE,KAAK,EAAE;EAAK,OAAO,EAAE;EAAO,EAAE,CACrD;;;;;;;;;;AAmBH,SAAgB,8BAA8B,QAAoD;AAChG,QAAO;EACL,SAAS,IAAI,cAAc,eAAe,OAAO,QAAQ,CAAC;EAC1D,qBAAqB,IAAI,cAAc,eAAe,OAAO,oBAAoB,CAAC;EAClF,aAAa,IAAI,cAAc,eAAe,OAAO,YAAY,CAAC;EAClE,MAAM,OAAO;EACb,YAAY,IAAI,cAAc,eAAe,OAAO,MAAM,CAAC;EAC5D"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Routes the host adapter to run scripts in-process via {@link runSandboxExecution} instead of an
3
+ * iframe. Used by node tests so the real postman-sandbox pipeline (which needs no DOM in node) can be
4
+ * exercised end-to-end. Never call this in production: it pulls `postman-sandbox` into the host.
5
+ */
6
+ export declare const registerInProcessSandbox: () => void;
7
+ //# sourceMappingURL=in-process-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-process-transport.d.ts","sourceRoot":"","sources":["../../../../src/libs/execute-scripts/postman-adapter/in-process-transport.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,QAAO,IAc3C,CAAA"}
@@ -1,6 +1,40 @@
1
1
  import type { RequestFactory, VariablesStore } from '@scalar/workspace-store/request-example';
2
2
  import type { ConsoleContext } from '../context/console.js';
3
3
  import type { TestResult } from '../execute-post-response-script.js';
4
+ import { type PostmanResponseDefinition, type SandboxExecuteRequest, type SandboxOutboundMessage } from './sandbox-protocol.js';
5
+ /**
6
+ * Host-side adapter for pre/post-request scripts.
7
+ *
8
+ * The actual script execution happens in `postman-sandbox`, which requires `eval`. To keep
9
+ * `'unsafe-eval'` out of the main application CSP, that execution is delegated to an iframe loaded
10
+ * from a real same-origin `.html` file with its own permissive CSP (see `sandbox-frame-server.ts`).
11
+ * This module therefore must NOT import `postman-sandbox`; it only serializes inputs, talks to the
12
+ * iframe over `postMessage`, and applies the results back to the live store/request.
13
+ */
14
+ export declare const toPostmanResponse: (response: Response) => Promise<PostmanResponseDefinition>;
15
+ /**
16
+ * Resolves the sandbox iframe URL.
17
+ *
18
+ * On the web we anchor to the origin root because the SPA router owns the current path: a
19
+ * relative `sandbox.html` would resolve under the active route and get rewritten to `index.html`
20
+ * by the history fallback. In Electron the document path is literal, so a sibling resolution
21
+ * works — and we use the `isElectron` helper rather than a `file://` check because Electron's
22
+ * dev mode is served over `http://localhost`.
23
+ *
24
+ * Exported for tests; production code reaches it through `ensureSandboxFrame`.
25
+ */
26
+ export declare const sandboxFrameUrl: () => string;
27
+ /**
28
+ * Delivers a single execute request to the sandbox and forwards every message it produces (test
29
+ * results, console output, and the final `done`) to `onMessage`.
30
+ *
31
+ * The default transport is the iframe (see {@link iframeTransport}). It is injectable so that node
32
+ * unit tests can run the real execution in-process (`runSandboxExecution`) without a DOM, and without
33
+ * the production host bundle ever importing `postman-sandbox`.
34
+ */
35
+ type SandboxTransport = (request: SandboxExecuteRequest, onMessage: (message: SandboxOutboundMessage) => void) => void;
36
+ /** Override how the host reaches the sandbox (e.g. an in-process transport for tests). */
37
+ export declare const setSandboxTransport: (next: SandboxTransport) => void;
4
38
  export declare const executeInPostmanSandbox: ({ script, type, context: { requestBuilder, response, variablesStore, scriptConsole }, onTestResultsUpdate, }: {
5
39
  script: string;
6
40
  type: "pre-request" | "post-response";
@@ -13,4 +47,5 @@ export declare const executeInPostmanSandbox: ({ script, type, context: { reques
13
47
  };
14
48
  onTestResultsUpdate?: ((results: TestResult[]) => void) | undefined;
15
49
  }) => Promise<VariablesStore | undefined>;
50
+ export {};
16
51
  //# sourceMappingURL=sandbox-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-adapter.d.ts","sourceRoot":"","sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAA;AAK7F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAmEjE,eAAO,MAAM,uBAAuB,GAAU,8GAK3C;IACD,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,aAAa,GAAG,eAAe,CAAA;IACrC,OAAO,EAAE;QACP,uFAAuF;QACvF,cAAc,CAAC,EAAE,cAAc,CAAA;QAC/B,QAAQ,CAAC,EAAE,QAAQ,CAAA;QACnB,cAAc,CAAC,EAAE,cAAc,CAAA;QAC/B,aAAa,EAAE,cAAc,CAAA;KAC9B,CAAA;IACD,mBAAmB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CACpE,KAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAmHrC,CAAA"}
1
+ {"version":3,"file":"sandbox-adapter.d.ts","sourceRoot":"","sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAA;AAE7F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AAGjE,OAAO,EACL,KAAK,yBAAyB,EAE9B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAAU,UAAU,QAAQ,KAAG,OAAO,CAAC,yBAAyB,CAiB7F,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,QAAO,MAKlC,CAAA;AAeD;;;;;;;GAOG;AACH,KAAK,gBAAgB,GAAG,CAAC,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,KAAK,IAAI,CAAA;AAqJtH,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,GAAI,MAAM,gBAAgB,KAAG,IAE5D,CAAA;AAID,eAAO,MAAM,uBAAuB,GAAU,8GAK3C;IACD,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,aAAa,GAAG,eAAe,CAAA;IACrC,OAAO,EAAE;QACP,uFAAuF;QACvF,cAAc,CAAC,EAAE,cAAc,CAAA;QAC/B,QAAQ,CAAC,EAAE,QAAQ,CAAA;QACnB,cAAc,CAAC,EAAE,cAAc,CAAA;QAC/B,aAAa,EAAE,cAAc,CAAA;KAC9B,CAAA;IACD,mBAAmB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CACpE,KAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CA4DrC,CAAA"}
@@ -1,11 +1,20 @@
1
- import { toVariableEntries } from "../variables-store.js";
2
- import { buildSandboxContextFromStore } from "../build-sandbox-context.js";
1
+ import { getVariableScopesFromStore, toVariableEntries } from "../variables-store.js";
3
2
  import { createPostmanRequestFromFactory, syncPlainPostmanRequestToRequestFactory } from "./request-factory-adapter.js";
4
- import Sandbox from "postman-sandbox";
3
+ import { SANDBOX_CHANNEL } from "./sandbox-protocol.js";
4
+ import { isElectron } from "@scalar/helpers/general/is-electron";
5
5
  //#region src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts
6
+ /**
7
+ * Host-side adapter for pre/post-request scripts.
8
+ *
9
+ * The actual script execution happens in `postman-sandbox`, which requires `eval`. To keep
10
+ * `'unsafe-eval'` out of the main application CSP, that execution is delegated to an iframe loaded
11
+ * from a real same-origin `.html` file with its own permissive CSP (see `sandbox-frame-server.ts`).
12
+ * This module therefore must NOT import `postman-sandbox`; it only serializes inputs, talks to the
13
+ * iframe over `postMessage`, and applies the results back to the live store/request.
14
+ */
6
15
  var toPostmanResponse = async (response) => {
7
- const responseText = await response.text();
8
- const responseBytes = Array.from(new TextEncoder().encode(responseText));
16
+ const buffer = await response.arrayBuffer();
17
+ const responseBytes = Array.from(new Uint8Array(buffer));
9
18
  return {
10
19
  code: response.status,
11
20
  status: response.statusText || String(response.status),
@@ -19,117 +28,173 @@ var toPostmanResponse = async (response) => {
19
28
  }
20
29
  };
21
30
  };
22
- var createContext = () => new Promise((resolve, reject) => {
23
- Sandbox.createContext((error, context) => {
24
- if (error) {
25
- reject(error);
26
- return;
27
- }
28
- resolve(context);
31
+ /**
32
+ * Resolves the sandbox iframe URL.
33
+ *
34
+ * On the web we anchor to the origin root because the SPA router owns the current path: a
35
+ * relative `sandbox.html` would resolve under the active route and get rewritten to `index.html`
36
+ * by the history fallback. In Electron the document path is literal, so a sibling resolution
37
+ * works — and we use the `isElectron` helper rather than a `file://` check because Electron's
38
+ * dev mode is served over `http://localhost`.
39
+ *
40
+ * Exported for tests; production code reaches it through `ensureSandboxFrame`.
41
+ */
42
+ var sandboxFrameUrl = () => {
43
+ if (isElectron()) return new URL("sandbox.html", document.baseURI).href;
44
+ return new URL("/sandbox.html", window.location.origin).href;
45
+ };
46
+ /**
47
+ * Origin we pin `postMessage` exchanges to.
48
+ *
49
+ * The sandbox iframe is served from the same origin as the host (see `sandbox.html`), so we lock
50
+ * every send and receive to `window.location.origin`. Pinning the send origin prevents the payload
51
+ * from leaking if the frame is ever navigated away to another origin; pinning the receive origin
52
+ * prevents another window (for example a cross-origin opener) from spoofing sandbox messages.
53
+ *
54
+ * For `file://` documents (Electron), `window.location.origin` is the string `'file://'`, which is
55
+ * the same value `postMessage` reports on `event.origin`, so the pin still holds.
56
+ */
57
+ var sandboxOrigin = () => window.location.origin;
58
+ var framePromise;
59
+ /**
60
+ * Maximum time we wait for a freshly created sandbox iframe to post its `ready` handshake.
61
+ *
62
+ * The iframe `error` event fires only when the `.html` document itself fails to load. If the
63
+ * document loads (HTTP 200) but the script inside never reaches `startSandboxFrameServer` — a
64
+ * 404 on the bundle, an import error, a CSP block, a crash during boot — no `ready` message is
65
+ * posted and no `error` event fires. Without this ceiling the readiness promise hangs forever;
66
+ * because it is module-cached, every later call awaits the same dead promise and all script
67
+ * execution freezes for the session. The window is generous: readiness only requires loading one
68
+ * same-origin HTML file plus the sandbox bundle, so timing out means the bundle is genuinely
69
+ * broken rather than slow.
70
+ */
71
+ var SANDBOX_READY_TIMEOUT_MS = 30 * 1e3;
72
+ /** Lazily creates a single hidden sandbox iframe and resolves once it reports readiness. */
73
+ var ensureSandboxFrame = () => {
74
+ if (framePromise) return framePromise;
75
+ framePromise = new Promise((resolve, reject) => {
76
+ const iframe = document.createElement("iframe");
77
+ iframe.setAttribute("aria-hidden", "true");
78
+ iframe.style.display = "none";
79
+ iframe.src = sandboxFrameUrl();
80
+ const expectedOrigin = sandboxOrigin();
81
+ let readyTimeoutId;
82
+ const stopWaiting = () => {
83
+ clearTimeout(readyTimeoutId);
84
+ window.removeEventListener("message", onReady);
85
+ };
86
+ const onReady = (event) => {
87
+ if (event.origin !== expectedOrigin || event.source !== iframe.contentWindow) return;
88
+ const data = event.data;
89
+ if (!data || data.channel !== "scalar-pre-post-request-scripts-sandbox" || data.kind !== "ready") return;
90
+ stopWaiting();
91
+ if (!iframe.contentWindow) {
92
+ iframe.remove();
93
+ framePromise = void 0;
94
+ reject(/* @__PURE__ */ new Error("Sandbox iframe has no content window"));
95
+ return;
96
+ }
97
+ resolve(iframe.contentWindow);
98
+ };
99
+ iframe.addEventListener("error", () => {
100
+ stopWaiting();
101
+ iframe.remove();
102
+ framePromise = void 0;
103
+ reject(/* @__PURE__ */ new Error("Failed to load the script sandbox iframe"));
104
+ });
105
+ readyTimeoutId = setTimeout(() => {
106
+ window.removeEventListener("message", onReady);
107
+ iframe.remove();
108
+ framePromise = void 0;
109
+ reject(/* @__PURE__ */ new Error(`Sandbox iframe did not report readiness within ${SANDBOX_READY_TIMEOUT_MS}ms`));
110
+ }, SANDBOX_READY_TIMEOUT_MS);
111
+ window.addEventListener("message", onReady);
112
+ document.body.appendChild(iframe);
29
113
  });
30
- });
31
- var toErrorMessage = (error) => {
32
- if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
33
- return String(error);
114
+ return framePromise;
34
115
  };
35
- var upsertTestResult = (testResults, assertion, duration) => {
36
- const title = assertion.name || `Assertion ${assertion.index + 1}`;
37
- const nextResult = {
38
- title,
39
- passed: assertion.passed,
40
- duration,
41
- error: assertion.error?.message,
42
- status: assertion.passed ? "passed" : "failed"
43
- };
44
- const existingResultIndex = testResults.findIndex((result) => result.title === title);
45
- if (existingResultIndex === -1) {
46
- testResults.push(nextResult);
47
- return;
48
- }
49
- testResults[existingResultIndex] = nextResult;
116
+ /**
117
+ * Maximum time we wait for the iframe to emit `kind: 'done'` for a given execute request.
118
+ *
119
+ * postman-sandbox enforces its own per-script timeout inside the sandbox, so reaching this ceiling
120
+ * means the iframe itself wedged (renderer OOM, hard crash, blocking infinite loop in a worker,
121
+ * messages dropped). The default is generous on purpose — this is a circuit-breaker for crashes,
122
+ * not a budget for user scripts.
123
+ */
124
+ var SANDBOX_EXECUTION_TIMEOUT_MS = 300 * 1e3;
125
+ var iframeTransport = (request, onMessage) => {
126
+ ensureSandboxFrame().then((frame) => {
127
+ const expectedOrigin = sandboxOrigin();
128
+ const timeoutId = setTimeout(() => {
129
+ window.removeEventListener("message", handleMessage);
130
+ onMessage({
131
+ channel: SANDBOX_CHANNEL,
132
+ kind: "done",
133
+ id: request.id,
134
+ error: `Script execution timed out after ${SANDBOX_EXECUTION_TIMEOUT_MS}ms`
135
+ });
136
+ }, SANDBOX_EXECUTION_TIMEOUT_MS);
137
+ const handleMessage = (event) => {
138
+ if (event.origin !== expectedOrigin || event.source !== frame) return;
139
+ const data = event.data;
140
+ if (!data || data.channel !== "scalar-pre-post-request-scripts-sandbox" || !("id" in data) || data.id !== request.id) return;
141
+ if (data.kind === "done") {
142
+ clearTimeout(timeoutId);
143
+ window.removeEventListener("message", handleMessage);
144
+ }
145
+ onMessage(data);
146
+ };
147
+ window.addEventListener("message", handleMessage);
148
+ frame.postMessage(request, expectedOrigin);
149
+ }).catch((error) => {
150
+ onMessage({
151
+ channel: SANDBOX_CHANNEL,
152
+ kind: "done",
153
+ id: request.id,
154
+ error: error instanceof Error ? error.message : String(error)
155
+ });
156
+ });
50
157
  };
158
+ var transport = iframeTransport;
159
+ var nextExecutionId = 0;
51
160
  var executeInPostmanSandbox = async ({ script, type, context: { requestBuilder, response, variablesStore, scriptConsole }, onTestResultsUpdate }) => {
52
- const testResults = [];
53
- let lastAssertionTime = 0;
54
- let scriptExecutionStartedAt = 0;
55
- const sandboxContext = await createContext();
56
- const handleAssertion = (_cursor, assertions) => {
57
- assertions.forEach((assertion) => {
58
- const duration = Number((performance.now() - lastAssertionTime).toFixed(2));
59
- lastAssertionTime = performance.now();
60
- upsertTestResult(testResults, assertion, duration);
61
- });
62
- onTestResultsUpdate?.([...testResults]);
63
- };
64
- const variablesContext = variablesStore ? buildSandboxContextFromStore(variablesStore) : void 0;
65
- const handleConsole = (_cursor, level, ...args) => {
66
- (scriptConsole[level] ?? scriptConsole.log)(...args);
161
+ const requestDefinition = requestBuilder ? createPostmanRequestFromFactory(requestBuilder).toJSON() : void 0;
162
+ const responseDefinition = response ? await toPostmanResponse(response) : void 0;
163
+ const scopes = variablesStore ? getVariableScopesFromStore(variablesStore) : void 0;
164
+ const request = {
165
+ channel: SANDBOX_CHANNEL,
166
+ kind: "execute",
167
+ id: String(nextExecutionId++),
168
+ listen: type === "pre-request" ? "prerequest" : "test",
169
+ script,
170
+ scopes,
171
+ request: requestDefinition,
172
+ response: responseDefinition
67
173
  };
68
- try {
69
- sandboxContext.on("execution.assertion", handleAssertion);
70
- sandboxContext.on("console", handleConsole);
71
- const postmanRequest = requestBuilder ? createPostmanRequestFromFactory(requestBuilder) : void 0;
72
- const postmanResponse = response ? await toPostmanResponse(response) : void 0;
73
- scriptExecutionStartedAt = performance.now();
74
- lastAssertionTime = scriptExecutionStartedAt;
75
- /**
76
- * Lodash `_.has(context, 'response')` is true even when the value is `undefined`,
77
- * which makes the sandbox run `new Response(undefined)` and breaks `pm.request`.
78
- * Only set keys that are actually present.
79
- */
80
- const context = { ...variablesContext ?? {} };
81
- if (postmanRequest !== void 0) context.request = postmanRequest;
82
- if (postmanResponse !== void 0) context.response = postmanResponse;
83
- /** Pre-request scripts must use the prerequest listener so pm.globals / pm.environment / collection mutations appear on ExecutionResult. */
84
- const listen = type === "pre-request" ? "prerequest" : "test";
85
- await new Promise((resolve) => {
86
- sandboxContext.execute({
87
- listen,
88
- script: { exec: [script] }
89
- }, {
90
- disableLegacyAPIs: true,
91
- context
92
- }, (error, execution) => {
93
- if (variablesStore && execution) {
94
- if (execution._variables?.values) {
95
- const localVariables = toVariableEntries(execution._variables.values);
96
- variablesStore.setLocalVariables(localVariables);
97
- }
98
- if (execution.collectionVariables?.values) {
99
- const collectionVariables = toVariableEntries(execution.collectionVariables.values);
100
- variablesStore.setCollectionVariables?.(collectionVariables);
101
- }
102
- if (execution.globals?.values) {
103
- const globals = toVariableEntries(execution.globals.values);
104
- variablesStore.setGlobals?.(globals);
105
- }
106
- if (execution.environment?.values) {
107
- const environmentVariables = toVariableEntries(execution.environment.values);
108
- variablesStore.setEnvironment?.(environmentVariables);
109
- }
110
- }
111
- if (!error && type === "pre-request" && requestBuilder && execution?.request !== void 0) syncPlainPostmanRequestToRequestFactory(execution.request, requestBuilder);
112
- if (error) {
113
- const duration = Number((performance.now() - scriptExecutionStartedAt).toFixed(2));
114
- const errorMessage = toErrorMessage(error);
115
- scriptConsole.error(`[${type.toUpperCase()} Script] Error (${duration}ms):`, errorMessage);
116
- testResults.push({
117
- title: "Script Execution",
118
- passed: false,
119
- duration,
120
- error: errorMessage,
121
- status: "failed"
122
- });
123
- onTestResultsUpdate?.([...testResults]);
174
+ await new Promise((resolve) => {
175
+ const onMessage = (data) => {
176
+ if (data.kind === "test-results") {
177
+ onTestResultsUpdate?.(data.results);
178
+ return;
179
+ }
180
+ if (data.kind === "console") {
181
+ (scriptConsole[data.level] ?? scriptConsole.log)(...data.args);
182
+ return;
183
+ }
184
+ if (data.kind === "done") {
185
+ if (variablesStore && data.variables) {
186
+ const { local, collection, globals, environment } = data.variables;
187
+ if (local) variablesStore.setLocalVariables(toVariableEntries(local));
188
+ if (collection) variablesStore.setCollectionVariables?.(toVariableEntries(collection));
189
+ if (globals) variablesStore.setGlobals?.(toVariableEntries(globals));
190
+ if (environment) variablesStore.setEnvironment?.(toVariableEntries(environment));
124
191
  }
192
+ if (!data.error && type === "pre-request" && requestBuilder && data.request !== void 0) syncPlainPostmanRequestToRequestFactory(data.request, requestBuilder);
125
193
  resolve();
126
- });
127
- });
128
- } finally {
129
- sandboxContext.off("execution.assertion", handleAssertion);
130
- sandboxContext.off("console", handleConsole);
131
- sandboxContext.dispose();
132
- }
194
+ }
195
+ };
196
+ transport(request, onMessage);
197
+ });
133
198
  return variablesStore;
134
199
  };
135
200
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-adapter.js","names":[],"sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts"],"sourcesContent":["import type { RequestFactory, VariablesStore } from '@scalar/workspace-store/request-example'\nimport type { ExecutionResult, SandboxContext } from 'postman-sandbox'\nimport Sandbox from 'postman-sandbox'\n\nimport { buildSandboxContextFromStore } from '../build-sandbox-context'\nimport type { ConsoleContext } from '../context/console'\nimport type { TestResult } from '../execute-post-response-script'\nimport { toVariableEntries } from '../variables-store'\nimport { createPostmanRequestFromFactory, syncPlainPostmanRequestToRequestFactory } from './request-factory-adapter'\n\ntype AssertionEvent = {\n name: string\n index: number\n passed: boolean\n skipped: boolean\n error: { message?: string } | null\n}\n\nconst toPostmanResponse = async (response: Response) => {\n const responseText = await response.text()\n const responseBytes = Array.from(new TextEncoder().encode(responseText))\n\n return {\n code: response.status,\n status: response.statusText || String(response.status),\n header: Array.from(response.headers.entries()).map(([key, value]) => ({ key, value })),\n stream: {\n type: 'Buffer',\n data: responseBytes,\n },\n }\n}\n\nconst createContext = (): Promise<SandboxContext> =>\n new Promise((resolve, reject) => {\n Sandbox.createContext((error: unknown, context: SandboxContext) => {\n if (error) {\n reject(error)\n return\n }\n\n resolve(context)\n })\n })\n\nconst toErrorMessage = (error: unknown): string => {\n if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n return error.message\n }\n\n return String(error)\n}\n\nconst upsertTestResult = (testResults: TestResult[], assertion: AssertionEvent, duration: number): void => {\n const title = assertion.name || `Assertion ${assertion.index + 1}`\n\n const nextResult: TestResult = {\n title,\n passed: assertion.passed,\n duration,\n error: assertion.error?.message,\n status: assertion.passed ? 'passed' : 'failed',\n }\n\n const existingResultIndex = testResults.findIndex((result) => result.title === title)\n if (existingResultIndex === -1) {\n testResults.push(nextResult)\n return\n }\n\n testResults[existingResultIndex] = nextResult\n}\n\nexport const executeInPostmanSandbox = async ({\n script,\n type,\n context: { requestBuilder, response, variablesStore, scriptConsole },\n onTestResultsUpdate,\n}: {\n script: string\n type: 'pre-request' | 'post-response'\n context: {\n /** Postman Collection request for `pm.request` (not the browser Fetch API Request). */\n requestBuilder?: RequestFactory\n response?: Response\n variablesStore?: VariablesStore\n scriptConsole: ConsoleContext\n }\n onTestResultsUpdate?: ((results: TestResult[]) => void) | undefined\n}): Promise<VariablesStore | undefined> => {\n const testResults: TestResult[] = []\n let lastAssertionTime = 0\n let scriptExecutionStartedAt = 0\n const sandboxContext = await createContext()\n\n const handleAssertion = (_cursor: unknown, assertions: AssertionEvent[]) => {\n assertions.forEach((assertion) => {\n const duration = Number((performance.now() - lastAssertionTime).toFixed(2))\n lastAssertionTime = performance.now()\n upsertTestResult(testResults, assertion, duration)\n })\n onTestResultsUpdate?.([...testResults])\n }\n\n const variablesContext = variablesStore ? buildSandboxContextFromStore(variablesStore) : undefined\n\n const handleConsole = (_cursor: unknown, level: keyof ConsoleContext, ...args: unknown[]) => {\n const consoleMethod = scriptConsole[level] ?? scriptConsole.log\n ;(consoleMethod as (...params: unknown[]) => void)(...args)\n }\n\n try {\n sandboxContext.on('execution.assertion', handleAssertion)\n sandboxContext.on('console', handleConsole)\n\n const postmanRequest = requestBuilder ? createPostmanRequestFromFactory(requestBuilder) : undefined\n const postmanResponse = response ? await toPostmanResponse(response) : undefined\n\n scriptExecutionStartedAt = performance.now()\n lastAssertionTime = scriptExecutionStartedAt\n\n /**\n * Lodash `_.has(context, 'response')` is true even when the value is `undefined`,\n * which makes the sandbox run `new Response(undefined)` and breaks `pm.request`.\n * Only set keys that are actually present.\n */\n const context: Record<string, unknown> = {\n ...(variablesContext ?? {}),\n }\n\n // We need to pass the postman request to the sandbox so it can be mutated by the script.\n if (postmanRequest !== undefined) {\n context.request = postmanRequest\n }\n // We need to pass the postman response to the sandbox so it can be used by the script.\n if (postmanResponse !== undefined) {\n context.response = postmanResponse\n }\n\n /** Pre-request scripts must use the prerequest listener so pm.globals / pm.environment / collection mutations appear on ExecutionResult. */\n const listen = type === 'pre-request' ? 'prerequest' : 'test'\n\n await new Promise<void>((resolve) => {\n sandboxContext.execute(\n {\n listen,\n script: {\n exec: [script],\n },\n },\n {\n disableLegacyAPIs: true,\n context,\n },\n (error: unknown, execution?: ExecutionResult) => {\n if (variablesStore && execution) {\n if (execution._variables?.values) {\n const localVariables = toVariableEntries(execution._variables.values)\n variablesStore.setLocalVariables(localVariables)\n }\n if (execution.collectionVariables?.values) {\n const collectionVariables = toVariableEntries(execution.collectionVariables.values)\n variablesStore.setCollectionVariables?.(collectionVariables)\n }\n if (execution.globals?.values) {\n const globals = toVariableEntries(execution.globals.values)\n variablesStore.setGlobals?.(globals)\n }\n if (execution.environment?.values) {\n const environmentVariables = toVariableEntries(execution.environment.values)\n variablesStore.setEnvironment?.(environmentVariables)\n }\n }\n\n if (!error && type === 'pre-request' && requestBuilder && execution?.request !== undefined) {\n syncPlainPostmanRequestToRequestFactory(execution.request, requestBuilder)\n }\n if (error) {\n const duration = Number((performance.now() - scriptExecutionStartedAt).toFixed(2))\n const errorMessage = toErrorMessage(error)\n\n scriptConsole.error(`[${type.toUpperCase()} Script] Error (${duration}ms):`, errorMessage)\n\n testResults.push({\n title: 'Script Execution',\n passed: false,\n duration,\n error: errorMessage,\n status: 'failed',\n })\n onTestResultsUpdate?.([...testResults])\n }\n\n resolve()\n },\n )\n })\n } finally {\n sandboxContext.off('execution.assertion', handleAssertion)\n sandboxContext.off('console', handleConsole)\n sandboxContext.dispose()\n }\n\n return variablesStore\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sandbox-adapter.js","names":[],"sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts"],"sourcesContent":["import { isElectron } from '@scalar/helpers/general/is-electron'\nimport type { RequestFactory, VariablesStore } from '@scalar/workspace-store/request-example'\n\nimport type { ConsoleContext } from '../context/console'\nimport type { TestResult } from '../execute-post-response-script'\nimport { getVariableScopesFromStore, toVariableEntries } from '../variables-store'\nimport { createPostmanRequestFromFactory, syncPlainPostmanRequestToRequestFactory } from './request-factory-adapter'\nimport {\n type PostmanResponseDefinition,\n SANDBOX_CHANNEL,\n type SandboxExecuteRequest,\n type SandboxOutboundMessage,\n} from './sandbox-protocol'\n\n/**\n * Host-side adapter for pre/post-request scripts.\n *\n * The actual script execution happens in `postman-sandbox`, which requires `eval`. To keep\n * `'unsafe-eval'` out of the main application CSP, that execution is delegated to an iframe loaded\n * from a real same-origin `.html` file with its own permissive CSP (see `sandbox-frame-server.ts`).\n * This module therefore must NOT import `postman-sandbox`; it only serializes inputs, talks to the\n * iframe over `postMessage`, and applies the results back to the live store/request.\n */\nexport const toPostmanResponse = async (response: Response): Promise<PostmanResponseDefinition> => {\n // Read as ArrayBuffer (not text) so binary payloads — gzipped JSON, images, PDFs, protobuf —\n // survive the trip through `postMessage` byte-for-byte. Round-tripping through `response.text()`\n // + `TextEncoder` corrupts any sequence that is not valid UTF-8 because the decoder replaces\n // invalid units with U+FFFD before we ever encode them back to bytes.\n const buffer = await response.arrayBuffer()\n const responseBytes = Array.from(new Uint8Array(buffer))\n\n return {\n code: response.status,\n status: response.statusText || String(response.status),\n header: Array.from(response.headers.entries()).map(([key, value]) => ({ key, value })),\n stream: {\n type: 'Buffer',\n data: responseBytes,\n },\n }\n}\n\n/**\n * Resolves the sandbox iframe URL.\n *\n * On the web we anchor to the origin root because the SPA router owns the current path: a\n * relative `sandbox.html` would resolve under the active route and get rewritten to `index.html`\n * by the history fallback. In Electron the document path is literal, so a sibling resolution\n * works — and we use the `isElectron` helper rather than a `file://` check because Electron's\n * dev mode is served over `http://localhost`.\n *\n * Exported for tests; production code reaches it through `ensureSandboxFrame`.\n */\nexport const sandboxFrameUrl = (): string => {\n if (isElectron()) {\n return new URL('sandbox.html', document.baseURI).href\n }\n return new URL('/sandbox.html', window.location.origin).href\n}\n\n/**\n * Origin we pin `postMessage` exchanges to.\n *\n * The sandbox iframe is served from the same origin as the host (see `sandbox.html`), so we lock\n * every send and receive to `window.location.origin`. Pinning the send origin prevents the payload\n * from leaking if the frame is ever navigated away to another origin; pinning the receive origin\n * prevents another window (for example a cross-origin opener) from spoofing sandbox messages.\n *\n * For `file://` documents (Electron), `window.location.origin` is the string `'file://'`, which is\n * the same value `postMessage` reports on `event.origin`, so the pin still holds.\n */\nconst sandboxOrigin = (): string => window.location.origin\n\n/**\n * Delivers a single execute request to the sandbox and forwards every message it produces (test\n * results, console output, and the final `done`) to `onMessage`.\n *\n * The default transport is the iframe (see {@link iframeTransport}). It is injectable so that node\n * unit tests can run the real execution in-process (`runSandboxExecution`) without a DOM, and without\n * the production host bundle ever importing `postman-sandbox`.\n */\ntype SandboxTransport = (request: SandboxExecuteRequest, onMessage: (message: SandboxOutboundMessage) => void) => void\n\nlet framePromise: Promise<Window> | undefined\n\n/**\n * Maximum time we wait for a freshly created sandbox iframe to post its `ready` handshake.\n *\n * The iframe `error` event fires only when the `.html` document itself fails to load. If the\n * document loads (HTTP 200) but the script inside never reaches `startSandboxFrameServer` — a\n * 404 on the bundle, an import error, a CSP block, a crash during boot — no `ready` message is\n * posted and no `error` event fires. Without this ceiling the readiness promise hangs forever;\n * because it is module-cached, every later call awaits the same dead promise and all script\n * execution freezes for the session. The window is generous: readiness only requires loading one\n * same-origin HTML file plus the sandbox bundle, so timing out means the bundle is genuinely\n * broken rather than slow.\n */\nconst SANDBOX_READY_TIMEOUT_MS = 30 * 1000\n\n/** Lazily creates a single hidden sandbox iframe and resolves once it reports readiness. */\nconst ensureSandboxFrame = (): Promise<Window> => {\n if (framePromise) {\n return framePromise\n }\n\n framePromise = new Promise<Window>((resolve, reject) => {\n const iframe = document.createElement('iframe')\n iframe.setAttribute('aria-hidden', 'true')\n iframe.style.display = 'none'\n iframe.src = sandboxFrameUrl()\n\n const expectedOrigin = sandboxOrigin()\n\n // Cleanup shared by the success, load-error, and readiness-timeout paths: cancel the timer and\n // drop the window listener so exactly one of the three settles the promise without leaking.\n let readyTimeoutId: ReturnType<typeof setTimeout>\n const stopWaiting = () => {\n clearTimeout(readyTimeoutId)\n window.removeEventListener('message', onReady)\n }\n\n const onReady = (event: MessageEvent) => {\n // Same-origin iframe: reject anything that does not come from our own origin and our own frame.\n if (event.origin !== expectedOrigin || event.source !== iframe.contentWindow) {\n return\n }\n const data = event.data as SandboxOutboundMessage | null\n if (!data || data.channel !== SANDBOX_CHANNEL || data.kind !== 'ready') {\n return\n }\n stopWaiting()\n if (!iframe.contentWindow) {\n // Tear the orphaned node down so a retry can append a fresh frame without piling up.\n iframe.remove()\n framePromise = undefined\n reject(new Error('Sandbox iframe has no content window'))\n return\n }\n resolve(iframe.contentWindow)\n }\n\n iframe.addEventListener('error', () => {\n stopWaiting()\n // Drop the broken iframe from the DOM. Without this, a transient failure (network blip, bad\n // bundle path, etc.) leaves a dead `<iframe>` attached, and every retry appends another one,\n // growing the DOM unboundedly across the session.\n iframe.remove()\n framePromise = undefined\n reject(new Error('Failed to load the script sandbox iframe'))\n })\n\n // Catch the silent failure mode the `error` event cannot see: the document loaded but its\n // script never announced readiness. Drop the dead frame and clear the cache so the next call\n // retries with a fresh iframe instead of awaiting this one forever.\n readyTimeoutId = setTimeout(() => {\n window.removeEventListener('message', onReady)\n iframe.remove()\n framePromise = undefined\n reject(new Error(`Sandbox iframe did not report readiness within ${SANDBOX_READY_TIMEOUT_MS}ms`))\n }, SANDBOX_READY_TIMEOUT_MS)\n\n window.addEventListener('message', onReady)\n document.body.appendChild(iframe)\n })\n\n return framePromise\n}\n\n/**\n * Maximum time we wait for the iframe to emit `kind: 'done'` for a given execute request.\n *\n * postman-sandbox enforces its own per-script timeout inside the sandbox, so reaching this ceiling\n * means the iframe itself wedged (renderer OOM, hard crash, blocking infinite loop in a worker,\n * messages dropped). The default is generous on purpose — this is a circuit-breaker for crashes,\n * not a budget for user scripts.\n */\nconst SANDBOX_EXECUTION_TIMEOUT_MS = 5 * 60 * 1000\n\nconst iframeTransport: SandboxTransport = (request, onMessage) => {\n ensureSandboxFrame()\n .then((frame) => {\n const expectedOrigin = sandboxOrigin()\n\n // Defensive circuit-breaker: if the iframe dies mid-execution we would otherwise wait on a\n // `done` message that will never arrive, leaking the listener and leaving the caller's\n // promise unresolved. The timer is cancelled the moment `done` is observed below.\n const timeoutId = setTimeout(() => {\n window.removeEventListener('message', handleMessage)\n onMessage({\n channel: SANDBOX_CHANNEL,\n kind: 'done',\n id: request.id,\n error: `Script execution timed out after ${SANDBOX_EXECUTION_TIMEOUT_MS}ms`,\n })\n }, SANDBOX_EXECUTION_TIMEOUT_MS)\n\n const handleMessage = (event: MessageEvent) => {\n // Reject anything that is not from our own origin and our own frame. Without this guard,\n // any window in the same browsing context group could send fabricated `done`/`test-results`\n // messages and trick the host into applying attacker-controlled variables and requests.\n if (event.origin !== expectedOrigin || event.source !== frame) {\n return\n }\n const data = event.data as SandboxOutboundMessage | null\n if (!data || data.channel !== SANDBOX_CHANNEL || !('id' in data) || data.id !== request.id) {\n return\n }\n if (data.kind === 'done') {\n clearTimeout(timeoutId)\n window.removeEventListener('message', handleMessage)\n }\n onMessage(data)\n }\n\n window.addEventListener('message', handleMessage)\n // Pin the target origin: the execute payload contains environments, headers, secrets, and\n // potentially auth tokens, so we must never broadcast it with `'*'`.\n frame.postMessage(request, expectedOrigin)\n })\n .catch((error: unknown) => {\n onMessage({\n channel: SANDBOX_CHANNEL,\n kind: 'done',\n id: request.id,\n error: error instanceof Error ? error.message : String(error),\n })\n })\n}\nlet transport: SandboxTransport = iframeTransport\n\n/** Override how the host reaches the sandbox (e.g. an in-process transport for tests). */\nexport const setSandboxTransport = (next: SandboxTransport): void => {\n transport = next\n}\n\nlet nextExecutionId = 0\n\nexport const executeInPostmanSandbox = async ({\n script,\n type,\n context: { requestBuilder, response, variablesStore, scriptConsole },\n onTestResultsUpdate,\n}: {\n script: string\n type: 'pre-request' | 'post-response'\n context: {\n /** Postman Collection request for `pm.request` (not the browser Fetch API Request). */\n requestBuilder?: RequestFactory\n response?: Response\n variablesStore?: VariablesStore\n scriptConsole: ConsoleContext\n }\n onTestResultsUpdate?: ((results: TestResult[]) => void) | undefined\n}): Promise<VariablesStore | undefined> => {\n // Serialize everything that crosses the iframe boundary on the host, so the iframe only deals with\n // plain, structured-cloneable data. The response body is read here (it requires the live Response).\n const requestDefinition = requestBuilder ? createPostmanRequestFromFactory(requestBuilder).toJSON() : undefined\n const responseDefinition = response ? await toPostmanResponse(response) : undefined\n const scopes = variablesStore ? getVariableScopesFromStore(variablesStore) : undefined\n\n const request: SandboxExecuteRequest = {\n channel: SANDBOX_CHANNEL,\n kind: 'execute',\n id: String(nextExecutionId++),\n listen: type === 'pre-request' ? 'prerequest' : 'test',\n script,\n scopes,\n request: requestDefinition,\n response: responseDefinition,\n }\n\n await new Promise<void>((resolve) => {\n const onMessage = (data: SandboxOutboundMessage) => {\n if (data.kind === 'test-results') {\n onTestResultsUpdate?.(data.results)\n return\n }\n\n if (data.kind === 'console') {\n const consoleMethod = scriptConsole[data.level] ?? scriptConsole.log\n ;(consoleMethod as (...params: unknown[]) => void)(...data.args)\n return\n }\n\n if (data.kind === 'done') {\n if (variablesStore && data.variables) {\n const { local, collection, globals, environment } = data.variables\n if (local) {\n variablesStore.setLocalVariables(toVariableEntries(local))\n }\n if (collection) {\n variablesStore.setCollectionVariables?.(toVariableEntries(collection))\n }\n if (globals) {\n variablesStore.setGlobals?.(toVariableEntries(globals))\n }\n if (environment) {\n variablesStore.setEnvironment?.(toVariableEntries(environment))\n }\n }\n\n if (!data.error && type === 'pre-request' && requestBuilder && data.request !== undefined) {\n syncPlainPostmanRequestToRequestFactory(data.request, requestBuilder)\n }\n\n resolve()\n }\n }\n\n transport(request, onMessage)\n })\n\n return variablesStore\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sandbox-frame-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-frame-entry.d.ts","sourceRoot":"","sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-frame-entry.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import { startSandboxFrameServer } from "./sandbox-frame-server.js";
2
+ //#region src/libs/execute-scripts/postman-adapter/sandbox-frame-entry.ts
3
+ /**
4
+ * Entry point loaded by the sandbox iframe host page (`sandbox.html`).
5
+ *
6
+ * Importing this module pulls in `postman-sandbox`, so it must only ever be loaded inside the sandbox
7
+ * iframe (which has its own permissive CSP) — never from the main application bundle.
8
+ */
9
+ startSandboxFrameServer();
10
+ //#endregion
11
+
12
+ //# sourceMappingURL=sandbox-frame-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-frame-entry.js","names":[],"sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-frame-entry.ts"],"sourcesContent":["import { startSandboxFrameServer } from './sandbox-frame-server'\n\n/**\n * Entry point loaded by the sandbox iframe host page (`sandbox.html`).\n *\n * Importing this module pulls in `postman-sandbox`, so it must only ever be loaded inside the sandbox\n * iframe (which has its own permissive CSP) — never from the main application bundle.\n */\nstartSandboxFrameServer()\n"],"mappings":";;;;;;;;AAQA,yBAAyB"}
@@ -0,0 +1,19 @@
1
+ import { type SandboxExecuteRequest, type SandboxOutboundMessage } from './sandbox-protocol.js';
2
+ /**
3
+ * Runs a single script in the postman-sandbox and reports progress/results via {@link emit}.
4
+ *
5
+ * Inputs arrive as plain, structured-cloneable data (already converted on the host) and are rebuilt
6
+ * into postman-collection objects here. Variable mutations and the mutated request are sent back as
7
+ * plain values; the host applies them to the live `VariablesStore`/`RequestFactory`.
8
+ */
9
+ export declare const runSandboxExecution: (request: SandboxExecuteRequest, emit: (message: SandboxOutboundMessage) => void) => Promise<void>;
10
+ /**
11
+ * Boots the sandbox bridge: listens for {@link SandboxExecuteRequest} messages from the host window
12
+ * and announces readiness. Called by the sandbox `.html` entry point.
13
+ *
14
+ * Returns a teardown function that removes the `message` listener it registered. Production code
15
+ * does not need to call it (the iframe lives for the lifetime of the document), but tests and any
16
+ * future hot-reload path can use it to avoid leaking listeners across reboots.
17
+ */
18
+ export declare const startSandboxFrameServer: () => (() => void);
19
+ //# sourceMappingURL=sandbox-frame-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-frame-server.d.ts","sourceRoot":"","sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-frame-server.ts"],"names":[],"mappings":"AAOA,OAAO,EAAmB,KAAK,qBAAqB,EAAE,KAAK,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AA2D7G;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,GAC9B,SAAS,qBAAqB,EAC9B,MAAM,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,KAC9C,OAAO,CAAC,IAAI,CAmHd,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,QAAO,CAAC,MAAM,IAAI,CAgDrD,CAAA"}
@@ -0,0 +1,187 @@
1
+ import { SANDBOX_CHANNEL } from "./sandbox-protocol.js";
2
+ import { buildSandboxContextFromScopes } from "../build-sandbox-context.js";
3
+ import { Request } from "postman-collection";
4
+ import Sandbox from "postman-sandbox";
5
+ //#region src/libs/execute-scripts/postman-adapter/sandbox-frame-server.ts
6
+ var toErrorMessage = (error) => {
7
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string") return error.message;
8
+ return String(error);
9
+ };
10
+ var upsertTestResult = (testResults, assertion, duration) => {
11
+ const title = assertion.name || `Assertion ${assertion.index + 1}`;
12
+ const nextResult = {
13
+ title,
14
+ passed: assertion.passed,
15
+ duration,
16
+ error: assertion.error?.message,
17
+ status: assertion.passed ? "passed" : "failed"
18
+ };
19
+ const existingResultIndex = testResults.findIndex((result) => result.title === title);
20
+ if (existingResultIndex === -1) {
21
+ testResults.push(nextResult);
22
+ return;
23
+ }
24
+ testResults[existingResultIndex] = nextResult;
25
+ };
26
+ var createContext = () => new Promise((resolve, reject) => {
27
+ Sandbox.createContext((error, context) => {
28
+ if (error) {
29
+ reject(error);
30
+ return;
31
+ }
32
+ resolve(context);
33
+ });
34
+ });
35
+ /**
36
+ * Runs a single script in the postman-sandbox and reports progress/results via {@link emit}.
37
+ *
38
+ * Inputs arrive as plain, structured-cloneable data (already converted on the host) and are rebuilt
39
+ * into postman-collection objects here. Variable mutations and the mutated request are sent back as
40
+ * plain values; the host applies them to the live `VariablesStore`/`RequestFactory`.
41
+ */
42
+ var runSandboxExecution = async (request, emit) => {
43
+ const { id, listen, script, scopes, request: requestDefinition, response } = request;
44
+ const testResults = [];
45
+ let lastAssertionTime = 0;
46
+ let scriptExecutionStartedAt = 0;
47
+ const sandboxContext = await createContext();
48
+ const handleAssertion = (_cursor, assertions) => {
49
+ assertions.forEach((assertion) => {
50
+ const duration = Number((performance.now() - lastAssertionTime).toFixed(2));
51
+ lastAssertionTime = performance.now();
52
+ upsertTestResult(testResults, assertion, duration);
53
+ });
54
+ emit({
55
+ channel: SANDBOX_CHANNEL,
56
+ kind: "test-results",
57
+ id,
58
+ results: [...testResults]
59
+ });
60
+ };
61
+ const handleConsole = (_cursor, level, ...args) => {
62
+ emit({
63
+ channel: SANDBOX_CHANNEL,
64
+ kind: "console",
65
+ id,
66
+ level,
67
+ args
68
+ });
69
+ };
70
+ try {
71
+ sandboxContext.on("execution.assertion", handleAssertion);
72
+ sandboxContext.on("console", handleConsole);
73
+ const variablesContext = scopes ? buildSandboxContextFromScopes(scopes) : void 0;
74
+ scriptExecutionStartedAt = performance.now();
75
+ lastAssertionTime = scriptExecutionStartedAt;
76
+ /**
77
+ * Lodash `_.has(context, 'response')` is true even when the value is `undefined`,
78
+ * which makes the sandbox run `new Response(undefined)` and breaks `pm.request`.
79
+ * Only set keys that are actually present.
80
+ */
81
+ const context = { ...variablesContext ?? {} };
82
+ if (requestDefinition !== void 0) context.request = new Request(requestDefinition);
83
+ if (response !== void 0) context.response = response;
84
+ await new Promise((resolve) => {
85
+ sandboxContext.execute({
86
+ listen,
87
+ script: { exec: [script] }
88
+ }, {
89
+ disableLegacyAPIs: true,
90
+ context
91
+ }, (error, execution) => {
92
+ const done = {
93
+ channel: SANDBOX_CHANNEL,
94
+ kind: "done",
95
+ id
96
+ };
97
+ if (execution) done.variables = {
98
+ local: execution._variables?.values,
99
+ collection: execution.collectionVariables?.values,
100
+ globals: execution.globals?.values,
101
+ environment: execution.environment?.values
102
+ };
103
+ if (!error && listen === "prerequest" && execution?.request !== void 0) done.request = execution.request;
104
+ if (error) {
105
+ const duration = Number((performance.now() - scriptExecutionStartedAt).toFixed(2));
106
+ const errorMessage = toErrorMessage(error);
107
+ done.error = errorMessage;
108
+ emit({
109
+ channel: SANDBOX_CHANNEL,
110
+ kind: "console",
111
+ id,
112
+ level: "error",
113
+ args: [`[${listen === "prerequest" ? "PRE-REQUEST" : "POST-RESPONSE"} Script] Error (${duration}ms):`, errorMessage]
114
+ });
115
+ testResults.push({
116
+ title: "Script Execution",
117
+ passed: false,
118
+ duration,
119
+ error: errorMessage,
120
+ status: "failed"
121
+ });
122
+ emit({
123
+ channel: SANDBOX_CHANNEL,
124
+ kind: "test-results",
125
+ id,
126
+ results: [...testResults]
127
+ });
128
+ }
129
+ emit(done);
130
+ resolve();
131
+ });
132
+ });
133
+ } finally {
134
+ sandboxContext.off("execution.assertion", handleAssertion);
135
+ sandboxContext.off("console", handleConsole);
136
+ sandboxContext.dispose();
137
+ }
138
+ };
139
+ /**
140
+ * Boots the sandbox bridge: listens for {@link SandboxExecuteRequest} messages from the host window
141
+ * and announces readiness. Called by the sandbox `.html` entry point.
142
+ *
143
+ * Returns a teardown function that removes the `message` listener it registered. Production code
144
+ * does not need to call it (the iframe lives for the lifetime of the document), but tests and any
145
+ * future hot-reload path can use it to avoid leaking listeners across reboots.
146
+ */
147
+ var startSandboxFrameServer = () => {
148
+ /**
149
+ * Origin we pin `postMessage` exchanges to.
150
+ *
151
+ * The iframe is loaded same-origin with its host (see `sandbox.html`), so both the parent we
152
+ * post to and the parent we accept from are pinned to `window.location.origin`. This prevents
153
+ * a hostile cross-origin opener from feeding fake execute requests into the sandbox, and stops
154
+ * sandbox output (which can include user secrets returned via `pm.environment`) from leaking
155
+ * to a different origin if the parent is ever swapped out.
156
+ *
157
+ * For `file://` documents (Electron) this resolves to `'file://'`, which is the same value
158
+ * `postMessage` exposes on `event.origin`, so the pin still holds.
159
+ */
160
+ const expectedOrigin = window.location.origin;
161
+ const post = (message) => {
162
+ window.parent?.postMessage(message, expectedOrigin);
163
+ };
164
+ const handleMessage = (event) => {
165
+ if (event.origin !== expectedOrigin || event.source !== window.parent) return;
166
+ const data = event.data;
167
+ if (!data || data.channel !== "scalar-pre-post-request-scripts-sandbox" || data.kind !== "execute") return;
168
+ runSandboxExecution(data, post).catch((error) => {
169
+ post({
170
+ channel: SANDBOX_CHANNEL,
171
+ kind: "done",
172
+ id: data.id,
173
+ error: toErrorMessage(error)
174
+ });
175
+ });
176
+ };
177
+ window.addEventListener("message", handleMessage);
178
+ post({
179
+ channel: SANDBOX_CHANNEL,
180
+ kind: "ready"
181
+ });
182
+ return () => window.removeEventListener("message", handleMessage);
183
+ };
184
+ //#endregion
185
+ export { startSandboxFrameServer };
186
+
187
+ //# sourceMappingURL=sandbox-frame-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-frame-server.js","names":[],"sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-frame-server.ts"],"sourcesContent":["import { Request as PostmanRequest } from 'postman-collection'\nimport type { ExecutionResult, SandboxContext } from 'postman-sandbox'\nimport Sandbox from 'postman-sandbox'\n\nimport { buildSandboxContextFromScopes } from '../build-sandbox-context'\nimport type { ConsoleContext } from '../context/console'\nimport type { TestResult } from '../execute-post-response-script'\nimport { SANDBOX_CHANNEL, type SandboxExecuteRequest, type SandboxOutboundMessage } from './sandbox-protocol'\n\n/**\n * Iframe-side sandbox bridge.\n *\n * This is the ONLY module that imports `postman-sandbox` (and therefore the only place that performs\n * `eval`/`new Function`). It is loaded exclusively by the sandbox `.html` host page, which carries its\n * own permissive CSP. The main application bundle never imports this file, so it stays eval-free and\n * its CSP can drop `'unsafe-eval'`.\n */\n\ntype AssertionEvent = {\n name: string\n index: number\n passed: boolean\n skipped: boolean\n error: { message?: string } | null\n}\n\nconst toErrorMessage = (error: unknown): string => {\n if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') {\n return error.message\n }\n\n return String(error)\n}\n\nconst upsertTestResult = (testResults: TestResult[], assertion: AssertionEvent, duration: number): void => {\n const title = assertion.name || `Assertion ${assertion.index + 1}`\n\n const nextResult: TestResult = {\n title,\n passed: assertion.passed,\n duration,\n error: assertion.error?.message,\n status: assertion.passed ? 'passed' : 'failed',\n }\n\n const existingResultIndex = testResults.findIndex((result) => result.title === title)\n if (existingResultIndex === -1) {\n testResults.push(nextResult)\n return\n }\n\n testResults[existingResultIndex] = nextResult\n}\n\nconst createContext = (): Promise<SandboxContext> =>\n new Promise((resolve, reject) => {\n Sandbox.createContext((error: unknown, context: SandboxContext) => {\n if (error) {\n reject(error)\n return\n }\n\n resolve(context)\n })\n })\n\n/**\n * Runs a single script in the postman-sandbox and reports progress/results via {@link emit}.\n *\n * Inputs arrive as plain, structured-cloneable data (already converted on the host) and are rebuilt\n * into postman-collection objects here. Variable mutations and the mutated request are sent back as\n * plain values; the host applies them to the live `VariablesStore`/`RequestFactory`.\n */\nexport const runSandboxExecution = async (\n request: SandboxExecuteRequest,\n emit: (message: SandboxOutboundMessage) => void,\n): Promise<void> => {\n const { id, listen, script, scopes, request: requestDefinition, response } = request\n\n const testResults: TestResult[] = []\n let lastAssertionTime = 0\n let scriptExecutionStartedAt = 0\n\n const sandboxContext = await createContext()\n\n const handleAssertion = (_cursor: unknown, assertions: AssertionEvent[]) => {\n assertions.forEach((assertion) => {\n const duration = Number((performance.now() - lastAssertionTime).toFixed(2))\n lastAssertionTime = performance.now()\n upsertTestResult(testResults, assertion, duration)\n })\n emit({ channel: SANDBOX_CHANNEL, kind: 'test-results', id, results: [...testResults] })\n }\n\n const handleConsole = (_cursor: unknown, level: string, ...args: unknown[]) => {\n emit({ channel: SANDBOX_CHANNEL, kind: 'console', id, level: level as keyof ConsoleContext, args })\n }\n\n try {\n sandboxContext.on('execution.assertion', handleAssertion)\n sandboxContext.on('console', handleConsole)\n\n const variablesContext = scopes ? buildSandboxContextFromScopes(scopes) : undefined\n\n scriptExecutionStartedAt = performance.now()\n lastAssertionTime = scriptExecutionStartedAt\n\n /**\n * Lodash `_.has(context, 'response')` is true even when the value is `undefined`,\n * which makes the sandbox run `new Response(undefined)` and breaks `pm.request`.\n * Only set keys that are actually present.\n */\n const context: Record<string, unknown> = {\n ...(variablesContext ?? {}),\n }\n\n if (requestDefinition !== undefined) {\n context.request = new PostmanRequest(requestDefinition as ConstructorParameters<typeof PostmanRequest>[0])\n }\n if (response !== undefined) {\n context.response = response\n }\n\n await new Promise<void>((resolve) => {\n sandboxContext.execute(\n {\n listen,\n script: {\n exec: [script],\n },\n },\n {\n disableLegacyAPIs: true,\n context,\n },\n (error: unknown, execution?: ExecutionResult) => {\n const done: SandboxOutboundMessage = {\n channel: SANDBOX_CHANNEL,\n kind: 'done',\n id,\n }\n\n if (execution) {\n done.variables = {\n local: execution._variables?.values,\n collection: execution.collectionVariables?.values,\n globals: execution.globals?.values,\n environment: execution.environment?.values,\n }\n }\n\n if (!error && listen === 'prerequest' && execution?.request !== undefined) {\n done.request = execution.request\n }\n\n if (error) {\n const duration = Number((performance.now() - scriptExecutionStartedAt).toFixed(2))\n const errorMessage = toErrorMessage(error)\n done.error = errorMessage\n\n emit({\n channel: SANDBOX_CHANNEL,\n kind: 'console',\n id,\n level: 'error',\n args: [\n `[${listen === 'prerequest' ? 'PRE-REQUEST' : 'POST-RESPONSE'} Script] Error (${duration}ms):`,\n errorMessage,\n ],\n })\n\n testResults.push({\n title: 'Script Execution',\n passed: false,\n duration,\n error: errorMessage,\n status: 'failed',\n })\n emit({ channel: SANDBOX_CHANNEL, kind: 'test-results', id, results: [...testResults] })\n }\n\n emit(done)\n resolve()\n },\n )\n })\n } finally {\n sandboxContext.off('execution.assertion', handleAssertion)\n sandboxContext.off('console', handleConsole)\n sandboxContext.dispose()\n }\n}\n\n/**\n * Boots the sandbox bridge: listens for {@link SandboxExecuteRequest} messages from the host window\n * and announces readiness. Called by the sandbox `.html` entry point.\n *\n * Returns a teardown function that removes the `message` listener it registered. Production code\n * does not need to call it (the iframe lives for the lifetime of the document), but tests and any\n * future hot-reload path can use it to avoid leaking listeners across reboots.\n */\nexport const startSandboxFrameServer = (): (() => void) => {\n /**\n * Origin we pin `postMessage` exchanges to.\n *\n * The iframe is loaded same-origin with its host (see `sandbox.html`), so both the parent we\n * post to and the parent we accept from are pinned to `window.location.origin`. This prevents\n * a hostile cross-origin opener from feeding fake execute requests into the sandbox, and stops\n * sandbox output (which can include user secrets returned via `pm.environment`) from leaking\n * to a different origin if the parent is ever swapped out.\n *\n * For `file://` documents (Electron) this resolves to `'file://'`, which is the same value\n * `postMessage` exposes on `event.origin`, so the pin still holds.\n */\n const expectedOrigin = window.location.origin\n\n const post = (message: SandboxOutboundMessage) => {\n // Always target the host's origin explicitly instead of `'*'`. The host also validates\n // `event.source` and `event.origin`, but defense-in-depth is cheap here.\n window.parent?.postMessage(message, expectedOrigin)\n }\n\n const handleMessage = (event: MessageEvent) => {\n // Reject anything that is not from our parent on our own origin. Without this guard, any\n // window that obtains a reference to this iframe could send fabricated execute payloads and\n // run arbitrary scripts inside the eval-permitted realm.\n if (event.origin !== expectedOrigin || event.source !== window.parent) {\n return\n }\n\n const data = event.data as Partial<SandboxExecuteRequest> | null\n if (!data || data.channel !== SANDBOX_CHANNEL || data.kind !== 'execute') {\n return\n }\n\n void runSandboxExecution(data as SandboxExecuteRequest, post).catch((error: unknown) => {\n post({\n channel: SANDBOX_CHANNEL,\n kind: 'done',\n id: (data as SandboxExecuteRequest).id,\n error: toErrorMessage(error),\n })\n })\n }\n\n window.addEventListener('message', handleMessage)\n post({ channel: SANDBOX_CHANNEL, kind: 'ready' })\n\n return () => window.removeEventListener('message', handleMessage)\n}\n"],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import type { VariableEntry } from '@scalar/workspace-store/request-example';
2
+ import type { ConsoleContext } from '../context/console.js';
3
+ import type { TestResult } from '../execute-post-response-script.js';
4
+ /**
5
+ * Message channel shared between the host (main app realm) and the sandbox iframe.
6
+ *
7
+ * Pre/post-request scripts are executed by `postman-sandbox`, which relies on `eval`/`new Function`.
8
+ * To keep `'unsafe-eval'` out of the main application CSP, that execution is relocated into an
9
+ * iframe loaded from a real, same-origin `.html` file that carries its own (permissive) CSP. The
10
+ * host and the iframe talk over `postMessage` using the messages defined here. Everything that
11
+ * crosses the boundary must be structured-cloneable, so postman-collection objects are serialized
12
+ * to their plain JSON form (`VariableScope`/`Request` definitions) and rebuilt inside the iframe.
13
+ */
14
+ export declare const SANDBOX_CHANNEL = "scalar-pre-post-request-scripts-sandbox";
15
+ /** Plain, serializable representation of a postman-collection `Request` (`Request.prototype.toJSON()`). */
16
+ export type PostmanRequestDefinition = unknown;
17
+ /** Plain response shape consumed by `pm.response` inside the sandbox. */
18
+ export type PostmanResponseDefinition = {
19
+ code: number;
20
+ status: string;
21
+ header: {
22
+ key: string;
23
+ value: string;
24
+ }[];
25
+ stream: {
26
+ type: 'Buffer';
27
+ data: number[];
28
+ };
29
+ };
30
+ /** Variable scopes serialized as key/value arrays so the iframe can rebuild postman `VariableScope`s. */
31
+ export type SerializableScopes = {
32
+ environment: VariableEntry[];
33
+ globals: VariableEntry[];
34
+ collectionVariables: VariableEntry[];
35
+ data: Record<string, string>;
36
+ local: VariableEntry[];
37
+ };
38
+ /** Plain variable values as returned on a postman `ExecutionResult` scope (`{ values }`). */
39
+ export type SerializableVariableValues = Array<{
40
+ key: string;
41
+ value: string;
42
+ type?: string;
43
+ }>;
44
+ /** Host → iframe: run a single pre-request or post-response script. */
45
+ export type SandboxExecuteRequest = {
46
+ channel: typeof SANDBOX_CHANNEL;
47
+ kind: 'execute';
48
+ id: string;
49
+ /** Maps to the postman-sandbox listener: `'prerequest'` for pre-request, `'test'` for post-response. */
50
+ listen: 'prerequest' | 'test';
51
+ script: string;
52
+ scopes?: SerializableScopes;
53
+ request?: PostmanRequestDefinition;
54
+ response?: PostmanResponseDefinition;
55
+ };
56
+ /** iframe → host: the sandbox bridge has loaded and is ready to receive `execute` requests. */
57
+ export type SandboxReadyMessage = {
58
+ channel: typeof SANDBOX_CHANNEL;
59
+ kind: 'ready';
60
+ };
61
+ /** iframe → host: incremental test results (assertions + script execution errors). */
62
+ export type SandboxTestResultsMessage = {
63
+ channel: typeof SANDBOX_CHANNEL;
64
+ kind: 'test-results';
65
+ id: string;
66
+ results: TestResult[];
67
+ };
68
+ /** iframe → host: a console call made by the script. */
69
+ export type SandboxConsoleMessage = {
70
+ channel: typeof SANDBOX_CHANNEL;
71
+ kind: 'console';
72
+ id: string;
73
+ level: keyof ConsoleContext;
74
+ args: unknown[];
75
+ };
76
+ /** iframe → host: the script finished (successfully or with an error). */
77
+ export type SandboxDoneMessage = {
78
+ channel: typeof SANDBOX_CHANNEL;
79
+ kind: 'done';
80
+ id: string;
81
+ variables?: {
82
+ local?: SerializableVariableValues;
83
+ collection?: SerializableVariableValues;
84
+ globals?: SerializableVariableValues;
85
+ environment?: SerializableVariableValues;
86
+ };
87
+ /** Mutated postman request (`execution.request`) for pre-request scripts. */
88
+ request?: PostmanRequestDefinition;
89
+ error?: string;
90
+ };
91
+ export type SandboxOutboundMessage = SandboxReadyMessage | SandboxTestResultsMessage | SandboxConsoleMessage | SandboxDoneMessage;
92
+ //# sourceMappingURL=sandbox-protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-protocol.d.ts","sourceRoot":"","sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ //#region src/libs/execute-scripts/postman-adapter/sandbox-protocol.ts
2
+ /**
3
+ * Message channel shared between the host (main app realm) and the sandbox iframe.
4
+ *
5
+ * Pre/post-request scripts are executed by `postman-sandbox`, which relies on `eval`/`new Function`.
6
+ * To keep `'unsafe-eval'` out of the main application CSP, that execution is relocated into an
7
+ * iframe loaded from a real, same-origin `.html` file that carries its own (permissive) CSP. The
8
+ * host and the iframe talk over `postMessage` using the messages defined here. Everything that
9
+ * crosses the boundary must be structured-cloneable, so postman-collection objects are serialized
10
+ * to their plain JSON form (`VariableScope`/`Request` definitions) and rebuilt inside the iframe.
11
+ */
12
+ var SANDBOX_CHANNEL = "scalar-pre-post-request-scripts-sandbox";
13
+ //#endregion
14
+ export { SANDBOX_CHANNEL };
15
+
16
+ //# sourceMappingURL=sandbox-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-protocol.js","names":[],"sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-protocol.ts"],"sourcesContent":["import type { VariableEntry } from '@scalar/workspace-store/request-example'\n\nimport type { ConsoleContext } from '../context/console'\nimport type { TestResult } from '../execute-post-response-script'\n\n/**\n * Message channel shared between the host (main app realm) and the sandbox iframe.\n *\n * Pre/post-request scripts are executed by `postman-sandbox`, which relies on `eval`/`new Function`.\n * To keep `'unsafe-eval'` out of the main application CSP, that execution is relocated into an\n * iframe loaded from a real, same-origin `.html` file that carries its own (permissive) CSP. The\n * host and the iframe talk over `postMessage` using the messages defined here. Everything that\n * crosses the boundary must be structured-cloneable, so postman-collection objects are serialized\n * to their plain JSON form (`VariableScope`/`Request` definitions) and rebuilt inside the iframe.\n */\nexport const SANDBOX_CHANNEL = 'scalar-pre-post-request-scripts-sandbox'\n\n/** Plain, serializable representation of a postman-collection `Request` (`Request.prototype.toJSON()`). */\nexport type PostmanRequestDefinition = unknown\n\n/** Plain response shape consumed by `pm.response` inside the sandbox. */\nexport type PostmanResponseDefinition = {\n code: number\n status: string\n header: { key: string; value: string }[]\n stream: { type: 'Buffer'; data: number[] }\n}\n\n/** Variable scopes serialized as key/value arrays so the iframe can rebuild postman `VariableScope`s. */\nexport type SerializableScopes = {\n environment: VariableEntry[]\n globals: VariableEntry[]\n collectionVariables: VariableEntry[]\n data: Record<string, string>\n local: VariableEntry[]\n}\n\n/** Plain variable values as returned on a postman `ExecutionResult` scope (`{ values }`). */\nexport type SerializableVariableValues = Array<{ key: string; value: string; type?: string }>\n\n/** Host → iframe: run a single pre-request or post-response script. */\nexport type SandboxExecuteRequest = {\n channel: typeof SANDBOX_CHANNEL\n kind: 'execute'\n id: string\n /** Maps to the postman-sandbox listener: `'prerequest'` for pre-request, `'test'` for post-response. */\n listen: 'prerequest' | 'test'\n script: string\n scopes?: SerializableScopes\n request?: PostmanRequestDefinition\n response?: PostmanResponseDefinition\n}\n\n/** iframe → host: the sandbox bridge has loaded and is ready to receive `execute` requests. */\nexport type SandboxReadyMessage = {\n channel: typeof SANDBOX_CHANNEL\n kind: 'ready'\n}\n\n/** iframe → host: incremental test results (assertions + script execution errors). */\nexport type SandboxTestResultsMessage = {\n channel: typeof SANDBOX_CHANNEL\n kind: 'test-results'\n id: string\n results: TestResult[]\n}\n\n/** iframe → host: a console call made by the script. */\nexport type SandboxConsoleMessage = {\n channel: typeof SANDBOX_CHANNEL\n kind: 'console'\n id: string\n level: keyof ConsoleContext\n args: unknown[]\n}\n\n/** iframe → host: the script finished (successfully or with an error). */\nexport type SandboxDoneMessage = {\n channel: typeof SANDBOX_CHANNEL\n kind: 'done'\n id: string\n variables?: {\n local?: SerializableVariableValues\n collection?: SerializableVariableValues\n globals?: SerializableVariableValues\n environment?: SerializableVariableValues\n }\n /** Mutated postman request (`execution.request`) for pre-request scripts. */\n request?: PostmanRequestDefinition\n error?: string\n}\n\nexport type SandboxOutboundMessage =\n | SandboxReadyMessage\n | SandboxTestResultsMessage\n | SandboxConsoleMessage\n | SandboxDoneMessage\n"],"mappings":";;;;;;;;;;;AAeA,IAAa,kBAAkB"}
@@ -1 +1 @@
1
- {"version":3,"file":"TestResultIndicator.vue.d.ts","sourceRoot":"","sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultIndicator.vue"],"names":[],"mappings":"AA8FA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD,KAAK,WAAW,GAAG;IACf,KAAK,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC5B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAC;AAqHJ,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"TestResultIndicator.vue.d.ts","sourceRoot":"","sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultIndicator.vue"],"names":[],"mappings":"AAgGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAExD,KAAK,WAAW,GAAG;IACf,KAAK,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC5B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAC;AAqHJ,QAAA,MAAM,YAAY,kSAEhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -1 +1 @@
1
- {"version":3,"file":"TestResultIndicator.vue.js","names":[],"sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultIndicator.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { cva, ScalarIcon, useBindCx, type Icon } from '@scalar/components'\nimport { computed } from 'vue'\n\nimport type { TestResult } from '@/libs/execute-scripts'\n\nconst { state, totalTestsCount, pendingTestsCount, failedTestsCount } =\n defineProps<{\n state?: TestResult['status']\n failedTestsCount: number | undefined\n pendingTestsCount: number | undefined\n totalTestsCount: number | undefined\n inline?: boolean\n }>()\n\nconst { cx } = useBindCx()\n\nconst icon = computed(() => {\n if (state === 'passed') {\n return { name: 'Checkmark', color: 'text-green p-0.25' }\n }\n\n if (state === 'failed') {\n return { name: 'Close', color: 'text-red' }\n }\n\n return { name: 'Ellipses', color: 'text-c-1' }\n})\n\nconst statusVariants = cva({\n base: 'flex items-center gap-1.5 rounded-full border pr-2 pl-1.25',\n variants: {\n status: {\n passed: 'text-green',\n failed: 'text-red',\n pending: 'text-orange',\n },\n },\n})\n\nconst textVariants = cva({\n base: 'text-c-2',\n variants: {\n inline: {\n true: 'text-xs',\n false: 'text-sm',\n },\n },\n})\n\n// Display the number of tests that passed e.g 1/3\nconst getTestCountDisplay = computed(() => {\n // When there are no tests, show nothing\n if (totalTestsCount === undefined) {\n return ''\n }\n\n // When they failed, show failed/total\n if (failedTestsCount) {\n return `${failedTestsCount}/${totalTestsCount}`\n }\n\n // When they passed, show passed/total\n const completedTests =\n totalTestsCount - (pendingTestsCount || 0) - (failedTestsCount || 0)\n return `${completedTests}/${totalTestsCount}`\n})\n</script>\n\n<template>\n <div v-bind=\"cx(statusVariants({ status: state }))\">\n <ScalarIcon\n v-if=\"state\"\n :class=\"icon.color\"\n :icon=\"icon.name as Icon\"\n size=\"sm\" />\n <span\n v-if=\"!inline\"\n class=\"ml-1.25 capitalize\">\n {{ state }}\n </span>\n <span\n v-if=\"totalTestsCount !== undefined\"\n v-bind=\"cx(textVariants({ inline }))\">\n {{ getTestCountDisplay }}\n </span>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"TestResultIndicator.vue.js","names":[],"sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultIndicator.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarIcon, type Icon } from '@scalar/components/icon'\nimport { cva, useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed } from 'vue'\n\nimport type { TestResult } from '@/libs/execute-scripts'\n\nconst { state, totalTestsCount, pendingTestsCount, failedTestsCount } =\n defineProps<{\n state?: TestResult['status']\n failedTestsCount: number | undefined\n pendingTestsCount: number | undefined\n totalTestsCount: number | undefined\n inline?: boolean\n }>()\n\nconst { cx } = useBindCx()\n\nconst icon = computed(() => {\n if (state === 'passed') {\n return { name: 'Checkmark', color: 'text-green p-0.25' }\n }\n\n if (state === 'failed') {\n return { name: 'Close', color: 'text-red' }\n }\n\n return { name: 'Ellipses', color: 'text-c-1' }\n})\n\nconst statusVariants = cva({\n base: 'flex items-center gap-1.5 rounded-full border pr-2 pl-1.25',\n variants: {\n status: {\n passed: 'text-green',\n failed: 'text-red',\n pending: 'text-orange',\n },\n },\n})\n\nconst textVariants = cva({\n base: 'text-c-2',\n variants: {\n inline: {\n true: 'text-xs',\n false: 'text-sm',\n },\n },\n})\n\n// Display the number of tests that passed e.g 1/3\nconst getTestCountDisplay = computed(() => {\n // When there are no tests, show nothing\n if (totalTestsCount === undefined) {\n return ''\n }\n\n // When they failed, show failed/total\n if (failedTestsCount) {\n return `${failedTestsCount}/${totalTestsCount}`\n }\n\n // When they passed, show passed/total\n const completedTests =\n totalTestsCount - (pendingTestsCount || 0) - (failedTestsCount || 0)\n return `${completedTests}/${totalTestsCount}`\n})\n</script>\n\n<template>\n <div v-bind=\"cx(statusVariants({ status: state }))\">\n <ScalarIcon\n v-if=\"state\"\n :class=\"icon.color\"\n :icon=\"icon.name as Icon\"\n size=\"sm\" />\n <span\n v-if=\"!inline\"\n class=\"ml-1.25 capitalize\">\n {{ state }}\n </span>\n <span\n v-if=\"totalTestsCount !== undefined\"\n v-bind=\"cx(textVariants({ inline }))\">\n {{ getTestCountDisplay }}\n </span>\n </div>\n</template>\n"],"mappings":""}
@@ -1,5 +1,6 @@
1
1
  import { computed, createBlock, createCommentVNode, createElementBlock, defineComponent, guardReactiveProps, mergeProps, normalizeClass, normalizeProps, openBlock, toDisplayString, unref } from "vue";
2
- import { ScalarIcon, cva, useBindCx } from "@scalar/components";
2
+ import { ScalarIcon } from "@scalar/components/icon";
3
+ import { cva, useBindCx } from "@scalar/use-hooks/useBindCx";
3
4
  //#region src/plugins/request-scripts/components/TestResults/TestResultIndicator.vue?vue&type=script&setup=true&lang.ts
4
5
  var _hoisted_1 = {
5
6
  key: 1,
@@ -1 +1 @@
1
- {"version":3,"file":"TestResultIndicator.vue.script.js","names":[],"sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultIndicator.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { cva, ScalarIcon, useBindCx, type Icon } from '@scalar/components'\nimport { computed } from 'vue'\n\nimport type { TestResult } from '@/libs/execute-scripts'\n\nconst { state, totalTestsCount, pendingTestsCount, failedTestsCount } =\n defineProps<{\n state?: TestResult['status']\n failedTestsCount: number | undefined\n pendingTestsCount: number | undefined\n totalTestsCount: number | undefined\n inline?: boolean\n }>()\n\nconst { cx } = useBindCx()\n\nconst icon = computed(() => {\n if (state === 'passed') {\n return { name: 'Checkmark', color: 'text-green p-0.25' }\n }\n\n if (state === 'failed') {\n return { name: 'Close', color: 'text-red' }\n }\n\n return { name: 'Ellipses', color: 'text-c-1' }\n})\n\nconst statusVariants = cva({\n base: 'flex items-center gap-1.5 rounded-full border pr-2 pl-1.25',\n variants: {\n status: {\n passed: 'text-green',\n failed: 'text-red',\n pending: 'text-orange',\n },\n },\n})\n\nconst textVariants = cva({\n base: 'text-c-2',\n variants: {\n inline: {\n true: 'text-xs',\n false: 'text-sm',\n },\n },\n})\n\n// Display the number of tests that passed e.g 1/3\nconst getTestCountDisplay = computed(() => {\n // When there are no tests, show nothing\n if (totalTestsCount === undefined) {\n return ''\n }\n\n // When they failed, show failed/total\n if (failedTestsCount) {\n return `${failedTestsCount}/${totalTestsCount}`\n }\n\n // When they passed, show passed/total\n const completedTests =\n totalTestsCount - (pendingTestsCount || 0) - (failedTestsCount || 0)\n return `${completedTests}/${totalTestsCount}`\n})\n</script>\n\n<template>\n <div v-bind=\"cx(statusVariants({ status: state }))\">\n <ScalarIcon\n v-if=\"state\"\n :class=\"icon.color\"\n :icon=\"icon.name as Icon\"\n size=\"sm\" />\n <span\n v-if=\"!inline\"\n class=\"ml-1.25 capitalize\">\n {{ state }}\n </span>\n <span\n v-if=\"totalTestsCount !== undefined\"\n v-bind=\"cx(textVariants({ inline }))\">\n {{ getTestCountDisplay }}\n </span>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;EAeA,MAAM,EAAE,OAAO,WAAU;EAEzB,MAAM,OAAO,eAAe;AAC1B,OAAI,QAAA,UAAU,SACZ,QAAO;IAAE,MAAM;IAAa,OAAO;IAAoB;AAGzD,OAAI,QAAA,UAAU,SACZ,QAAO;IAAE,MAAM;IAAS,OAAO;IAAW;AAG5C,UAAO;IAAE,MAAM;IAAY,OAAO;IAAW;IAC9C;EAED,MAAM,iBAAiB,IAAI;GACzB,MAAM;GACN,UAAU,EACR,QAAQ;IACN,QAAQ;IACR,QAAQ;IACR,SAAS;IACV,EACF;GACF,CAAA;EAED,MAAM,eAAe,IAAI;GACvB,MAAM;GACN,UAAU,EACR,QAAQ;IACN,MAAM;IACN,OAAO;IACR,EACF;GACF,CAAA;EAGD,MAAM,sBAAsB,eAAe;AAEzC,OAAI,QAAA,oBAAoB,KAAA,EACtB,QAAO;AAIT,OAAI,QAAA,iBACF,QAAO,GAAG,QAAA,iBAAiB,GAAG,QAAA;AAMhC,UAAO,GADL,QAAA,mBAAmB,QAAA,qBAAqB,MAAM,QAAA,oBAAoB,GAC3C,GAAG,QAAA;IAC7B;;uBAIC,mBAgBM,OAAA,eAAA,mBAhBO,MAAA,GAAE,CAAC,MAAA,eAAc,CAAA,EAAA,QAAW,QAAA,OAAK,CAAA,CAAA,CAAA,CAAA,EAAA;IAEpC,QAAA,SAAA,WAAA,EADR,YAIc,MAAA,WAAA,EAAA;;KAFX,OAAK,eAAE,KAAA,MAAK,MAAK;KACjB,MAAM,KAAA,MAAK;KACZ,MAAK;;KAEE,QAAA,UAAA,WAAA,EADT,mBAIO,QAJP,YAIO,gBADF,QAAA,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA;IAGF,QAAA,oBAAoB,KAAA,KAAA,WAAA,EAD5B,mBAIO,QAAA,eAAA,WAAA,EAAA,KAAA,GAAA,EAFG,MAAA,GAAE,CAAC,MAAA,aAAY,CAAA,EAAA,QAAG,QAAA,QAAM,CAAA,CAAA,CAAA,CAAA,EAAA,gBAC7B,oBAAA,MAAmB,EAAA,GAAA,IAAA,mBAAA,IAAA,KAAA"}
1
+ {"version":3,"file":"TestResultIndicator.vue.script.js","names":[],"sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultIndicator.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarIcon, type Icon } from '@scalar/components/icon'\nimport { cva, useBindCx } from '@scalar/use-hooks/useBindCx'\nimport { computed } from 'vue'\n\nimport type { TestResult } from '@/libs/execute-scripts'\n\nconst { state, totalTestsCount, pendingTestsCount, failedTestsCount } =\n defineProps<{\n state?: TestResult['status']\n failedTestsCount: number | undefined\n pendingTestsCount: number | undefined\n totalTestsCount: number | undefined\n inline?: boolean\n }>()\n\nconst { cx } = useBindCx()\n\nconst icon = computed(() => {\n if (state === 'passed') {\n return { name: 'Checkmark', color: 'text-green p-0.25' }\n }\n\n if (state === 'failed') {\n return { name: 'Close', color: 'text-red' }\n }\n\n return { name: 'Ellipses', color: 'text-c-1' }\n})\n\nconst statusVariants = cva({\n base: 'flex items-center gap-1.5 rounded-full border pr-2 pl-1.25',\n variants: {\n status: {\n passed: 'text-green',\n failed: 'text-red',\n pending: 'text-orange',\n },\n },\n})\n\nconst textVariants = cva({\n base: 'text-c-2',\n variants: {\n inline: {\n true: 'text-xs',\n false: 'text-sm',\n },\n },\n})\n\n// Display the number of tests that passed e.g 1/3\nconst getTestCountDisplay = computed(() => {\n // When there are no tests, show nothing\n if (totalTestsCount === undefined) {\n return ''\n }\n\n // When they failed, show failed/total\n if (failedTestsCount) {\n return `${failedTestsCount}/${totalTestsCount}`\n }\n\n // When they passed, show passed/total\n const completedTests =\n totalTestsCount - (pendingTestsCount || 0) - (failedTestsCount || 0)\n return `${completedTests}/${totalTestsCount}`\n})\n</script>\n\n<template>\n <div v-bind=\"cx(statusVariants({ status: state }))\">\n <ScalarIcon\n v-if=\"state\"\n :class=\"icon.color\"\n :icon=\"icon.name as Icon\"\n size=\"sm\" />\n <span\n v-if=\"!inline\"\n class=\"ml-1.25 capitalize\">\n {{ state }}\n </span>\n <span\n v-if=\"totalTestsCount !== undefined\"\n v-bind=\"cx(textVariants({ inline }))\">\n {{ getTestCountDisplay }}\n </span>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAgBA,MAAM,EAAE,OAAO,WAAU;EAEzB,MAAM,OAAO,eAAe;AAC1B,OAAI,QAAA,UAAU,SACZ,QAAO;IAAE,MAAM;IAAa,OAAO;IAAoB;AAGzD,OAAI,QAAA,UAAU,SACZ,QAAO;IAAE,MAAM;IAAS,OAAO;IAAW;AAG5C,UAAO;IAAE,MAAM;IAAY,OAAO;IAAW;IAC9C;EAED,MAAM,iBAAiB,IAAI;GACzB,MAAM;GACN,UAAU,EACR,QAAQ;IACN,QAAQ;IACR,QAAQ;IACR,SAAS;IACV,EACF;GACF,CAAA;EAED,MAAM,eAAe,IAAI;GACvB,MAAM;GACN,UAAU,EACR,QAAQ;IACN,MAAM;IACN,OAAO;IACR,EACF;GACF,CAAA;EAGD,MAAM,sBAAsB,eAAe;AAEzC,OAAI,QAAA,oBAAoB,KAAA,EACtB,QAAO;AAIT,OAAI,QAAA,iBACF,QAAO,GAAG,QAAA,iBAAiB,GAAG,QAAA;AAMhC,UAAO,GADL,QAAA,mBAAmB,QAAA,qBAAqB,MAAM,QAAA,oBAAoB,GAC3C,GAAG,QAAA;IAC7B;;uBAIC,mBAgBM,OAAA,eAAA,mBAhBO,MAAA,GAAE,CAAC,MAAA,eAAc,CAAA,EAAA,QAAW,QAAA,OAAK,CAAA,CAAA,CAAA,CAAA,EAAA;IAEpC,QAAA,SAAA,WAAA,EADR,YAIc,MAAA,WAAA,EAAA;;KAFX,OAAK,eAAE,KAAA,MAAK,MAAK;KACjB,MAAM,KAAA,MAAK;KACZ,MAAK;;KAEE,QAAA,UAAA,WAAA,EADT,mBAIO,QAJP,YAIO,gBADF,QAAA,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA;IAGF,QAAA,oBAAoB,KAAA,KAAA,WAAA,EAD5B,mBAIO,QAAA,eAAA,WAAA,EAAA,KAAA,GAAA,EAFG,MAAA,GAAE,CAAC,MAAA,aAAY,CAAA,EAAA,QAAG,QAAA,QAAM,CAAA,CAAA,CAAA,CAAA,EAAA,gBAC7B,oBAAA,MAAmB,EAAA,GAAA,IAAA,mBAAA,IAAA,KAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"TestResultItem.vue.js","names":[],"sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarIcon, type Icon } from '@scalar/components'\nimport { computed } from 'vue'\n\nimport type { TestResult } from '@/libs/execute-scripts'\n\nconst { currentState, result } = defineProps<{\n result: TestResult\n currentState: TestResult['status']\n}>()\n\nconst resultStatus = computed(() => {\n if (result.passed) {\n return { icon: 'Checkmark', color: 'text-green p-0.25' }\n }\n\n if (!result.passed && currentState !== 'pending') {\n return { icon: 'Close', color: 'text-red' }\n }\n\n return { icon: 'Ellipses', color: 'text-c-1' }\n})\n</script>\n\n<template>\n <div class=\"flex flex-col\">\n <div\n class=\"flex h-8 items-center gap-2 pr-3 pl-2.25\"\n :class=\"result.error && 'bg-b-danger'\">\n <!-- Title -->\n <div class=\"flex items-center gap-3 p-2\">\n <ScalarIcon\n :class=\"resultStatus.color\"\n :icon=\"resultStatus.icon as Icon\"\n size=\"sm\" />\n\n <span class=\"text-c-2 overflow-hidden text-ellipsis whitespace-nowrap\">\n {{ result.title }}\n </span>\n </div>\n <!-- Duration -->\n <span class=\"text-c-3 ml-auto whitespace-nowrap\">\n {{ result.duration }} ms\n </span>\n </div>\n <!-- Error -->\n <div\n v-if=\"result.error\"\n class=\"bg-b-danger text-c-1 flex items-center pr-3 pb-1.5 pl-9\">\n {{ result.error }}\n </div>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"TestResultItem.vue.js","names":[],"sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarIcon, type Icon } from '@scalar/components/icon'\nimport { computed } from 'vue'\n\nimport type { TestResult } from '@/libs/execute-scripts'\n\nconst { currentState, result } = defineProps<{\n result: TestResult\n currentState: TestResult['status']\n}>()\n\nconst resultStatus = computed(() => {\n if (result.passed) {\n return { icon: 'Checkmark', color: 'text-green p-0.25' }\n }\n\n if (!result.passed && currentState !== 'pending') {\n return { icon: 'Close', color: 'text-red' }\n }\n\n return { icon: 'Ellipses', color: 'text-c-1' }\n})\n</script>\n\n<template>\n <div class=\"flex flex-col\">\n <div\n class=\"flex h-8 items-center gap-2 pr-3 pl-2.25\"\n :class=\"result.error && 'bg-b-danger'\">\n <!-- Title -->\n <div class=\"flex items-center gap-3 p-2\">\n <ScalarIcon\n :class=\"resultStatus.color\"\n :icon=\"resultStatus.icon as Icon\"\n size=\"sm\" />\n\n <span class=\"text-c-2 overflow-hidden text-ellipsis whitespace-nowrap\">\n {{ result.title }}\n </span>\n </div>\n <!-- Duration -->\n <span class=\"text-c-3 ml-auto whitespace-nowrap\">\n {{ result.duration }} ms\n </span>\n </div>\n <!-- Error -->\n <div\n v-if=\"result.error\"\n class=\"bg-b-danger text-c-1 flex items-center pr-3 pb-1.5 pl-9\">\n {{ result.error }}\n </div>\n </div>\n</template>\n"],"mappings":""}
@@ -1,5 +1,5 @@
1
1
  import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, openBlock, toDisplayString, unref } from "vue";
2
- import { ScalarIcon } from "@scalar/components";
2
+ import { ScalarIcon } from "@scalar/components/icon";
3
3
  //#region src/plugins/request-scripts/components/TestResults/TestResultItem.vue?vue&type=script&setup=true&lang.ts
4
4
  var _hoisted_1 = { class: "flex flex-col" };
5
5
  var _hoisted_2 = { class: "flex items-center gap-3 p-2" };
@@ -1 +1 @@
1
- {"version":3,"file":"TestResultItem.vue.script.js","names":[],"sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarIcon, type Icon } from '@scalar/components'\nimport { computed } from 'vue'\n\nimport type { TestResult } from '@/libs/execute-scripts'\n\nconst { currentState, result } = defineProps<{\n result: TestResult\n currentState: TestResult['status']\n}>()\n\nconst resultStatus = computed(() => {\n if (result.passed) {\n return { icon: 'Checkmark', color: 'text-green p-0.25' }\n }\n\n if (!result.passed && currentState !== 'pending') {\n return { icon: 'Close', color: 'text-red' }\n }\n\n return { icon: 'Ellipses', color: 'text-c-1' }\n})\n</script>\n\n<template>\n <div class=\"flex flex-col\">\n <div\n class=\"flex h-8 items-center gap-2 pr-3 pl-2.25\"\n :class=\"result.error && 'bg-b-danger'\">\n <!-- Title -->\n <div class=\"flex items-center gap-3 p-2\">\n <ScalarIcon\n :class=\"resultStatus.color\"\n :icon=\"resultStatus.icon as Icon\"\n size=\"sm\" />\n\n <span class=\"text-c-2 overflow-hidden text-ellipsis whitespace-nowrap\">\n {{ result.title }}\n </span>\n </div>\n <!-- Duration -->\n <span class=\"text-c-3 ml-auto whitespace-nowrap\">\n {{ result.duration }} ms\n </span>\n </div>\n <!-- Error -->\n <div\n v-if=\"result.error\"\n class=\"bg-b-danger text-c-1 flex items-center pr-3 pb-1.5 pl-9\">\n {{ result.error }}\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAWA,MAAM,eAAe,eAAe;AAClC,OAAI,QAAA,OAAO,OACT,QAAO;IAAE,MAAM;IAAa,OAAO;IAAoB;AAGzD,OAAI,CAAC,QAAA,OAAO,UAAU,QAAA,iBAAiB,UACrC,QAAO;IAAE,MAAM;IAAS,OAAO;IAAW;AAG5C,UAAO;IAAE,MAAM;IAAY,OAAO;IAAW;IAC9C;;uBAIC,mBA0BM,OA1BN,YA0BM,CAzBJ,mBAkBM,OAAA,EAjBJ,OAAK,eAAA,CAAC,4CACE,QAAA,OAAO,SAAK,cAAA,CAAA,EAAA,EAAA,CAEpB,mBASM,OATN,YASM,CARJ,YAGc,MAAA,WAAA,EAAA;IAFX,OAAK,eAAE,aAAA,MAAa,MAAK;IACzB,MAAM,aAAA,MAAa;IACpB,MAAK;mCAEP,mBAEO,QAFP,YAEO,gBADF,QAAA,OAAO,MAAK,EAAA,EAAA,CAAA,CAAA,EAInB,mBAEO,QAFP,YAEO,gBADF,QAAA,OAAO,SAAQ,GAAG,QACvB,EAAA,CAAA,EAAA,EAAA,EAIM,QAAA,OAAO,SAAA,WAAA,EADf,mBAIM,OAJN,YAIM,gBADD,QAAA,OAAO,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"TestResultItem.vue.script.js","names":[],"sources":["../../../../../src/plugins/request-scripts/components/TestResults/TestResultItem.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ScalarIcon, type Icon } from '@scalar/components/icon'\nimport { computed } from 'vue'\n\nimport type { TestResult } from '@/libs/execute-scripts'\n\nconst { currentState, result } = defineProps<{\n result: TestResult\n currentState: TestResult['status']\n}>()\n\nconst resultStatus = computed(() => {\n if (result.passed) {\n return { icon: 'Checkmark', color: 'text-green p-0.25' }\n }\n\n if (!result.passed && currentState !== 'pending') {\n return { icon: 'Close', color: 'text-red' }\n }\n\n return { icon: 'Ellipses', color: 'text-c-1' }\n})\n</script>\n\n<template>\n <div class=\"flex flex-col\">\n <div\n class=\"flex h-8 items-center gap-2 pr-3 pl-2.25\"\n :class=\"result.error && 'bg-b-danger'\">\n <!-- Title -->\n <div class=\"flex items-center gap-3 p-2\">\n <ScalarIcon\n :class=\"resultStatus.color\"\n :icon=\"resultStatus.icon as Icon\"\n size=\"sm\" />\n\n <span class=\"text-c-2 overflow-hidden text-ellipsis whitespace-nowrap\">\n {{ result.title }}\n </span>\n </div>\n <!-- Duration -->\n <span class=\"text-c-3 ml-auto whitespace-nowrap\">\n {{ result.duration }} ms\n </span>\n </div>\n <!-- Error -->\n <div\n v-if=\"result.error\"\n class=\"bg-b-danger text-c-1 flex items-center pr-3 pb-1.5 pl-9\">\n {{ result.error }}\n </div>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;EAWA,MAAM,eAAe,eAAe;AAClC,OAAI,QAAA,OAAO,OACT,QAAO;IAAE,MAAM;IAAa,OAAO;IAAoB;AAGzD,OAAI,CAAC,QAAA,OAAO,UAAU,QAAA,iBAAiB,UACrC,QAAO;IAAE,MAAM;IAAS,OAAO;IAAW;AAG5C,UAAO;IAAE,MAAM;IAAY,OAAO;IAAW;IAC9C;;uBAIC,mBA0BM,OA1BN,YA0BM,CAzBJ,mBAkBM,OAAA,EAjBJ,OAAK,eAAA,CAAC,4CACE,QAAA,OAAO,SAAK,cAAA,CAAA,EAAA,EAAA,CAEpB,mBASM,OATN,YASM,CARJ,YAGc,MAAA,WAAA,EAAA;IAFX,OAAK,eAAE,aAAA,MAAa,MAAK;IACzB,MAAM,aAAA,MAAa;IACpB,MAAK;mCAEP,mBAEO,QAFP,YAEO,gBADF,QAAA,OAAO,MAAK,EAAA,EAAA,CAAA,CAAA,EAInB,mBAEO,QAFP,YAEO,gBADF,QAAA,OAAO,SAAQ,GAAG,QACvB,EAAA,CAAA,EAAA,EAAA,EAIM,QAAA,OAAO,SAAA,WAAA,EADf,mBAIM,OAJN,YAIM,gBADD,QAAA,OAAO,MAAK,EAAA,EAAA,IAAA,mBAAA,IAAA,KAAA,CAAA,CAAA"}
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "post-response scripts",
20
20
  "api client"
21
21
  ],
22
- "version": "0.4.15",
22
+ "version": "0.4.16",
23
23
  "engines": {
24
24
  "node": ">=22"
25
25
  },
@@ -41,6 +41,11 @@
41
41
  "import": "./dist/plugins/index.js",
42
42
  "types": "./dist/plugins/index.d.ts",
43
43
  "default": "./dist/plugins/index.js"
44
+ },
45
+ "./sandbox": {
46
+ "import": "./dist/libs/execute-scripts/postman-adapter/sandbox-frame-entry.js",
47
+ "types": "./dist/libs/execute-scripts/postman-adapter/sandbox-frame-entry.d.ts",
48
+ "default": "./dist/libs/execute-scripts/postman-adapter/sandbox-frame-entry.js"
44
49
  }
45
50
  },
46
51
  "files": [
@@ -54,10 +59,12 @@
54
59
  "postman-collection": "^5.3.0",
55
60
  "postman-sandbox": "^6.6.1",
56
61
  "vue": "^3.5.30",
57
- "@scalar/components": "0.25.0",
62
+ "@scalar/components": "0.26.0",
63
+ "@scalar/oas-utils": "0.17.1",
64
+ "@scalar/helpers": "0.8.0",
58
65
  "@scalar/use-codemirror": "0.14.11",
59
- "@scalar/oas-utils": "0.17.0",
60
- "@scalar/workspace-store": "0.51.0"
66
+ "@scalar/workspace-store": "0.51.1",
67
+ "@scalar/use-hooks": "0.4.6"
61
68
  },
62
69
  "devDependencies": {
63
70
  "@vitejs/plugin-vue": "^6.0.3",