@scalar/pre-post-request-scripts 0.4.41 → 0.4.43

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 (26) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/ViewLayout/ViewLayoutCollapse.vue.js.map +1 -1
  3. package/dist/components/ViewLayout/ViewLayoutCollapse.vue.script.js.map +1 -1
  4. package/dist/consts/example-scripts.d.ts +2 -1
  5. package/dist/consts/example-scripts.d.ts.map +1 -1
  6. package/dist/consts/example-scripts.js +11 -0
  7. package/dist/consts/example-scripts.js.map +1 -1
  8. package/dist/libs/execute-scripts/execute-post-response-script.d.ts +2 -0
  9. package/dist/libs/execute-scripts/execute-post-response-script.d.ts.map +1 -1
  10. package/dist/libs/execute-scripts/execute-post-response-script.js +1 -0
  11. package/dist/libs/execute-scripts/execute-post-response-script.js.map +1 -1
  12. package/dist/libs/execute-scripts/postman-adapter/sandbox-adapter.d.ts +4 -2
  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 +6 -4
  15. package/dist/libs/execute-scripts/postman-adapter/sandbox-adapter.js.map +1 -1
  16. package/dist/libs/execute-scripts/postman-adapter/sandbox-protocol.d.ts +2 -0
  17. package/dist/libs/execute-scripts/postman-adapter/sandbox-protocol.d.ts.map +1 -1
  18. package/dist/libs/execute-scripts/postman-adapter/sandbox-protocol.js.map +1 -1
  19. package/dist/plugins/request-scripts/components/ScriptsSection.vue.d.ts +1 -1
  20. package/dist/plugins/request-scripts/components/ScriptsSection.vue.d.ts.map +1 -1
  21. package/dist/plugins/request-scripts/components/ScriptsSection.vue.js.map +1 -1
  22. package/dist/plugins/request-scripts/components/ScriptsSection.vue.script.js.map +1 -1
  23. package/dist/plugins/request-scripts/request-scripts-plugin.d.ts.map +1 -1
  24. package/dist/plugins/request-scripts/request-scripts-plugin.js +2 -1
  25. package/dist/plugins/request-scripts/request-scripts-plugin.js.map +1 -1
  26. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @scalar/scripts
2
2
 
3
+ ## 0.4.43
4
+
5
+ ### Patch Changes
6
+
7
+ - [#10160](https://github.com/scalar/scalar/pull/10160): Pass request duration to post-response scripts as `pm.response.responseTime` in milliseconds and enable the response-time example.
8
+ - [#10102](https://github.com/scalar/scalar/pull/10102): Allow response hooks to return a replacement Response before the client processes its body, status, and headers. Add the onResponseReceived configuration callback for API References. Existing hooks can still read responses and return nothing.
9
+ - [#10140](https://github.com/scalar/scalar/pull/10140): Replace redundant type assertions with compiler-checked annotations, typed accumulators, and existing guards across helpers, API conversion, request handling, and schema rendering.
10
+
11
+ Narrow DOM elements and caught errors before accessing their properties. Correct header lookup to include missing values and handle them during PowerShell snippet generation.
12
+
13
+ Validate release-note provider responses, represent unresolved references and absent groups in helper return types, and require narrowing merged object values. Preserve AsyncAPI broker credentials separately from HTTP authentication schemes.
14
+
15
+ ## 0.4.42
16
+
3
17
  ## 0.4.41
4
18
 
5
19
  ## 0.4.40
@@ -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/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
+ {"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\"\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 +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/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,MAAM;;uBAItB,YAwEa,MAAA,UAAA,GAAA;IAtEX,IAAG;IACH,OAAK,eAAA,CAAC,kFACE,QAAA,YAAQ,uCAAA,CAAA;IACf,aAAa,QAAA;IACb,QAAQ,QAAA;;sBAIiD,EAThD,WAAI,CAOd,YAE0D,sBAAA;KADvD,OAAO;KACP,UAAM,OAAA,OAAA,OAAA,MAAG,UAAU,KAAI,qBAAsB,KAAK;4BAErD,mBA2DU,WAAA;KA1DP,mBAAiB,MAAA,SAAA;KAClB,OAAM;QACN,mBAgDM,OAAA,EA/CJ,OAAK,eAAA,CAAC,4BACE,QAAA,YAAQ,gCAAA,CAAA,EAAA,GAAA,CAEhB,YAkCmB,MAAA,gBAAA,GAAA;KAjCjB,OAAK,eAAA,CAAC,wKACE,QAAA,YAAQ,OAAA,CAAA;KACf,UAAU,QAAA;;4BAKG,CAAA,CAHL,QAAA,YAAA,UAAA,GADT,YAIc,MAAA,UAAA,GAAA;;MAFZ,OAAM;MACN,MAAK;MACL,MAAK;yCAGP,mBAsBK,MAtBL,YAsBK,CApBH,mBAWO,QAAA;MAVJ,IAAI,MAAA,SAAA;MACL,OAAM;SACN,WAEiB,KAAA,QAAA,SAAA,EAAR,KAAI,CAAA,GAAA,CAEJ,QAAA,UAAA,GADT,mBAIO,QAJP,YAEkB,eAElB,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,GAAA,UAAA,GAAA,CAKO,QAAQ,QAAA,aAAA,UAAA,GADjB,mBAKO,QALP,YAKO,CAAA,gBAAA,gBAFF,QAAA,SAAS,IAAG,KACf,CAAA,GAAA,mBAAiE,QAAjE,YAAsB,SAAI,gBAAG,QAAA,cAAS,IAAA,KAAA,GAAA,GAAA,CAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,CAAA,CAAA;;qCAOpCA,KAAAA,OAAO,WAAA,UAAA,GADf,mBAMM,OANN,YAMM,CAHJ,WAEiB,KAAA,QAAA,WAAA,EAAR,KAAI,CAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,CAAA,GAKjB,YAIkB,MAAA,eAAA,GAJlB,WACUC,KAGQ,QAHF,EACd,OAAM,6CAA4C,CAAA,GAAA;4BAC7B,CAArB,WAAqB,KAAA,QAAA,WAAA,EAAR,KAAI,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\"\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,MAAM;;uBAItB,YAwEa,MAAA,UAAA,GAAA;IAtEX,IAAG;IACH,OAAK,eAAA,CAAC,kFACE,QAAA,YAAQ,uCAAA,CAAA;IACf,aAAa,QAAA;IACb,QAAQ,QAAA;;sBAIiD,EAThD,WAAI,CAOd,YAE0D,sBAAA;KADvD,OAAO;KACP,UAAM,OAAA,OAAA,OAAA,MAAG,UAAU,KAAI,qBAAsB,KAAK;4BAErD,mBA2DU,WAAA;KA1DP,mBAAiB,MAAA,SAAA;KAClB,OAAM;QACN,mBAgDM,OAAA,EA/CJ,OAAK,eAAA,CAAC,4BACE,QAAA,YAAQ,gCAAA,CAAA,EAAA,GAAA,CAEhB,YAkCmB,MAAA,gBAAA,GAAA;KAjCjB,OAAK,eAAA,CAAC,wKACE,QAAA,YAAQ,OAAA,CAAA;KACf,UAAU,QAAA;;4BAKG,CAAA,CAHL,QAAA,YAAA,UAAA,GADT,YAIc,MAAA,UAAA,GAAA;;MAFZ,OAAM;MACN,MAAK;MACL,MAAK;yCAGP,mBAsBK,MAtBL,YAsBK,CApBH,mBAWO,QAAA;MAVJ,IAAI,MAAA,SAAA;MACL,OAAM;SACN,WAEiB,KAAA,QAAA,SAAA,EAAR,KAAI,CAAA,GAAA,CAEJ,QAAA,UAAA,GADT,mBAIO,QAJP,YAEkB,eAElB,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,GAAA,UAAA,GAAA,CAKO,QAAQ,QAAA,aAAA,UAAA,GADjB,mBAKO,QALP,YAKO,CAAA,gBAAA,gBAFF,QAAA,SAAS,IAAG,KACf,CAAA,GAAA,mBAAiE,QAAjE,YAAsB,SAAI,gBAAG,QAAA,cAAS,IAAA,KAAA,GAAA,GAAA,CAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,CAAA,CAAA;;qCAOpCA,KAAAA,OAAO,WAAA,UAAA,GADf,mBAMM,OANN,YAMM,CAHJ,WAEiB,KAAA,QAAA,WAAA,EAAR,KAAI,CAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,CAAA,GAKjB,YAIkB,MAAA,eAAA,GAJlB,WACUC,KAGQ,QAHF,EACd,OAAM,6CAA4C,CAAA,GAAA;4BAC7B,CAArB,WAAqB,KAAA,QAAA,WAAA,EAAR,KAAI,CAAA,CAAA,CAAA"}
@@ -2,7 +2,8 @@ type ExampleScript = {
2
2
  title: string;
3
3
  script: string;
4
4
  mockResponse: {
5
- body?: any;
5
+ body?: unknown;
6
+ duration?: number;
6
7
  status?: number;
7
8
  headers?: Record<string, string>;
8
9
  text?: () => string;
@@ -1 +1 @@
1
- {"version":3,"file":"example-scripts.d.ts","sourceRoot":"","sources":["../../src/consts/example-scripts.ts"],"names":[],"mappings":"AAAA,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE;QACZ,IAAI,CAAC,EAAE,GAAG,CAAA;QACV,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,IAAI,CAAC,EAAE,MAAM,MAAM,CAAA;KACpB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,aAAa,EA0H1C,CAAA"}
1
+ {"version":3,"file":"example-scripts.d.ts","sourceRoot":"","sources":["../../src/consts/example-scripts.ts"],"names":[],"mappings":"AAAA,KAAK,aAAa,GAAG;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE;QACZ,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAChC,IAAI,CAAC,EAAE,MAAM,MAAM,CAAA;KACpB,CAAA;CACF,CAAA;AAED,eAAO,MAAM,eAAe,EAAE,aAAa,EA0H1C,CAAA"}
@@ -10,6 +10,17 @@ var EXAMPLE_SCRIPTS = [
10
10
  body: { success: true }
11
11
  }
12
12
  },
13
+ {
14
+ title: "Check response time",
15
+ script: `pm.test("Response time is acceptable", () => {
16
+ pm.expect(pm.response.responseTime).to.be.below(200)
17
+ })`,
18
+ mockResponse: {
19
+ status: 200,
20
+ body: { success: true },
21
+ duration: 150
22
+ }
23
+ },
13
24
  {
14
25
  title: "Check JSON response",
15
26
  script: `pm.test("Response is valid JSON", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"example-scripts.js","names":[],"sources":["../../src/consts/example-scripts.ts"],"sourcesContent":["type ExampleScript = {\n title: string\n script: string\n mockResponse: {\n body?: any\n status?: number\n headers?: Record<string, string>\n text?: () => string\n }\n}\n\nexport const EXAMPLE_SCRIPTS: ExampleScript[] = [\n {\n title: 'Check the status code',\n script: `pm.test(\"Status code is 200\", () => {\n pm.response.to.have.status(200)\n})`,\n mockResponse: {\n status: 200,\n body: { success: true },\n },\n },\n // TODO: We didn't add the proper response duration yet.\n // {\n // title: 'Check response time',\n // script: `pm.test(\"Response time is acceptable\", () => {\n // pm.expect(pm.response.responseTime).to.be.below(200)\n // })`,\n // mockResponse: {\n // status: 200,\n // body: { success: true },\n // },\n // },\n {\n title: 'Check JSON response',\n script: `pm.test(\"Response is valid JSON\", () => {\n const responseData = pm.response.json()\n pm.expect(responseData).to.be.an('object')\n})`,\n mockResponse: {\n status: 200,\n body: { data: 'test' },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Check response headers',\n script: `pm.test(\"Content-Type header is present\", () => {\n pm.response.to.have.header('Content-Type')\n})`,\n mockResponse: {\n status: 200,\n body: { success: true },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Validate JSON schema',\n script: `pm.test(\"Response matches schema\", () => {\n const schema = {\n type: 'object',\n required: ['id', 'name'],\n properties: {\n id: { type: 'number' },\n name: { type: 'string' }\n }\n }\n pm.response.to.have.jsonSchema(schema)\n})`,\n mockResponse: {\n status: 200,\n body: { id: 1, name: 'Test Item' },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Check response body',\n script: `pm.test(\"Response body contains string\", () => {\n pm.expect(pm.response.text()).to.include('success')\n})`,\n mockResponse: {\n status: 200,\n body: 'This is a successful response',\n },\n },\n {\n title: 'Successful POST request',\n script: `pm.test(\"Successful POST request\", () => {\n pm.expect(pm.response.code).to.be.oneOf([201, 202])\n})`,\n mockResponse: {\n status: 201,\n body: { success: true },\n },\n },\n {\n title: 'Check header value',\n script: `pm.test(\"Content-Type is JSON\", () => {\n pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json')\n})`,\n mockResponse: {\n status: 200,\n body: { success: true },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Assert data types',\n script: `pm.test(\"Response data types are correct\", () => {\n const data = pm.response.json()\n pm.expect(data).to.be.an('object')\n pm.expect(data.name).to.be.a('string')\n pm.expect(data.age).to.be.a('number')\n pm.expect(data.hobbies).to.be.an('array')\n})`,\n mockResponse: {\n status: 200,\n body: { name: 'Jane', age: 23, hobbies: ['reading'] },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Check object containment',\n script: `pm.test(\"Response contains expected object\", () => {\n const expected = { created: true, errors: [] }\n pm.expect(pm.response.json()).to.deep.include(expected)\n})`,\n mockResponse: {\n status: 200,\n body: { created: true, errors: [], id: 42 },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n]\n"],"mappings":";AAWA,IAAa,kBAAmC;CAC9C;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;EACxB;CACF;CAYA;EACE,OAAO;EACP,QAAQ;;;;EAIR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,MAAM,OAAO;GACrB,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;GACtB,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;;;;;;;;;EAWR,cAAc;GACZ,QAAQ;GACR,MAAM;IAAE,IAAI;IAAG,MAAM;GAAY;GACjC,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM;EACR;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;EACxB;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;GACtB,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;;;;;EAOR,cAAc;GACZ,QAAQ;GACR,MAAM;IAAE,MAAM;IAAQ,KAAK;IAAI,SAAS,CAAC,SAAS;GAAE;GACpD,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;;EAIR,cAAc;GACZ,QAAQ;GACR,MAAM;IAAE,SAAS;IAAM,QAAQ,CAAC;IAAG,IAAI;GAAG;GAC1C,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;AACF"}
1
+ {"version":3,"file":"example-scripts.js","names":[],"sources":["../../src/consts/example-scripts.ts"],"sourcesContent":["type ExampleScript = {\n title: string\n script: string\n mockResponse: {\n body?: unknown\n duration?: number\n status?: number\n headers?: Record<string, string>\n text?: () => string\n }\n}\n\nexport const EXAMPLE_SCRIPTS: ExampleScript[] = [\n {\n title: 'Check the status code',\n script: `pm.test(\"Status code is 200\", () => {\n pm.response.to.have.status(200)\n})`,\n mockResponse: {\n status: 200,\n body: { success: true },\n },\n },\n {\n title: 'Check response time',\n script: `pm.test(\"Response time is acceptable\", () => {\n pm.expect(pm.response.responseTime).to.be.below(200)\n})`,\n mockResponse: {\n status: 200,\n body: { success: true },\n duration: 150,\n },\n },\n {\n title: 'Check JSON response',\n script: `pm.test(\"Response is valid JSON\", () => {\n const responseData = pm.response.json()\n pm.expect(responseData).to.be.an('object')\n})`,\n mockResponse: {\n status: 200,\n body: { data: 'test' },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Check response headers',\n script: `pm.test(\"Content-Type header is present\", () => {\n pm.response.to.have.header('Content-Type')\n})`,\n mockResponse: {\n status: 200,\n body: { success: true },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Validate JSON schema',\n script: `pm.test(\"Response matches schema\", () => {\n const schema = {\n type: 'object',\n required: ['id', 'name'],\n properties: {\n id: { type: 'number' },\n name: { type: 'string' }\n }\n }\n pm.response.to.have.jsonSchema(schema)\n})`,\n mockResponse: {\n status: 200,\n body: { id: 1, name: 'Test Item' },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Check response body',\n script: `pm.test(\"Response body contains string\", () => {\n pm.expect(pm.response.text()).to.include('success')\n})`,\n mockResponse: {\n status: 200,\n body: 'This is a successful response',\n },\n },\n {\n title: 'Successful POST request',\n script: `pm.test(\"Successful POST request\", () => {\n pm.expect(pm.response.code).to.be.oneOf([201, 202])\n})`,\n mockResponse: {\n status: 201,\n body: { success: true },\n },\n },\n {\n title: 'Check header value',\n script: `pm.test(\"Content-Type is JSON\", () => {\n pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json')\n})`,\n mockResponse: {\n status: 200,\n body: { success: true },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Assert data types',\n script: `pm.test(\"Response data types are correct\", () => {\n const data = pm.response.json()\n pm.expect(data).to.be.an('object')\n pm.expect(data.name).to.be.a('string')\n pm.expect(data.age).to.be.a('number')\n pm.expect(data.hobbies).to.be.an('array')\n})`,\n mockResponse: {\n status: 200,\n body: { name: 'Jane', age: 23, hobbies: ['reading'] },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n {\n title: 'Check object containment',\n script: `pm.test(\"Response contains expected object\", () => {\n const expected = { created: true, errors: [] }\n pm.expect(pm.response.json()).to.deep.include(expected)\n})`,\n mockResponse: {\n status: 200,\n body: { created: true, errors: [], id: 42 },\n headers: { 'Content-Type': 'application/json' },\n },\n },\n]\n"],"mappings":";AAYA,IAAa,kBAAmC;CAC9C;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;EACxB;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;GACtB,UAAU;EACZ;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;;EAIR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,MAAM,OAAO;GACrB,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;GACtB,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;;;;;;;;;EAWR,cAAc;GACZ,QAAQ;GACR,MAAM;IAAE,IAAI;IAAG,MAAM;GAAY;GACjC,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM;EACR;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;EACxB;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;EAGR,cAAc;GACZ,QAAQ;GACR,MAAM,EAAE,SAAS,KAAK;GACtB,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;;;;;EAOR,cAAc;GACZ,QAAQ;GACR,MAAM;IAAE,MAAM;IAAQ,KAAK;IAAI,SAAS,CAAC,SAAS;GAAE;GACpD,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;CACA;EACE,OAAO;EACP,QAAQ;;;;EAIR,cAAc;GACZ,QAAQ;GACR,MAAM;IAAE,SAAS;IAAM,QAAQ,CAAC;IAAG,IAAI;GAAG;GAC1C,SAAS,EAAE,gBAAgB,mBAAmB;EAChD;CACF;AACF"}
@@ -9,6 +9,8 @@ export type TestResult = {
9
9
  export declare const executePostResponseScript: (script: string | undefined, data: {
10
10
  requestBuilder: RequestFactory;
11
11
  response: Response;
12
+ /** Request duration in milliseconds. */
13
+ responseDuration?: number;
12
14
  onTestResultsUpdate?: ((results: TestResult[]) => void) | undefined;
13
15
  variablesStore?: VariablesStore;
14
16
  }) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"execute-post-response-script.d.ts","sourceRoot":"","sources":["../../../src/libs/execute-scripts/execute-post-response-script.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAA;AAK7F,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;CACxC,CAAA;AAED,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,GAAG,SAAS,EAC1B,MAAM;IACJ,cAAc,EAAE,cAAc,CAAA;IAC9B,QAAQ,EAAE,QAAQ,CAAA;IAClB,mBAAmB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACnE,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC,KACA,OAAO,CAAC,IAAI,CAed,CAAA"}
1
+ {"version":3,"file":"execute-post-response-script.d.ts","sourceRoot":"","sources":["../../../src/libs/execute-scripts/execute-post-response-script.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAA;AAK7F,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;CACxC,CAAA;AAED,eAAO,MAAM,yBAAyB,GACpC,QAAQ,MAAM,GAAG,SAAS,EAC1B,MAAM;IACJ,cAAc,EAAE,cAAc,CAAA;IAC9B,QAAQ,EAAE,QAAQ,CAAA;IAClB,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,mBAAmB,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACnE,cAAc,CAAC,EAAE,cAAc,CAAA;CAChC,KACA,OAAO,CAAC,IAAI,CAgBd,CAAA"}
@@ -9,6 +9,7 @@ var executePostResponseScript = async (script, data) => {
9
9
  context: {
10
10
  requestBuilder: data.requestBuilder,
11
11
  response: data.response,
12
+ responseDuration: data.responseDuration,
12
13
  scriptConsole: createConsoleContext(),
13
14
  variablesStore: data.variablesStore
14
15
  },
@@ -1 +1 @@
1
- {"version":3,"file":"execute-post-response-script.js","names":[],"sources":["../../../src/libs/execute-scripts/execute-post-response-script.ts"],"sourcesContent":["import type { RequestFactory, VariablesStore } from '@scalar/workspace-store/request-example'\n\nimport { createConsoleContext } from './context/console'\nimport { executeInPostmanSandbox } from './postman-adapter/sandbox-adapter'\n\nexport type TestResult = {\n title: string\n passed: boolean\n duration: number\n error?: string\n status: 'pending' | 'passed' | 'failed'\n}\n\nexport const executePostResponseScript = async (\n script: string | undefined,\n data: {\n requestBuilder: RequestFactory\n response: Response\n onTestResultsUpdate?: ((results: TestResult[]) => void) | undefined\n variablesStore?: VariablesStore\n },\n): Promise<void> => {\n if (!script) {\n return\n }\n await executeInPostmanSandbox({\n script,\n type: 'post-response',\n context: {\n requestBuilder: data.requestBuilder,\n response: data.response,\n scriptConsole: createConsoleContext(),\n variablesStore: data.variablesStore,\n },\n onTestResultsUpdate: data.onTestResultsUpdate,\n })\n}\n"],"mappings":";;;AAaA,IAAa,4BAA4B,OACvC,QACA,SAMkB;CAClB,IAAI,CAAC,QACH;CAEF,MAAM,wBAAwB;EAC5B;EACA,MAAM;EACN,SAAS;GACP,gBAAgB,KAAK;GACrB,UAAU,KAAK;GACf,eAAe,qBAAqB;GACpC,gBAAgB,KAAK;EACvB;EACA,qBAAqB,KAAK;CAC5B,CAAC;AACH"}
1
+ {"version":3,"file":"execute-post-response-script.js","names":[],"sources":["../../../src/libs/execute-scripts/execute-post-response-script.ts"],"sourcesContent":["import type { RequestFactory, VariablesStore } from '@scalar/workspace-store/request-example'\n\nimport { createConsoleContext } from './context/console'\nimport { executeInPostmanSandbox } from './postman-adapter/sandbox-adapter'\n\nexport type TestResult = {\n title: string\n passed: boolean\n duration: number\n error?: string\n status: 'pending' | 'passed' | 'failed'\n}\n\nexport const executePostResponseScript = async (\n script: string | undefined,\n data: {\n requestBuilder: RequestFactory\n response: Response\n /** Request duration in milliseconds. */\n responseDuration?: number\n onTestResultsUpdate?: ((results: TestResult[]) => void) | undefined\n variablesStore?: VariablesStore\n },\n): Promise<void> => {\n if (!script) {\n return\n }\n await executeInPostmanSandbox({\n script,\n type: 'post-response',\n context: {\n requestBuilder: data.requestBuilder,\n response: data.response,\n responseDuration: data.responseDuration,\n scriptConsole: createConsoleContext(),\n variablesStore: data.variablesStore,\n },\n onTestResultsUpdate: data.onTestResultsUpdate,\n })\n}\n"],"mappings":";;;AAaA,IAAa,4BAA4B,OACvC,QACA,SAQkB;CAClB,IAAI,CAAC,QACH;CAEF,MAAM,wBAAwB;EAC5B;EACA,MAAM;EACN,SAAS;GACP,gBAAgB,KAAK;GACrB,UAAU,KAAK;GACf,kBAAkB,KAAK;GACvB,eAAe,qBAAqB;GACpC,gBAAgB,KAAK;EACvB;EACA,qBAAqB,KAAK;CAC5B,CAAC;AACH"}
@@ -11,7 +11,7 @@ import { type PostmanResponseDefinition, type SandboxExecuteRequest, type Sandbo
11
11
  * This module therefore must NOT import `postman-sandbox`; it only serializes inputs, talks to the
12
12
  * iframe over `postMessage`, and applies the results back to the live store/request.
13
13
  */
14
- export declare const toPostmanResponse: (response: Response) => Promise<PostmanResponseDefinition>;
14
+ export declare const toPostmanResponse: (response: Response, responseDuration?: number) => Promise<PostmanResponseDefinition>;
15
15
  /**
16
16
  * Resolves the sandbox iframe URL.
17
17
  *
@@ -47,13 +47,15 @@ export declare const setSandboxTransport: (next: SandboxTransport) => void;
47
47
  * scripts would defeat the lazy creation that keeps script-free requests cheap.
48
48
  */
49
49
  export declare const prewarmSandboxFrame: () => void;
50
- export declare const executeInPostmanSandbox: ({ script, type, context: { requestBuilder, response, variablesStore, scriptConsole }, onTestResultsUpdate, }: {
50
+ export declare const executeInPostmanSandbox: ({ script, type, context: { requestBuilder, response, responseDuration, variablesStore, scriptConsole }, onTestResultsUpdate, }: {
51
51
  script: string;
52
52
  type: "pre-request" | "post-response";
53
53
  context: {
54
54
  /** Postman Collection request for `pm.request` (not the browser Fetch API Request). */
55
55
  requestBuilder?: RequestFactory;
56
56
  response?: Response;
57
+ /** Request duration in milliseconds. */
58
+ responseDuration?: number;
57
59
  variablesStore?: VariablesStore;
58
60
  scriptConsole: ConsoleContext;
59
61
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-adapter.d.ts","sourceRoot":"","sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts"],"names":[],"mappings":"AAEA,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;AAIjE,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;AAkNtH,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,GAAI,MAAM,gBAAgB,KAAG,IAE5D,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,QAAO,IAQtC,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
+ {"version":3,"file":"sandbox-adapter.d.ts","sourceRoot":"","sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts"],"names":[],"mappings":"AAGA,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;AAIjE,OAAO,EACL,KAAK,yBAAyB,EAE9B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAA;AAE3B;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAC5B,UAAU,QAAQ,EAClB,mBAAmB,MAAM,KACxB,OAAO,CAAC,yBAAyB,CAsBnC,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;AAkNtH,0FAA0F;AAC1F,eAAO,MAAM,mBAAmB,GAAI,MAAM,gBAAgB,KAAG,IAE5D,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,QAAO,IAQtC,CAAA;AAID,eAAO,MAAM,uBAAuB,GAAU,gIAK3C;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,wCAAwC;QACxC,gBAAgB,CAAC,EAAE,MAAM,CAAA;QACzB,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"}
@@ -4,6 +4,7 @@ import { getSandboxOrigins } from "./sandbox-origins.js";
4
4
  import { SANDBOX_CHANNEL } from "./sandbox-protocol.js";
5
5
  import { normalizeError } from "@scalar/helpers/errors/normalize-error";
6
6
  import { isElectron } from "@scalar/helpers/general/is-electron";
7
+ import { isStreamingContentType } from "@scalar/helpers/http/is-streaming-content-type";
7
8
  //#region src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts
8
9
  /**
9
10
  * Host-side adapter for pre/post-request scripts.
@@ -14,10 +15,11 @@ import { isElectron } from "@scalar/helpers/general/is-electron";
14
15
  * This module therefore must NOT import `postman-sandbox`; it only serializes inputs, talks to the
15
16
  * iframe over `postMessage`, and applies the results back to the live store/request.
16
17
  */
17
- var toPostmanResponse = async (response) => {
18
- const buffer = await response.arrayBuffer();
18
+ var toPostmanResponse = async (response, responseDuration) => {
19
+ const buffer = isStreamingContentType(response.headers.get("content-type")) ? /* @__PURE__ */ new ArrayBuffer(0) : await response.arrayBuffer();
19
20
  const responseBytes = Array.from(new Uint8Array(buffer));
20
21
  return {
22
+ ...responseDuration === void 0 ? {} : { responseTime: responseDuration },
21
23
  code: response.status,
22
24
  status: response.statusText || String(response.status),
23
25
  header: Array.from(response.headers.entries()).map(([key, value]) => ({
@@ -207,9 +209,9 @@ var prewarmSandboxFrame = () => {
207
209
  ensureSandboxFrame().catch(() => {});
208
210
  };
209
211
  var nextExecutionId = 0;
210
- var executeInPostmanSandbox = async ({ script, type, context: { requestBuilder, response, variablesStore, scriptConsole }, onTestResultsUpdate }) => {
212
+ var executeInPostmanSandbox = async ({ script, type, context: { requestBuilder, response, responseDuration, variablesStore, scriptConsole }, onTestResultsUpdate }) => {
211
213
  const requestDefinition = requestBuilder ? createPostmanRequestFromFactory(requestBuilder).toJSON() : void 0;
212
- const responseDefinition = response ? await toPostmanResponse(response) : void 0;
214
+ const responseDefinition = response ? await toPostmanResponse(response, responseDuration) : void 0;
213
215
  const scopes = variablesStore ? getVariableScopesFromStore(variablesStore) : void 0;
214
216
  const request = {
215
217
  channel: SANDBOX_CHANNEL,
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox-adapter.js","names":[],"sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts"],"sourcesContent":["import { normalizeError } from '@scalar/helpers/errors/normalize-error'\nimport { 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 { getSandboxOrigins } from './sandbox-origins'\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 * 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\ntype SandboxFrame = {\n element: HTMLIFrameElement\n id: number\n url: string\n window: Window\n}\n\nlet framePromise: Promise<SandboxFrame> | undefined\nlet activeFrameId: number | undefined\nlet nextFrameId = 0\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\nconst clearActiveFrame = (id: number): void => {\n if (activeFrameId === id) {\n framePromise = undefined\n activeFrameId = undefined\n }\n}\n\nconst invalidateSandboxFrame = ({ element, id }: SandboxFrame): void => {\n clearActiveFrame(id)\n element.remove()\n}\n\nconst assertSandboxFrameLocation = (sandboxFrame: SandboxFrame): void => {\n const { element, url, window: frameWindow } = sandboxFrame\n if (!element.isConnected || !document.body.contains(element)) {\n invalidateSandboxFrame(sandboxFrame)\n throw new Error('Sandbox iframe was detached before script execution')\n }\n\n if (element.contentWindow !== frameWindow) {\n invalidateSandboxFrame(sandboxFrame)\n throw new Error('Sandbox iframe window changed before script execution')\n }\n\n let currentUrl: string\n\n try {\n currentUrl = frameWindow.location.href\n } catch (error) {\n invalidateSandboxFrame(sandboxFrame)\n const message = normalizeError(error).message\n throw new Error(`Could not verify sandbox iframe location before script execution: ${message}`)\n }\n\n if (currentUrl !== url) {\n invalidateSandboxFrame(sandboxFrame)\n throw new Error('Sandbox iframe navigated before script execution')\n }\n}\n\n/** Lazily creates a single hidden sandbox iframe and resolves once it reports readiness. */\nconst ensureSandboxFrame = (): Promise<SandboxFrame> => {\n if (framePromise) {\n return framePromise\n }\n\n framePromise = new Promise<SandboxFrame>((resolve, reject) => {\n const frameId = nextFrameId++\n activeFrameId = frameId\n const iframe = document.createElement('iframe')\n iframe.setAttribute('aria-hidden', 'true')\n iframe.style.display = 'none'\n const iframeUrl = sandboxFrameUrl()\n iframe.src = iframeUrl\n\n const { receive: expectedOrigin } = getSandboxOrigins()\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 clearActiveFrame(frameId)\n reject(new Error('Sandbox iframe has no content window'))\n return\n }\n resolve({\n element: iframe,\n id: frameId,\n url: iframeUrl,\n window: iframe.contentWindow,\n })\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 clearActiveFrame(frameId)\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 clearActiveFrame(frameId)\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((sandboxFrame) => {\n assertSandboxFrameLocation(sandboxFrame)\n\n const frame = sandboxFrame.window\n const { receive: expectedOrigin, send: targetOrigin } = getSandboxOrigins()\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 for web origins. `file://` is the only exception: Chromium does not\n // accept it as a target origin, so Electron file builds use `'*'` and rely on the source-window\n // checks above for receive-side isolation.\n frame.postMessage(request, targetOrigin)\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\n/**\n * Eagerly creates the sandbox iframe so the first script execution does not pay the cold-start\n * cost of loading `sandbox.html` and the `postman-sandbox` bundle (noticeably slow on Electron).\n *\n * This is a best-effort optimization: it only runs in a DOM context, reuses the same module-cached\n * frame {@link executeInPostmanSandbox} later awaits, and swallows failures so a warm-up problem can\n * never surface to the caller — the real execution path retries and reports errors on its own.\n *\n * Call this only when scripts are actually present; warming the sandbox for a request without\n * scripts would defeat the lazy creation that keeps script-free requests cheap.\n */\nexport const prewarmSandboxFrame = (): void => {\n if (typeof document === 'undefined') {\n return\n }\n\n ensureSandboxFrame().catch(() => {\n // Ignored on purpose: warm-up is opportunistic and the execution path handles real errors.\n })\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":";;;;;;;;;;;;;;;;AAyBA,IAAa,oBAAoB,OAAO,aAA2D;CAKjG,MAAM,SAAS,MAAM,SAAS,YAAY;CAC1C,MAAM,gBAAgB,MAAM,KAAK,IAAI,WAAW,MAAM,CAAC;CAEvD,OAAO;EACL,MAAM,SAAS;EACf,QAAQ,SAAS,cAAc,OAAO,SAAS,MAAM;EACrD,QAAQ,MAAM,KAAK,SAAS,QAAQ,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,YAAY;GAAE;GAAK;EAAM,EAAE;EACrF,QAAQ;GACN,MAAM;GACN,MAAM;EACR;CACF;AACF;;;;;;;;;;;;AAaA,IAAa,wBAAgC;CAC3C,IAAI,WAAW,GACb,OAAO,IAAI,IAAI,gBAAgB,SAAS,OAAO,CAAC,CAAC;CAEnD,OAAO,IAAI,IAAI,iBAAiB,OAAO,SAAS,MAAM,CAAC,CAAC;AAC1D;AAmBA,IAAI;AACJ,IAAI;AACJ,IAAI,cAAc;;;;;;;;;;;;;AAclB,IAAM,2BAA2B,KAAK;AAEtC,IAAM,oBAAoB,OAAqB;CAC7C,IAAI,kBAAkB,IAAI;EACxB,eAAe,KAAA;EACf,gBAAgB,KAAA;CAClB;AACF;AAEA,IAAM,0BAA0B,EAAE,SAAS,SAA6B;CACtE,iBAAiB,EAAE;CACnB,QAAQ,OAAO;AACjB;AAEA,IAAM,8BAA8B,iBAAqC;CACvE,MAAM,EAAE,SAAS,KAAK,QAAQ,gBAAgB;CAC9C,IAAI,CAAC,QAAQ,eAAe,CAAC,SAAS,KAAK,SAAS,OAAO,GAAG;EAC5D,uBAAuB,YAAY;EACnC,MAAM,IAAI,MAAM,qDAAqD;CACvE;CAEA,IAAI,QAAQ,kBAAkB,aAAa;EACzC,uBAAuB,YAAY;EACnC,MAAM,IAAI,MAAM,uDAAuD;CACzE;CAEA,IAAI;CAEJ,IAAI;EACF,aAAa,YAAY,SAAS;CACpC,SAAS,OAAO;EACd,uBAAuB,YAAY;EACnC,MAAM,UAAU,eAAe,KAAK,CAAC,CAAC;EACtC,MAAM,IAAI,MAAM,qEAAqE,SAAS;CAChG;CAEA,IAAI,eAAe,KAAK;EACtB,uBAAuB,YAAY;EACnC,MAAM,IAAI,MAAM,kDAAkD;CACpE;AACF;;AAGA,IAAM,2BAAkD;CACtD,IAAI,cACF,OAAO;CAGT,eAAe,IAAI,SAAuB,SAAS,WAAW;EAC5D,MAAM,UAAU;EAChB,gBAAgB;EAChB,MAAM,SAAS,SAAS,cAAc,QAAQ;EAC9C,OAAO,aAAa,eAAe,MAAM;EACzC,OAAO,MAAM,UAAU;EACvB,MAAM,YAAY,gBAAgB;EAClC,OAAO,MAAM;EAEb,MAAM,EAAE,SAAS,mBAAmB,kBAAkB;EAItD,IAAI;EACJ,MAAM,oBAAoB;GACxB,aAAa,cAAc;GAC3B,OAAO,oBAAoB,WAAW,OAAO;EAC/C;EAEA,MAAM,WAAW,UAAwB;GAEvC,IAAI,MAAM,WAAW,kBAAkB,MAAM,WAAW,OAAO,eAC7D;GAEF,MAAM,OAAO,MAAM;GACnB,IAAI,CAAC,QAAQ,KAAK,YAAA,6CAA+B,KAAK,SAAS,SAC7D;GAEF,YAAY;GACZ,IAAI,CAAC,OAAO,eAAe;IAEzB,OAAO,OAAO;IACd,iBAAiB,OAAO;IACxB,uBAAO,IAAI,MAAM,sCAAsC,CAAC;IACxD;GACF;GACA,QAAQ;IACN,SAAS;IACT,IAAI;IACJ,KAAK;IACL,QAAQ,OAAO;GACjB,CAAC;EACH;EAEA,OAAO,iBAAiB,eAAe;GACrC,YAAY;GAIZ,OAAO,OAAO;GACd,iBAAiB,OAAO;GACxB,uBAAO,IAAI,MAAM,0CAA0C,CAAC;EAC9D,CAAC;EAKD,iBAAiB,iBAAiB;GAChC,OAAO,oBAAoB,WAAW,OAAO;GAC7C,OAAO,OAAO;GACd,iBAAiB,OAAO;GACxB,uBAAO,IAAI,MAAM,kDAAkD,yBAAyB,GAAG,CAAC;EAClG,GAAG,wBAAwB;EAE3B,OAAO,iBAAiB,WAAW,OAAO;EAC1C,SAAS,KAAK,YAAY,MAAM;CAClC,CAAC;CAED,OAAO;AACT;;;;;;;;;AAUA,IAAM,+BAA+B,MAAS;AAE9C,IAAM,mBAAqC,SAAS,cAAc;CAChE,mBAAmB,CAAC,CACjB,MAAM,iBAAiB;EACtB,2BAA2B,YAAY;EAEvC,MAAM,QAAQ,aAAa;EAC3B,MAAM,EAAE,SAAS,gBAAgB,MAAM,iBAAiB,kBAAkB;EAK1E,MAAM,YAAY,iBAAiB;GACjC,OAAO,oBAAoB,WAAW,aAAa;GACnD,UAAU;IACR,SAAS;IACT,MAAM;IACN,IAAI,QAAQ;IACZ,OAAO,oCAAoC,6BAA6B;GAC1E,CAAC;EACH,GAAG,4BAA4B;EAE/B,MAAM,iBAAiB,UAAwB;GAI7C,IAAI,MAAM,WAAW,kBAAkB,MAAM,WAAW,OACtD;GAEF,MAAM,OAAO,MAAM;GACnB,IAAI,CAAC,QAAQ,KAAK,YAAA,6CAA+B,EAAE,QAAQ,SAAS,KAAK,OAAO,QAAQ,IACtF;GAEF,IAAI,KAAK,SAAS,QAAQ;IACxB,aAAa,SAAS;IACtB,OAAO,oBAAoB,WAAW,aAAa;GACrD;GACA,UAAU,IAAI;EAChB;EAEA,OAAO,iBAAiB,WAAW,aAAa;EAIhD,MAAM,YAAY,SAAS,YAAY;CACzC,CAAC,CAAC,CACD,OAAO,UAAmB;EACzB,UAAU;GACR,SAAS;GACT,MAAM;GACN,IAAI,QAAQ;GACZ,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC9D,CAAC;CACH,CAAC;AACL;AACA,IAAI,YAA8B;;;;;;;;;;;;AAkBlC,IAAa,4BAAkC;CAC7C,IAAI,OAAO,aAAa,aACtB;CAGF,mBAAmB,CAAC,CAAC,YAAY,CAEjC,CAAC;AACH;AAEA,IAAI,kBAAkB;AAEtB,IAAa,0BAA0B,OAAO,EAC5C,QACA,MACA,SAAS,EAAE,gBAAgB,UAAU,gBAAgB,iBACrD,0BAYyC;CAGzC,MAAM,oBAAoB,iBAAiB,gCAAgC,cAAc,CAAC,CAAC,OAAO,IAAI,KAAA;CACtG,MAAM,qBAAqB,WAAW,MAAM,kBAAkB,QAAQ,IAAI,KAAA;CAC1E,MAAM,SAAS,iBAAiB,2BAA2B,cAAc,IAAI,KAAA;CAE7E,MAAM,UAAiC;EACrC,SAAS;EACT,MAAM;EACN,IAAI,OAAO,iBAAiB;EAC5B,QAAQ,SAAS,gBAAgB,eAAe;EAChD;EACA;EACA,SAAS;EACT,UAAU;CACZ;CAEA,MAAM,IAAI,SAAe,YAAY;EACnC,MAAM,aAAa,SAAiC;GAClD,IAAI,KAAK,SAAS,gBAAgB;IAChC,sBAAsB,KAAK,OAAO;IAClC;GACF;GAEA,IAAI,KAAK,SAAS,WAAW;IAE1B,CADqB,cAAc,KAAK,UAAU,cAAc,IAAA,CACd,GAAG,KAAK,IAAI;IAC/D;GACF;GAEA,IAAI,KAAK,SAAS,QAAQ;IACxB,IAAI,kBAAkB,KAAK,WAAW;KACpC,MAAM,EAAE,OAAO,YAAY,SAAS,gBAAgB,KAAK;KACzD,IAAI,OACF,eAAe,kBAAkB,kBAAkB,KAAK,CAAC;KAE3D,IAAI,YACF,eAAe,yBAAyB,kBAAkB,UAAU,CAAC;KAEvE,IAAI,SACF,eAAe,aAAa,kBAAkB,OAAO,CAAC;KAExD,IAAI,aACF,eAAe,iBAAiB,kBAAkB,WAAW,CAAC;IAElE;IAEA,IAAI,CAAC,KAAK,SAAS,SAAS,iBAAiB,kBAAkB,KAAK,YAAY,KAAA,GAC9E,wCAAwC,KAAK,SAAS,cAAc;IAGtE,QAAQ;GACV;EACF;EAEA,UAAU,SAAS,SAAS;CAC9B,CAAC;CAED,OAAO;AACT"}
1
+ {"version":3,"file":"sandbox-adapter.js","names":[],"sources":["../../../../src/libs/execute-scripts/postman-adapter/sandbox-adapter.ts"],"sourcesContent":["import { normalizeError } from '@scalar/helpers/errors/normalize-error'\nimport { isElectron } from '@scalar/helpers/general/is-electron'\nimport { isStreamingContentType } from '@scalar/helpers/http/is-streaming-content-type'\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 { getSandboxOrigins } from './sandbox-origins'\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 (\n response: Response,\n responseDuration?: number,\n): 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 // Streaming responses may never finish. Keep post-response scripts limited to their metadata,\n // as before hooks moved ahead of response processing, so scripts cannot block the viewer.\n const buffer = isStreamingContentType(response.headers.get('content-type'))\n ? new ArrayBuffer(0)\n : await response.arrayBuffer()\n const responseBytes = Array.from(new Uint8Array(buffer))\n\n return {\n ...(responseDuration === undefined ? {} : { responseTime: responseDuration }),\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 * 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\ntype SandboxFrame = {\n element: HTMLIFrameElement\n id: number\n url: string\n window: Window\n}\n\nlet framePromise: Promise<SandboxFrame> | undefined\nlet activeFrameId: number | undefined\nlet nextFrameId = 0\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\nconst clearActiveFrame = (id: number): void => {\n if (activeFrameId === id) {\n framePromise = undefined\n activeFrameId = undefined\n }\n}\n\nconst invalidateSandboxFrame = ({ element, id }: SandboxFrame): void => {\n clearActiveFrame(id)\n element.remove()\n}\n\nconst assertSandboxFrameLocation = (sandboxFrame: SandboxFrame): void => {\n const { element, url, window: frameWindow } = sandboxFrame\n if (!element.isConnected || !document.body.contains(element)) {\n invalidateSandboxFrame(sandboxFrame)\n throw new Error('Sandbox iframe was detached before script execution')\n }\n\n if (element.contentWindow !== frameWindow) {\n invalidateSandboxFrame(sandboxFrame)\n throw new Error('Sandbox iframe window changed before script execution')\n }\n\n let currentUrl: string\n\n try {\n currentUrl = frameWindow.location.href\n } catch (error) {\n invalidateSandboxFrame(sandboxFrame)\n const message = normalizeError(error).message\n throw new Error(`Could not verify sandbox iframe location before script execution: ${message}`)\n }\n\n if (currentUrl !== url) {\n invalidateSandboxFrame(sandboxFrame)\n throw new Error('Sandbox iframe navigated before script execution')\n }\n}\n\n/** Lazily creates a single hidden sandbox iframe and resolves once it reports readiness. */\nconst ensureSandboxFrame = (): Promise<SandboxFrame> => {\n if (framePromise) {\n return framePromise\n }\n\n framePromise = new Promise<SandboxFrame>((resolve, reject) => {\n const frameId = nextFrameId++\n activeFrameId = frameId\n const iframe = document.createElement('iframe')\n iframe.setAttribute('aria-hidden', 'true')\n iframe.style.display = 'none'\n const iframeUrl = sandboxFrameUrl()\n iframe.src = iframeUrl\n\n const { receive: expectedOrigin } = getSandboxOrigins()\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 clearActiveFrame(frameId)\n reject(new Error('Sandbox iframe has no content window'))\n return\n }\n resolve({\n element: iframe,\n id: frameId,\n url: iframeUrl,\n window: iframe.contentWindow,\n })\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 clearActiveFrame(frameId)\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 clearActiveFrame(frameId)\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((sandboxFrame) => {\n assertSandboxFrameLocation(sandboxFrame)\n\n const frame = sandboxFrame.window\n const { receive: expectedOrigin, send: targetOrigin } = getSandboxOrigins()\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 for web origins. `file://` is the only exception: Chromium does not\n // accept it as a target origin, so Electron file builds use `'*'` and rely on the source-window\n // checks above for receive-side isolation.\n frame.postMessage(request, targetOrigin)\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\n/**\n * Eagerly creates the sandbox iframe so the first script execution does not pay the cold-start\n * cost of loading `sandbox.html` and the `postman-sandbox` bundle (noticeably slow on Electron).\n *\n * This is a best-effort optimization: it only runs in a DOM context, reuses the same module-cached\n * frame {@link executeInPostmanSandbox} later awaits, and swallows failures so a warm-up problem can\n * never surface to the caller — the real execution path retries and reports errors on its own.\n *\n * Call this only when scripts are actually present; warming the sandbox for a request without\n * scripts would defeat the lazy creation that keeps script-free requests cheap.\n */\nexport const prewarmSandboxFrame = (): void => {\n if (typeof document === 'undefined') {\n return\n }\n\n ensureSandboxFrame().catch(() => {\n // Ignored on purpose: warm-up is opportunistic and the execution path handles real errors.\n })\n}\n\nlet nextExecutionId = 0\n\nexport const executeInPostmanSandbox = async ({\n script,\n type,\n context: { requestBuilder, response, responseDuration, 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 /** Request duration in milliseconds. */\n responseDuration?: number\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, responseDuration) : 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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"}
@@ -16,6 +16,8 @@ export declare const SANDBOX_CHANNEL = "scalar-pre-post-request-scripts-sandbox"
16
16
  export type PostmanRequestDefinition = unknown;
17
17
  /** Plain response shape consumed by `pm.response` inside the sandbox. */
18
18
  export type PostmanResponseDefinition = {
19
+ /** Request duration in milliseconds, exposed as pm.response.responseTime. */
20
+ responseTime?: number;
19
21
  code: number;
20
22
  status: string;
21
23
  header: {
@@ -1 +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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"}
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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"}
@@ -1 +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
+ {"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 /** Request duration in milliseconds, exposed as pm.response.responseTime. */\n responseTime?: number\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,4 +1,4 @@
1
- import type { OperationObject } from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document';
1
+ import type { OperationObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
2
2
  type __VLS_Props = {
3
3
  operation?: Pick<OperationObject, 'x-pre-request' | 'x-post-response'>;
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ScriptsSection.vue.d.ts","sourceRoot":"","sources":["../../../../src/plugins/request-scripts/components/ScriptsSection.vue"],"names":[],"mappings":"AAiFA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAA;AAOnG,KAAK,WAAW,GAAG;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,eAAe,GAAG,iBAAiB,CAAC,CAAA;CACvE,CAAC;AAgLF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
1
+ {"version":3,"file":"ScriptsSection.vue.d.ts","sourceRoot":"","sources":["../../../../src/plugins/request-scripts/components/ScriptsSection.vue"],"names":[],"mappings":"AA6EA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8DAA8D,CAAA;AAOnG,KAAK,WAAW,GAAG;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,eAAe,GAAG,iBAAiB,CAAC,CAAA;CACvE,CAAC;AA4KF,QAAA,MAAM,YAAY;;;;kFAGhB,CAAC;wBACkB,OAAO,YAAY;AAAxC,wBAAyC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ScriptsSection.vue.js","names":[],"sources":["../../../../src/plugins/request-scripts/components/ScriptsSection.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { OperationObject } from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed } from 'vue'\n\nimport ScriptEditor from '@/components/ScriptEditor.vue'\nimport { ViewLayoutCollapse } from '@/components/ViewLayout'\nimport ExampleScripts from '@/plugins/request-scripts/components/ExampleScripts.vue'\n\nconst { operation } = defineProps<{\n operation?: Pick<OperationObject, 'x-pre-request' | 'x-post-response'>\n}>()\n\nconst emit = defineEmits<{\n (e: 'operation:update:extension', payload: any): void\n}>()\n\nconst preRequestScript = computed(\n () => (operation?.['x-pre-request'] as string) ?? '',\n)\nconst postResponseScript = computed(\n () => (operation?.['x-post-response'] as string) ?? '',\n)\n\nconst hasAnyScript = computed(\n () =>\n preRequestScript.value.length > 0 || postResponseScript.value.length > 0,\n)\n\nconst updatePreRequestScript = (value: string) => {\n emit('operation:update:extension', { 'x-pre-request': value })\n}\nconst updatePostResponseScript = (value: string) => {\n emit('operation:update:extension', { 'x-post-response': value })\n}\n</script>\n\n<template>\n <div class=\"w-full\">\n <ViewLayoutCollapse\n class=\"w-full\"\n :defaultOpen=\"true\">\n <template #title>Scripts</template>\n <template #suffix>\n <!-- Show an indicator whether we have a script -->\n <div class=\"mr-2\">\n <div\n v-if=\"hasAnyScript\"\n class=\"bg-green h-2 w-2 rounded-full\" />\n </div>\n </template>\n\n <!-- Pre-request -->\n <div\n class=\"text-c-3 flex min-h-8 items-center border-y px-3 py-2 text-sm\">\n Pre-request — run before the request is sent (e.g. set variables,\n headers).\n </div>\n <ScriptEditor\n :modelValue=\"preRequestScript\"\n @update:modelValue=\"updatePreRequestScript\" />\n\n <!-- Post-response -->\n <div\n class=\"text-c-3 flex min-h-8 items-center border-y px-3 py-2 text-sm\">\n Post-response — run after the response is received (e.g. tests,\n assertions).\n </div>\n <ScriptEditor\n :modelValue=\"postResponseScript\"\n @update:modelValue=\"updatePostResponseScript\" />\n <div class=\"border-y p-3\">\n <ExampleScripts\n :modelValue=\"postResponseScript\"\n @update:modelValue=\"updatePostResponseScript\" />\n </div>\n </ViewLayoutCollapse>\n </div>\n</template>\n"],"mappings":""}
1
+ {"version":3,"file":"ScriptsSection.vue.js","names":[],"sources":["../../../../src/plugins/request-scripts/components/ScriptsSection.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { OperationObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { computed } from 'vue'\n\nimport ScriptEditor from '@/components/ScriptEditor.vue'\nimport { ViewLayoutCollapse } from '@/components/ViewLayout'\nimport ExampleScripts from '@/plugins/request-scripts/components/ExampleScripts.vue'\n\nconst { operation } = defineProps<{\n operation?: Pick<OperationObject, 'x-pre-request' | 'x-post-response'>\n}>()\n\nconst emit = defineEmits<{\n (e: 'operation:update:extension', payload: any): void\n}>()\n\nconst preRequestScript = computed(() => operation?.['x-pre-request'] ?? '')\nconst postResponseScript = computed(() => operation?.['x-post-response'] ?? '')\n\nconst hasAnyScript = computed(\n () =>\n preRequestScript.value.length > 0 || postResponseScript.value.length > 0,\n)\n\nconst updatePreRequestScript = (value: string) => {\n emit('operation:update:extension', { 'x-pre-request': value })\n}\nconst updatePostResponseScript = (value: string) => {\n emit('operation:update:extension', { 'x-post-response': value })\n}\n</script>\n\n<template>\n <div class=\"w-full\">\n <ViewLayoutCollapse\n class=\"w-full\"\n :defaultOpen=\"true\">\n <template #title>Scripts</template>\n <template #suffix>\n <!-- Show an indicator whether we have a script -->\n <div class=\"mr-2\">\n <div\n v-if=\"hasAnyScript\"\n class=\"bg-green h-2 w-2 rounded-full\" />\n </div>\n </template>\n\n <!-- Pre-request -->\n <div\n class=\"text-c-3 flex min-h-8 items-center border-y px-3 py-2 text-sm\">\n Pre-request — run before the request is sent (e.g. set variables,\n headers).\n </div>\n <ScriptEditor\n :modelValue=\"preRequestScript\"\n @update:modelValue=\"updatePreRequestScript\" />\n\n <!-- Post-response -->\n <div\n class=\"text-c-3 flex min-h-8 items-center border-y px-3 py-2 text-sm\">\n Post-response — run after the response is received (e.g. tests,\n assertions).\n </div>\n <ScriptEditor\n :modelValue=\"postResponseScript\"\n @update:modelValue=\"updatePostResponseScript\" />\n <div class=\"border-y p-3\">\n <ExampleScripts\n :modelValue=\"postResponseScript\"\n @update:modelValue=\"updatePostResponseScript\" />\n </div>\n </ViewLayoutCollapse>\n </div>\n</template>\n"],"mappings":""}
@@ -1 +1 @@
1
- {"version":3,"file":"ScriptsSection.vue.script.js","names":[],"sources":["../../../../src/plugins/request-scripts/components/ScriptsSection.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { OperationObject } from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document'\nimport { computed } from 'vue'\n\nimport ScriptEditor from '@/components/ScriptEditor.vue'\nimport { ViewLayoutCollapse } from '@/components/ViewLayout'\nimport ExampleScripts from '@/plugins/request-scripts/components/ExampleScripts.vue'\n\nconst { operation } = defineProps<{\n operation?: Pick<OperationObject, 'x-pre-request' | 'x-post-response'>\n}>()\n\nconst emit = defineEmits<{\n (e: 'operation:update:extension', payload: any): void\n}>()\n\nconst preRequestScript = computed(\n () => (operation?.['x-pre-request'] as string) ?? '',\n)\nconst postResponseScript = computed(\n () => (operation?.['x-post-response'] as string) ?? '',\n)\n\nconst hasAnyScript = computed(\n () =>\n preRequestScript.value.length > 0 || postResponseScript.value.length > 0,\n)\n\nconst updatePreRequestScript = (value: string) => {\n emit('operation:update:extension', { 'x-pre-request': value })\n}\nconst updatePostResponseScript = (value: string) => {\n emit('operation:update:extension', { 'x-post-response': value })\n}\n</script>\n\n<template>\n <div class=\"w-full\">\n <ViewLayoutCollapse\n class=\"w-full\"\n :defaultOpen=\"true\">\n <template #title>Scripts</template>\n <template #suffix>\n <!-- Show an indicator whether we have a script -->\n <div class=\"mr-2\">\n <div\n v-if=\"hasAnyScript\"\n class=\"bg-green h-2 w-2 rounded-full\" />\n </div>\n </template>\n\n <!-- Pre-request -->\n <div\n class=\"text-c-3 flex min-h-8 items-center border-y px-3 py-2 text-sm\">\n Pre-request — run before the request is sent (e.g. set variables,\n headers).\n </div>\n <ScriptEditor\n :modelValue=\"preRequestScript\"\n @update:modelValue=\"updatePreRequestScript\" />\n\n <!-- Post-response -->\n <div\n class=\"text-c-3 flex min-h-8 items-center border-y px-3 py-2 text-sm\">\n Post-response — run after the response is received (e.g. tests,\n assertions).\n </div>\n <ScriptEditor\n :modelValue=\"postResponseScript\"\n @update:modelValue=\"updatePostResponseScript\" />\n <div class=\"border-y p-3\">\n <ExampleScripts\n :modelValue=\"postResponseScript\"\n @update:modelValue=\"updatePostResponseScript\" />\n </div>\n </ViewLayoutCollapse>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;EAYA,MAAM,OAAO;EAIb,MAAM,mBAAmB,eAChB,QAAA,YAAY,oBAA+B,EACpD;EACA,MAAM,qBAAqB,eAClB,QAAA,YAAY,sBAAiC,EACtD;EAEA,MAAM,eAAe,eAEjB,iBAAiB,MAAM,SAAS,KAAK,mBAAmB,MAAM,SAAS,CAC3E;EAEA,MAAM,0BAA0B,UAAkB;GAChD,KAAK,8BAA8B,EAAE,iBAAiB,MAAM,CAAC;EAC/D;EACA,MAAM,4BAA4B,UAAkB;GAClD,KAAK,8BAA8B,EAAE,mBAAmB,MAAM,CAAC;EACjE;;uBAIE,mBAuCM,OAvCN,YAuCM,CAtCJ,YAqCqB,MAAA,0BAAA,GAAA;IApCnB,OAAM;IACL,aAAa;;IACH,OAAK,cAAQ,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,gBAAP,WAAO,EAAA,CAAA,EAAA,CAAA;IACb,QAAM,cAMT,CAJN,mBAIM,OAJN,YAIM,CAFI,aAAA,SAAA,UAAA,GADR,mBAE0C,OAF1C,UAE0C,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,CAAA,CAAA;2BASxC;+BAJN,mBAIM,OAAA,EAHJ,OAAM,gEAA+D,GAAC,iFAGxE,EAAA;KACA,YAEgD,sBAAA;MAD7C,YAAY,iBAAA;MACZ,uBAAmB;;+BAGtB,mBAIM,OAAA,EAHJ,OAAM,gEAA+D,GAAC,kFAGxE,EAAA;KACA,YAEkD,sBAAA;MAD/C,YAAY,mBAAA;MACZ,uBAAmB;;KACtB,mBAIM,OAJN,YAIM,CAHJ,YAEkD,wBAAA;MAD/C,YAAY,mBAAA;MACZ,uBAAmB"}
1
+ {"version":3,"file":"ScriptsSection.vue.script.js","names":[],"sources":["../../../../src/plugins/request-scripts/components/ScriptsSection.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { OperationObject } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document'\nimport { computed } from 'vue'\n\nimport ScriptEditor from '@/components/ScriptEditor.vue'\nimport { ViewLayoutCollapse } from '@/components/ViewLayout'\nimport ExampleScripts from '@/plugins/request-scripts/components/ExampleScripts.vue'\n\nconst { operation } = defineProps<{\n operation?: Pick<OperationObject, 'x-pre-request' | 'x-post-response'>\n}>()\n\nconst emit = defineEmits<{\n (e: 'operation:update:extension', payload: any): void\n}>()\n\nconst preRequestScript = computed(() => operation?.['x-pre-request'] ?? '')\nconst postResponseScript = computed(() => operation?.['x-post-response'] ?? '')\n\nconst hasAnyScript = computed(\n () =>\n preRequestScript.value.length > 0 || postResponseScript.value.length > 0,\n)\n\nconst updatePreRequestScript = (value: string) => {\n emit('operation:update:extension', { 'x-pre-request': value })\n}\nconst updatePostResponseScript = (value: string) => {\n emit('operation:update:extension', { 'x-post-response': value })\n}\n</script>\n\n<template>\n <div class=\"w-full\">\n <ViewLayoutCollapse\n class=\"w-full\"\n :defaultOpen=\"true\">\n <template #title>Scripts</template>\n <template #suffix>\n <!-- Show an indicator whether we have a script -->\n <div class=\"mr-2\">\n <div\n v-if=\"hasAnyScript\"\n class=\"bg-green h-2 w-2 rounded-full\" />\n </div>\n </template>\n\n <!-- Pre-request -->\n <div\n class=\"text-c-3 flex min-h-8 items-center border-y px-3 py-2 text-sm\">\n Pre-request — run before the request is sent (e.g. set variables,\n headers).\n </div>\n <ScriptEditor\n :modelValue=\"preRequestScript\"\n @update:modelValue=\"updatePreRequestScript\" />\n\n <!-- Post-response -->\n <div\n class=\"text-c-3 flex min-h-8 items-center border-y px-3 py-2 text-sm\">\n Post-response — run after the response is received (e.g. tests,\n assertions).\n </div>\n <ScriptEditor\n :modelValue=\"postResponseScript\"\n @update:modelValue=\"updatePostResponseScript\" />\n <div class=\"border-y p-3\">\n <ExampleScripts\n :modelValue=\"postResponseScript\"\n @update:modelValue=\"updatePostResponseScript\" />\n </div>\n </ViewLayoutCollapse>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;EAYA,MAAM,OAAO;EAIb,MAAM,mBAAmB,eAAe,QAAA,YAAY,oBAAoB,EAAE;EAC1E,MAAM,qBAAqB,eAAe,QAAA,YAAY,sBAAsB,EAAE;EAE9E,MAAM,eAAe,eAEjB,iBAAiB,MAAM,SAAS,KAAK,mBAAmB,MAAM,SAAS,CAC3E;EAEA,MAAM,0BAA0B,UAAkB;GAChD,KAAK,8BAA8B,EAAE,iBAAiB,MAAM,CAAC;EAC/D;EACA,MAAM,4BAA4B,UAAkB;GAClD,KAAK,8BAA8B,EAAE,mBAAmB,MAAM,CAAC;EACjE;;uBAIE,mBAuCM,OAvCN,YAuCM,CAtCJ,YAqCqB,MAAA,0BAAA,GAAA;IApCnB,OAAM;IACL,aAAa;;IACH,OAAK,cAAQ,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,gBAAP,WAAO,EAAA,CAAA,EAAA,CAAA;IACb,QAAM,cAMT,CAJN,mBAIM,OAJN,YAIM,CAFI,aAAA,SAAA,UAAA,GADR,mBAE0C,OAF1C,UAE0C,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,CAAA,CAAA;2BASxC;+BAJN,mBAIM,OAAA,EAHJ,OAAM,gEAA+D,GAAC,iFAGxE,EAAA;KACA,YAEgD,sBAAA;MAD7C,YAAY,iBAAA;MACZ,uBAAmB;;+BAGtB,mBAIM,OAAA,EAHJ,OAAM,gEAA+D,GAAC,kFAGxE,EAAA;KACA,YAEkD,sBAAA;MAD/C,YAAY,mBAAA;MACZ,uBAAmB;;KACtB,mBAIM,OAJN,YAIM,CAHJ,YAEkD,wBAAA;MAD/C,YAAY,mBAAA;MACZ,uBAAmB"}
@@ -1 +1 @@
1
- {"version":3,"file":"request-scripts-plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/request-scripts/request-scripts-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAc7D;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,QAAO,YAuDvC,CAAA"}
1
+ {"version":3,"file":"request-scripts-plugin.d.ts","sourceRoot":"","sources":["../../../src/plugins/request-scripts/request-scripts-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAc7D;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,QAAO,YAwDvC,CAAA"}
@@ -38,12 +38,13 @@ var requestScriptsPlugin = () => {
38
38
  onTestResultsUpdate: (newResults) => results.value = [...newResults]
39
39
  });
40
40
  },
41
- responseReceived: async ({ requestBuilder, response, operation, document, variablesStore }) => {
41
+ responseReceived: async ({ requestBuilder, response, responseDuration, operation, document, variablesStore }) => {
42
42
  const script = getScript(document["x-post-response"], operation["x-post-response"]);
43
43
  const preRequestResults = [...results.value];
44
44
  await executePostResponseScript(script, {
45
45
  requestBuilder,
46
46
  response,
47
+ responseDuration,
47
48
  onTestResultsUpdate: (postResponseResults) => results.value = [...preRequestResults, ...postResponseResults],
48
49
  variablesStore
49
50
  });
@@ -1 +1 @@
1
- {"version":3,"file":"request-scripts-plugin.js","names":[],"sources":["../../../src/plugins/request-scripts/request-scripts-plugin.ts"],"sourcesContent":["import type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport { ref } from 'vue'\n\nimport { getScript } from '@/helpers/get-script'\nimport {\n type TestResult,\n executePostResponseScript,\n executePreRequestScript,\n prewarmSandboxFrame,\n} from '@/libs/execute-scripts'\nimport { TestResults } from '@/plugins/request-scripts/components/TestResults'\n\nimport ScriptsSection from './components/ScriptsSection.vue'\n\n/**\n * Unified request scripts plugin for client V2. Provides a single \"Scripts\"\n * section with:\n * - Pre-request script editor (x-pre-request) — runs before each request\n * - Post-response script editor (x-post-response) — runs after each response\n * - Test results from post-response assertions, shown in the response section\n *\n * Hooks: beforeRequest runs pre-request scripts and resets results;\n * responseReceived runs post-response scripts and updates test results.\n */\nexport const requestScriptsPlugin = (): ClientPlugin => {\n const results = ref<TestResult[]>([])\n\n return {\n components: {\n request: {\n component: ScriptsSection,\n },\n response: {\n component: TestResults,\n additionalProps: { results },\n },\n },\n\n hooks: {\n onRequestMount: ({ document, operation }) => {\n // Only warm up the sandbox when this operation actually has scripts. Requests without\n // scripts never touch the sandbox, so creating the iframe for them would waste work.\n const script = getScript(\n document['x-pre-request'],\n operation['x-pre-request'],\n document['x-post-response'],\n operation['x-post-response'],\n )\n\n if (script) {\n prewarmSandboxFrame()\n }\n },\n\n beforeRequest: async ({ requestBuilder, document, operation, variablesStore }) => {\n // Reset test results when a new request is sent\n results.value = []\n const script = getScript(document['x-pre-request'], operation['x-pre-request'])\n await executePreRequestScript(script, {\n requestBuilder,\n variablesStore,\n onTestResultsUpdate: (newResults) => (results.value = [...newResults]),\n })\n },\n\n responseReceived: async ({ requestBuilder, response, operation, document, variablesStore }) => {\n const script = getScript(document['x-post-response'], operation['x-post-response'])\n // Snapshot pre-request results before running post-response script\n const preRequestResults = [...results.value]\n await executePostResponseScript(script, {\n requestBuilder,\n response,\n onTestResultsUpdate: (postResponseResults) =>\n (results.value = [...preRequestResults, ...postResponseResults]),\n variablesStore,\n })\n },\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,IAAa,6BAA2C;CACtD,MAAM,UAAU,IAAkB,CAAC,CAAC;CAEpC,OAAO;EACL,YAAY;GACV,SAAS,EACP,WAAW,uBACb;GACA,UAAU;IACR,WAAW;IACX,iBAAiB,EAAE,QAAQ;GAC7B;EACF;EAEA,OAAO;GACL,iBAAiB,EAAE,UAAU,gBAAgB;IAU3C,IAPe,UACb,SAAS,kBACT,UAAU,kBACV,SAAS,oBACT,UAAU,kBAGR,GACF,oBAAoB;GAExB;GAEA,eAAe,OAAO,EAAE,gBAAgB,UAAU,WAAW,qBAAqB;IAEhF,QAAQ,QAAQ,CAAC;IAEjB,MAAM,wBADS,UAAU,SAAS,kBAAkB,UAAU,gBAChC,GAAQ;KACpC;KACA;KACA,sBAAsB,eAAgB,QAAQ,QAAQ,CAAC,GAAG,UAAU;IACtE,CAAC;GACH;GAEA,kBAAkB,OAAO,EAAE,gBAAgB,UAAU,WAAW,UAAU,qBAAqB;IAC7F,MAAM,SAAS,UAAU,SAAS,oBAAoB,UAAU,kBAAkB;IAElF,MAAM,oBAAoB,CAAC,GAAG,QAAQ,KAAK;IAC3C,MAAM,0BAA0B,QAAQ;KACtC;KACA;KACA,sBAAsB,wBACnB,QAAQ,QAAQ,CAAC,GAAG,mBAAmB,GAAG,mBAAmB;KAChE;IACF,CAAC;GACH;EACF;CACF;AACF"}
1
+ {"version":3,"file":"request-scripts-plugin.js","names":[],"sources":["../../../src/plugins/request-scripts/request-scripts-plugin.ts"],"sourcesContent":["import type { ClientPlugin } from '@scalar/oas-utils/helpers'\nimport { ref } from 'vue'\n\nimport { getScript } from '@/helpers/get-script'\nimport {\n type TestResult,\n executePostResponseScript,\n executePreRequestScript,\n prewarmSandboxFrame,\n} from '@/libs/execute-scripts'\nimport { TestResults } from '@/plugins/request-scripts/components/TestResults'\n\nimport ScriptsSection from './components/ScriptsSection.vue'\n\n/**\n * Unified request scripts plugin for client V2. Provides a single \"Scripts\"\n * section with:\n * - Pre-request script editor (x-pre-request) — runs before each request\n * - Post-response script editor (x-post-response) — runs after each response\n * - Test results from post-response assertions, shown in the response section\n *\n * Hooks: beforeRequest runs pre-request scripts and resets results;\n * responseReceived runs post-response scripts and updates test results.\n */\nexport const requestScriptsPlugin = (): ClientPlugin => {\n const results = ref<TestResult[]>([])\n\n return {\n components: {\n request: {\n component: ScriptsSection,\n },\n response: {\n component: TestResults,\n additionalProps: { results },\n },\n },\n\n hooks: {\n onRequestMount: ({ document, operation }) => {\n // Only warm up the sandbox when this operation actually has scripts. Requests without\n // scripts never touch the sandbox, so creating the iframe for them would waste work.\n const script = getScript(\n document['x-pre-request'],\n operation['x-pre-request'],\n document['x-post-response'],\n operation['x-post-response'],\n )\n\n if (script) {\n prewarmSandboxFrame()\n }\n },\n\n beforeRequest: async ({ requestBuilder, document, operation, variablesStore }) => {\n // Reset test results when a new request is sent\n results.value = []\n const script = getScript(document['x-pre-request'], operation['x-pre-request'])\n await executePreRequestScript(script, {\n requestBuilder,\n variablesStore,\n onTestResultsUpdate: (newResults) => (results.value = [...newResults]),\n })\n },\n\n responseReceived: async ({ requestBuilder, response, responseDuration, operation, document, variablesStore }) => {\n const script = getScript(document['x-post-response'], operation['x-post-response'])\n // Snapshot pre-request results before running post-response script\n const preRequestResults = [...results.value]\n await executePostResponseScript(script, {\n requestBuilder,\n response,\n responseDuration,\n onTestResultsUpdate: (postResponseResults) =>\n (results.value = [...preRequestResults, ...postResponseResults]),\n variablesStore,\n })\n },\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,IAAa,6BAA2C;CACtD,MAAM,UAAU,IAAkB,CAAC,CAAC;CAEpC,OAAO;EACL,YAAY;GACV,SAAS,EACP,WAAW,uBACb;GACA,UAAU;IACR,WAAW;IACX,iBAAiB,EAAE,QAAQ;GAC7B;EACF;EAEA,OAAO;GACL,iBAAiB,EAAE,UAAU,gBAAgB;IAU3C,IAPe,UACb,SAAS,kBACT,UAAU,kBACV,SAAS,oBACT,UAAU,kBAGR,GACF,oBAAoB;GAExB;GAEA,eAAe,OAAO,EAAE,gBAAgB,UAAU,WAAW,qBAAqB;IAEhF,QAAQ,QAAQ,CAAC;IAEjB,MAAM,wBADS,UAAU,SAAS,kBAAkB,UAAU,gBAChC,GAAQ;KACpC;KACA;KACA,sBAAsB,eAAgB,QAAQ,QAAQ,CAAC,GAAG,UAAU;IACtE,CAAC;GACH;GAEA,kBAAkB,OAAO,EAAE,gBAAgB,UAAU,kBAAkB,WAAW,UAAU,qBAAqB;IAC/G,MAAM,SAAS,UAAU,SAAS,oBAAoB,UAAU,kBAAkB;IAElF,MAAM,oBAAoB,CAAC,GAAG,QAAQ,KAAK;IAC3C,MAAM,0BAA0B,QAAQ;KACtC;KACA;KACA;KACA,sBAAsB,wBACnB,QAAQ,QAAQ,CAAC,GAAG,mBAAmB,GAAG,mBAAmB;KAChE;IACF,CAAC;GACH;EACF;CACF;AACF"}
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "post-response scripts",
20
20
  "api client"
21
21
  ],
22
- "version": "0.4.41",
22
+ "version": "0.4.43",
23
23
  "engines": {
24
24
  "node": ">=22"
25
25
  },
@@ -59,12 +59,12 @@
59
59
  "postman-collection": "^5.3.0",
60
60
  "postman-sandbox": "^6.6.1",
61
61
  "vue": "^3.5.40",
62
- "@scalar/components": "0.29.0",
63
- "@scalar/oas-utils": "0.19.15",
64
- "@scalar/helpers": "0.11.2",
62
+ "@scalar/components": "0.29.2",
63
+ "@scalar/oas-utils": "0.20.0",
65
64
  "@scalar/use-codemirror": "0.14.15",
66
- "@scalar/use-hooks": "0.4.10",
67
- "@scalar/workspace-store": "0.59.0"
65
+ "@scalar/workspace-store": "0.61.0",
66
+ "@scalar/helpers": "0.12.0",
67
+ "@scalar/use-hooks": "0.4.12"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@vitejs/plugin-vue": "^6.0.8",