@slicemachine/manager 0.1.1-dev-plugins-m3.3 → 0.1.1-dev-plugins-m3.5

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.
@@ -61,8 +61,8 @@ function _interopNamespaceDefault(e) {
61
61
  return Object.freeze(n);
62
62
  }
63
63
  const t__namespace = /* @__PURE__ */ _interopNamespaceDefault(t);
64
- const SLICE_SIMULATOR_WAIT_FOR_TIMEOUT = 1e4;
65
64
  const SLICE_SIMULATOR_WAIT_FOR_SELECTOR = "#__iframe-ready";
65
+ const SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT = 1e4;
66
66
  const SLICE_SIMULATOR_SCREENSHOT_SELECTOR = "#__iframe-renderer";
67
67
  const DEFAULT_SCREENSHOT_VIEWPORT = {
68
68
  width: 1200,
@@ -165,9 +165,9 @@ class ScreenshotsManager extends BaseManager.BaseManager {
165
165
  }
166
166
  const page = await this._browserContext.newPage();
167
167
  page.setViewport(viewport);
168
- await page.goto(url.toString(), { waitUntil: "load" });
168
+ await page.goto(url.toString(), { waitUntil: ["load", "networkidle0"] });
169
169
  await page.waitForSelector(SLICE_SIMULATOR_WAIT_FOR_SELECTOR, {
170
- timeout: SLICE_SIMULATOR_WAIT_FOR_TIMEOUT
170
+ timeout: SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT
171
171
  });
172
172
  const element = await page.$(SLICE_SIMULATOR_SCREENSHOT_SELECTOR);
173
173
  if (!element) {
@@ -218,7 +218,9 @@ class ScreenshotsManager extends BaseManager.BaseManager {
218
218
  }
219
219
  async deleteScreenshotFolder(args) {
220
220
  const res = await this._fetch({
221
- body: { sliceId: args.sliceID },
221
+ // We're sending `args.sliceID` as `sliceName` because it's inconsistently
222
+ // named in the ACL Provider API.
223
+ body: { sliceName: args.sliceID },
222
224
  method: "POST",
223
225
  url: new URL("delete-folder", API_ENDPOINTS.API_ENDPOINTS.AwsAclProvider)
224
226
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ScreenshotsManager.cjs","sources":["../../../../src/managers/screenshots/ScreenshotsManager.ts"],"sourcesContent":["import * as t from \"io-ts\";\nimport { fileTypeFromBuffer } from \"file-type\";\nimport fetch, { FormData, Blob, Response } from \"node-fetch\";\n// puppeteer is lazy-loaded in captureSliceSimulatorScreenshot\nimport type { BrowserContext, Viewport } from \"puppeteer\";\n\nimport { checkIsURLAccessible } from \"../../lib/checkIsURLAccessible\";\nimport { createContentDigest } from \"../../lib/createContentDigest\";\nimport { decode } from \"../../lib/decode\";\n\nimport { S3ACL } from \"../../types\";\nimport { SLICE_MACHINE_USER_AGENT } from \"../../constants/SLICE_MACHINE_USER_AGENT\";\nimport { API_ENDPOINTS } from \"../../constants/API_ENDPOINTS\";\nimport { InternalError } from \"../../errors\";\n\nimport { BaseManager } from \"../BaseManager\";\n\nconst SLICE_SIMULATOR_WAIT_FOR_TIMEOUT = 10_000; // ms\nconst SLICE_SIMULATOR_WAIT_FOR_SELECTOR = \"#__iframe-ready\";\nconst SLICE_SIMULATOR_SCREENSHOT_SELECTOR = \"#__iframe-renderer\";\n\nconst DEFAULT_SCREENSHOT_VIEWPORT: Viewport = {\n\twidth: 1200,\n\theight: 800,\n};\n\nfunction assertS3ACLInitialized(\n\ts3ACL: S3ACL | undefined,\n): asserts s3ACL is NonNullable<typeof s3ACL> {\n\tif (s3ACL == undefined) {\n\t\tthrow new Error(\n\t\t\t\"An S3 ACL has not been initialized. Run `SliceMachineManager.screenshots.prototype.initS3ACL()` before re-calling this method.\",\n\t\t);\n\t}\n}\n\nfunction assertBrowserContextInitialized(\n\tbrowserContext: BrowserContext | undefined,\n): asserts browserContext is NonNullable<typeof browserContext> {\n\tif (browserContext == undefined) {\n\t\tthrow new Error(\n\t\t\t\"A browser context has not been initialized. Run `SliceMachineManager.screenshots.prototype.initBrowserContext()` before re-calling this method.\",\n\t\t);\n\t}\n}\n\n/**\n * Encodes a part of a Slice Simulator URL to ensure it can be added to a URL\n * safely.\n *\n * The encoding logic must match Slice Machine UI's URL encoding practices.\n * Today, that requires the following:\n *\n * - Replace \"/\" with \"--\" (e.g. a Slice Library ID of \"./slices\" should turn into\n * \".--slices\")\n *\n * @param urlPart - A part of the URL.\n *\n * @returns `urlPart` encoded for use in a URL.\n */\nconst encodeSliceSimulatorURLPart = (urlPart: string): string => {\n\treturn urlPart.replace(/\\//g, \"--\");\n};\n\ntype ScreenshotsManagerCaptureSliceSimulatorScreenshotArgs = {\n\tsliceMachineUIOrigin: string;\n\tlibraryID: string;\n\tsliceID: string;\n\tvariationID: string;\n\tviewport?: Viewport;\n};\n\ntype ScreenshotsManagerCaptureSliceSimulatorScreenshotReturnType = {\n\tdata: Buffer;\n};\n\ntype ScreenshotsManagerUploadScreenshotArgs = {\n\tdata: Buffer;\n\tkeyPrefix?: string;\n};\n\ntype ScreenshotsManagerUploadScreenshotReturnType = {\n\turl: string;\n};\n\ntype ScreenshotsManagerDeleteScreenshotFolderArgs = {\n\tsliceID: string;\n};\n\nexport class ScreenshotsManager extends BaseManager {\n\tprivate _browserContext: BrowserContext | undefined;\n\tprivate _s3ACL: S3ACL | undefined;\n\n\tasync initBrowserContext(): Promise<void> {\n\t\tif (this._browserContext) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet puppeteer: typeof import(\"puppeteer\");\n\t\ttry {\n\t\t\t// Lazy-load Puppeteer only once it is needed.\n\t\t\tpuppeteer = await import(\"puppeteer\");\n\t\t} catch {\n\t\t\tthrow new InternalError(\n\t\t\t\t\"Screenshots require Puppeteer but Puppeteer was not found. Check that the `puppeteer` package is installed before trying again.\",\n\t\t\t);\n\t\t}\n\n\t\tconst browser = await puppeteer.launch();\n\n\t\tthis._browserContext = await browser.createIncognitoBrowserContext();\n\t}\n\n\tasync initS3ACL(): Promise<void> {\n\t\tif (this._s3ACL) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst awsACLURL = new URL(\"create\", API_ENDPOINTS.AwsAclProvider);\n\t\tconst awsACLRes = await this._fetch({ url: awsACLURL });\n\n\t\tconst awsACLText = await awsACLRes.text();\n\t\tlet awsACLJSON: unknown;\n\t\ttry {\n\t\t\tawsACLJSON = JSON.parse(awsACLText);\n\t\t} catch (error) {\n\t\t\t// Response is not JSON\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid AWS ACL response from ${awsACLURL}: ${awsACLText}`,\n\t\t\t);\n\t\t}\n\n\t\tconst { value: awsACL, error } = decode(\n\t\t\tt.intersection([\n\t\t\t\tt.type({\n\t\t\t\t\tvalues: t.type({\n\t\t\t\t\t\turl: t.string,\n\t\t\t\t\t\tfields: t.record(t.string, t.string),\n\t\t\t\t\t}),\n\t\t\t\t\timgixEndpoint: t.string,\n\t\t\t\t}),\n\t\t\t\tt.partial({\n\t\t\t\t\tmessage: t.string,\n\t\t\t\t\tMessage: t.string,\n\t\t\t\t\terror: t.string,\n\t\t\t\t}),\n\t\t\t]),\n\t\t\tawsACLJSON,\n\t\t);\n\n\t\tif (error) {\n\t\t\tthrow new Error(`Invalid AWS ACL response from ${awsACLURL}`);\n\t\t}\n\n\t\tconst errorMessage = awsACL.error || awsACL.message || awsACL.Message;\n\t\tif (errorMessage) {\n\t\t\tthrow new Error(`Failed to create an AWS ACL: ${errorMessage}`);\n\t\t}\n\n\t\tthis._s3ACL = {\n\t\t\tuploadEndpoint: awsACL.values.url,\n\t\t\trequiredFormDataFields: awsACL.values.fields,\n\t\t\timgixEndpoint: awsACL.imgixEndpoint,\n\t\t};\n\t}\n\n\t// TODO: Abstract to a generic `captureScreenshot()` method that is\n\t// used within a Slice-specific method in SliceManager.\n\tasync captureSliceSimulatorScreenshot(\n\t\targs: ScreenshotsManagerCaptureSliceSimulatorScreenshotArgs,\n\t): Promise<ScreenshotsManagerCaptureSliceSimulatorScreenshotReturnType> {\n\t\tassertBrowserContextInitialized(this._browserContext);\n\n\t\tconst sliceMachineConfig = await this.project.getSliceMachineConfig();\n\n\t\tif (!sliceMachineConfig.localSliceSimulatorURL) {\n\t\t\t// TODO: Provide a more helpful error message.\n\t\t\tthrow new Error(\n\t\t\t\t\"A local Slice Simulator URL must be configured in your Slice Machine configuration file.\",\n\t\t\t);\n\t\t}\n\n\t\tconst { model } = await this.slices.readSlice({\n\t\t\tlibraryID: args.libraryID,\n\t\t\tsliceID: args.sliceID,\n\t\t});\n\t\tif (!model) {\n\t\t\tthrow new Error(\n\t\t\t\t`Did not find a Slice in library \"${args.libraryID}\" with ID \"${args.sliceID}\".`,\n\t\t\t);\n\t\t}\n\n\t\tconst viewport = args.viewport || DEFAULT_SCREENSHOT_VIEWPORT;\n\n\t\t// TODO: Change `model.name` to `args.sliceID`?\n\t\t// Making that change would require changing the screenshot\n\t\t// page path in Slice Machine UI.\n\t\tconst url = new URL(\n\t\t\t`./${encodeSliceSimulatorURLPart(args.libraryID)}/${model.name}/${\n\t\t\t\targs.variationID\n\t\t\t}/screenshot`,\n\t\t\targs.sliceMachineUIOrigin,\n\t\t);\n\t\turl.searchParams.set(\"screenWidth\", viewport.width.toString());\n\t\turl.searchParams.set(\"screenHeight\", viewport.height.toString());\n\n\t\tconst isURLAccessible = await checkIsURLAccessible(url.toString());\n\n\t\tif (!isURLAccessible) {\n\t\t\tthrow new Error(\n\t\t\t\t`Slice Simulator screenshot URL is not accessible: ${url}`,\n\t\t\t);\n\t\t}\n\n\t\tconst page = await this._browserContext.newPage();\n\t\tpage.setViewport(viewport);\n\n\t\tawait page.goto(url.toString(), { waitUntil: \"load\" });\n\t\tawait page.waitForSelector(SLICE_SIMULATOR_WAIT_FOR_SELECTOR, {\n\t\t\ttimeout: SLICE_SIMULATOR_WAIT_FOR_TIMEOUT,\n\t\t});\n\n\t\tconst element = await page.$(SLICE_SIMULATOR_SCREENSHOT_SELECTOR);\n\t\tif (!element) {\n\t\t\tconst baseURL = new URL(url.pathname, url.origin);\n\n\t\t\tthrow new Error(\n\t\t\t\t`Slice Simulator did not find ${SLICE_SIMULATOR_WAIT_FOR_SELECTOR} on the page. Verify the URL is correct: ${baseURL}`,\n\t\t\t);\n\t\t}\n\n\t\tconst data = (await element.screenshot({\n\t\t\tencoding: \"binary\",\n\t\t\tclip: {\n\t\t\t\twidth: viewport.width,\n\t\t\t\theight: viewport.height,\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t},\n\t\t})) as Buffer;\n\n\t\treturn {\n\t\t\tdata,\n\t\t};\n\t}\n\n\tasync uploadScreenshot(\n\t\targs: ScreenshotsManagerUploadScreenshotArgs,\n\t): Promise<ScreenshotsManagerUploadScreenshotReturnType> {\n\t\tassertS3ACLInitialized(this._s3ACL);\n\n\t\tconst formData = new FormData();\n\n\t\tfor (const requiredFormDataFieldKey in this._s3ACL.requiredFormDataFields) {\n\t\t\tformData.append(\n\t\t\t\trequiredFormDataFieldKey,\n\t\t\t\tthis._s3ACL.requiredFormDataFields[requiredFormDataFieldKey],\n\t\t\t);\n\t\t}\n\n\t\tconst contentDigest = createContentDigest(args.data);\n\t\tconst fileType = await fileTypeFromBuffer(args.data);\n\t\tconst fileName = fileType\n\t\t\t? `${contentDigest}.${fileType.ext}`\n\t\t\t: contentDigest;\n\t\tconst key = args.keyPrefix ? `${args.keyPrefix}/${fileName}` : fileName;\n\n\t\tformData.set(\"key\", key);\n\n\t\tif (fileType) {\n\t\t\tformData.set(\"Content-Type\", fileType.mime);\n\t\t}\n\n\t\tformData.set(\"file\", new Blob([args.data], { type: fileType?.mime }));\n\n\t\tconst res = await fetch(this._s3ACL.uploadEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: formData,\n\t\t});\n\n\t\tif (res.ok) {\n\t\t\tconst url = new URL(key, this._s3ACL.imgixEndpoint);\n\t\t\turl.searchParams.set(\"auto\", \"compress,format\");\n\n\t\t\treturn {\n\t\t\t\turl: url.toString(),\n\t\t\t};\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`Unable to upload screenshot with status code: ${res.status}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync deleteScreenshotFolder(\n\t\targs: ScreenshotsManagerDeleteScreenshotFolderArgs,\n\t): Promise<void> {\n\t\tconst res = await this._fetch({\n\t\t\tbody: { sliceId: args.sliceID },\n\t\t\tmethod: \"POST\",\n\t\t\turl: new URL(\"delete-folder\", API_ENDPOINTS.AwsAclProvider),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Unable to delete screenshot folder with status code: ${res.status}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async _fetch(args: {\n\t\turl: URL;\n\t\tmethod?: \"GET\" | \"POST\";\n\t\tbody?: unknown;\n\t}): Promise<Response> {\n\t\tconst authenticationToken = await this.user.getAuthenticationToken();\n\t\tconst sliceMachineConfig = await this.project.getSliceMachineConfig();\n\n\t\treturn await fetch(args.url, {\n\t\t\tbody: args.body ? JSON.stringify(args.body) : undefined,\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${authenticationToken}`,\n\t\t\t\tRepository: sliceMachineConfig.repositoryName,\n\t\t\t\t\"User-Agent\": SLICE_MACHINE_USER_AGENT,\n\t\t\t\t...(args.body ? { \"Content-Type\": \"application/json\" } : {}),\n\t\t\t},\n\t\t\tmethod: args.method,\n\t\t});\n\t}\n}\n"],"names":["BaseManager","InternalError","API_ENDPOINTS","error","decode","t","checkIsURLAccessible","FormData","createContentDigest","fileTypeFromBuffer","Blob","fetch","SLICE_MACHINE_USER_AGENT"],"mappings":"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"}
1
+ {"version":3,"file":"ScreenshotsManager.cjs","sources":["../../../../src/managers/screenshots/ScreenshotsManager.ts"],"sourcesContent":["import * as t from \"io-ts\";\nimport { fileTypeFromBuffer } from \"file-type\";\nimport fetch, { FormData, Blob, Response } from \"node-fetch\";\n// puppeteer is lazy-loaded in captureSliceSimulatorScreenshot\nimport type { BrowserContext, Viewport } from \"puppeteer\";\n\nimport { checkIsURLAccessible } from \"../../lib/checkIsURLAccessible\";\nimport { createContentDigest } from \"../../lib/createContentDigest\";\nimport { decode } from \"../../lib/decode\";\n\nimport { S3ACL } from \"../../types\";\nimport { SLICE_MACHINE_USER_AGENT } from \"../../constants/SLICE_MACHINE_USER_AGENT\";\nimport { API_ENDPOINTS } from \"../../constants/API_ENDPOINTS\";\nimport { InternalError } from \"../../errors\";\n\nimport { BaseManager } from \"../BaseManager\";\n\nconst SLICE_SIMULATOR_WAIT_FOR_SELECTOR = \"#__iframe-ready\";\nconst SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT = 10_000; // ms\nconst SLICE_SIMULATOR_SCREENSHOT_SELECTOR = \"#__iframe-renderer\";\n\nconst DEFAULT_SCREENSHOT_VIEWPORT: Viewport = {\n\twidth: 1200,\n\theight: 800,\n};\n\nfunction assertS3ACLInitialized(\n\ts3ACL: S3ACL | undefined,\n): asserts s3ACL is NonNullable<typeof s3ACL> {\n\tif (s3ACL == undefined) {\n\t\tthrow new Error(\n\t\t\t\"An S3 ACL has not been initialized. Run `SliceMachineManager.screenshots.prototype.initS3ACL()` before re-calling this method.\",\n\t\t);\n\t}\n}\n\nfunction assertBrowserContextInitialized(\n\tbrowserContext: BrowserContext | undefined,\n): asserts browserContext is NonNullable<typeof browserContext> {\n\tif (browserContext == undefined) {\n\t\tthrow new Error(\n\t\t\t\"A browser context has not been initialized. Run `SliceMachineManager.screenshots.prototype.initBrowserContext()` before re-calling this method.\",\n\t\t);\n\t}\n}\n\n/**\n * Encodes a part of a Slice Simulator URL to ensure it can be added to a URL\n * safely.\n *\n * The encoding logic must match Slice Machine UI's URL encoding practices.\n * Today, that requires the following:\n *\n * - Replace \"/\" with \"--\" (e.g. a Slice Library ID of \"./slices\" should turn into\n * \".--slices\")\n *\n * @param urlPart - A part of the URL.\n *\n * @returns `urlPart` encoded for use in a URL.\n */\nconst encodeSliceSimulatorURLPart = (urlPart: string): string => {\n\treturn urlPart.replace(/\\//g, \"--\");\n};\n\ntype ScreenshotsManagerCaptureSliceSimulatorScreenshotArgs = {\n\tsliceMachineUIOrigin: string;\n\tlibraryID: string;\n\tsliceID: string;\n\tvariationID: string;\n\tviewport?: Viewport;\n};\n\ntype ScreenshotsManagerCaptureSliceSimulatorScreenshotReturnType = {\n\tdata: Buffer;\n};\n\ntype ScreenshotsManagerUploadScreenshotArgs = {\n\tdata: Buffer;\n\tkeyPrefix?: string;\n};\n\ntype ScreenshotsManagerUploadScreenshotReturnType = {\n\turl: string;\n};\n\ntype ScreenshotsManagerDeleteScreenshotFolderArgs = {\n\tsliceID: string;\n};\n\nexport class ScreenshotsManager extends BaseManager {\n\tprivate _browserContext: BrowserContext | undefined;\n\tprivate _s3ACL: S3ACL | undefined;\n\n\tasync initBrowserContext(): Promise<void> {\n\t\tif (this._browserContext) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet puppeteer: typeof import(\"puppeteer\");\n\t\ttry {\n\t\t\t// Lazy-load Puppeteer only once it is needed.\n\t\t\tpuppeteer = await import(\"puppeteer\");\n\t\t} catch {\n\t\t\tthrow new InternalError(\n\t\t\t\t\"Screenshots require Puppeteer but Puppeteer was not found. Check that the `puppeteer` package is installed before trying again.\",\n\t\t\t);\n\t\t}\n\n\t\tconst browser = await puppeteer.launch();\n\n\t\tthis._browserContext = await browser.createIncognitoBrowserContext();\n\t}\n\n\tasync initS3ACL(): Promise<void> {\n\t\tif (this._s3ACL) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst awsACLURL = new URL(\"create\", API_ENDPOINTS.AwsAclProvider);\n\t\tconst awsACLRes = await this._fetch({ url: awsACLURL });\n\n\t\tconst awsACLText = await awsACLRes.text();\n\t\tlet awsACLJSON: unknown;\n\t\ttry {\n\t\t\tawsACLJSON = JSON.parse(awsACLText);\n\t\t} catch (error) {\n\t\t\t// Response is not JSON\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid AWS ACL response from ${awsACLURL}: ${awsACLText}`,\n\t\t\t);\n\t\t}\n\n\t\tconst { value: awsACL, error } = decode(\n\t\t\tt.intersection([\n\t\t\t\tt.type({\n\t\t\t\t\tvalues: t.type({\n\t\t\t\t\t\turl: t.string,\n\t\t\t\t\t\tfields: t.record(t.string, t.string),\n\t\t\t\t\t}),\n\t\t\t\t\timgixEndpoint: t.string,\n\t\t\t\t}),\n\t\t\t\tt.partial({\n\t\t\t\t\tmessage: t.string,\n\t\t\t\t\tMessage: t.string,\n\t\t\t\t\terror: t.string,\n\t\t\t\t}),\n\t\t\t]),\n\t\t\tawsACLJSON,\n\t\t);\n\n\t\tif (error) {\n\t\t\tthrow new Error(`Invalid AWS ACL response from ${awsACLURL}`);\n\t\t}\n\n\t\tconst errorMessage = awsACL.error || awsACL.message || awsACL.Message;\n\t\tif (errorMessage) {\n\t\t\tthrow new Error(`Failed to create an AWS ACL: ${errorMessage}`);\n\t\t}\n\n\t\tthis._s3ACL = {\n\t\t\tuploadEndpoint: awsACL.values.url,\n\t\t\trequiredFormDataFields: awsACL.values.fields,\n\t\t\timgixEndpoint: awsACL.imgixEndpoint,\n\t\t};\n\t}\n\n\t// TODO: Abstract to a generic `captureScreenshot()` method that is\n\t// used within a Slice-specific method in SliceManager.\n\tasync captureSliceSimulatorScreenshot(\n\t\targs: ScreenshotsManagerCaptureSliceSimulatorScreenshotArgs,\n\t): Promise<ScreenshotsManagerCaptureSliceSimulatorScreenshotReturnType> {\n\t\tassertBrowserContextInitialized(this._browserContext);\n\n\t\tconst sliceMachineConfig = await this.project.getSliceMachineConfig();\n\n\t\tif (!sliceMachineConfig.localSliceSimulatorURL) {\n\t\t\t// TODO: Provide a more helpful error message.\n\t\t\tthrow new Error(\n\t\t\t\t\"A local Slice Simulator URL must be configured in your Slice Machine configuration file.\",\n\t\t\t);\n\t\t}\n\n\t\tconst { model } = await this.slices.readSlice({\n\t\t\tlibraryID: args.libraryID,\n\t\t\tsliceID: args.sliceID,\n\t\t});\n\t\tif (!model) {\n\t\t\tthrow new Error(\n\t\t\t\t`Did not find a Slice in library \"${args.libraryID}\" with ID \"${args.sliceID}\".`,\n\t\t\t);\n\t\t}\n\n\t\tconst viewport = args.viewport || DEFAULT_SCREENSHOT_VIEWPORT;\n\n\t\t// TODO: Change `model.name` to `args.sliceID`?\n\t\t// Making that change would require changing the screenshot\n\t\t// page path in Slice Machine UI.\n\t\tconst url = new URL(\n\t\t\t`./${encodeSliceSimulatorURLPart(args.libraryID)}/${model.name}/${\n\t\t\t\targs.variationID\n\t\t\t}/screenshot`,\n\t\t\targs.sliceMachineUIOrigin,\n\t\t);\n\t\turl.searchParams.set(\"screenWidth\", viewport.width.toString());\n\t\turl.searchParams.set(\"screenHeight\", viewport.height.toString());\n\n\t\tconst isURLAccessible = await checkIsURLAccessible(url.toString());\n\n\t\tif (!isURLAccessible) {\n\t\t\tthrow new Error(\n\t\t\t\t`Slice Simulator screenshot URL is not accessible: ${url}`,\n\t\t\t);\n\t\t}\n\n\t\tconst page = await this._browserContext.newPage();\n\t\tpage.setViewport(viewport);\n\n\t\tawait page.goto(url.toString(), { waitUntil: [\"load\", \"networkidle0\"] });\n\t\tawait page.waitForSelector(SLICE_SIMULATOR_WAIT_FOR_SELECTOR, {\n\t\t\ttimeout: SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT,\n\t\t});\n\n\t\tconst element = await page.$(SLICE_SIMULATOR_SCREENSHOT_SELECTOR);\n\t\tif (!element) {\n\t\t\tconst baseURL = new URL(url.pathname, url.origin);\n\n\t\t\tthrow new Error(\n\t\t\t\t`Slice Simulator did not find ${SLICE_SIMULATOR_WAIT_FOR_SELECTOR} on the page. Verify the URL is correct: ${baseURL}`,\n\t\t\t);\n\t\t}\n\n\t\tconst data = (await element.screenshot({\n\t\t\tencoding: \"binary\",\n\t\t\tclip: {\n\t\t\t\twidth: viewport.width,\n\t\t\t\theight: viewport.height,\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t},\n\t\t})) as Buffer;\n\n\t\treturn {\n\t\t\tdata,\n\t\t};\n\t}\n\n\tasync uploadScreenshot(\n\t\targs: ScreenshotsManagerUploadScreenshotArgs,\n\t): Promise<ScreenshotsManagerUploadScreenshotReturnType> {\n\t\tassertS3ACLInitialized(this._s3ACL);\n\n\t\tconst formData = new FormData();\n\n\t\tfor (const requiredFormDataFieldKey in this._s3ACL.requiredFormDataFields) {\n\t\t\tformData.append(\n\t\t\t\trequiredFormDataFieldKey,\n\t\t\t\tthis._s3ACL.requiredFormDataFields[requiredFormDataFieldKey],\n\t\t\t);\n\t\t}\n\n\t\tconst contentDigest = createContentDigest(args.data);\n\t\tconst fileType = await fileTypeFromBuffer(args.data);\n\t\tconst fileName = fileType\n\t\t\t? `${contentDigest}.${fileType.ext}`\n\t\t\t: contentDigest;\n\t\tconst key = args.keyPrefix ? `${args.keyPrefix}/${fileName}` : fileName;\n\n\t\tformData.set(\"key\", key);\n\n\t\tif (fileType) {\n\t\t\tformData.set(\"Content-Type\", fileType.mime);\n\t\t}\n\n\t\tformData.set(\"file\", new Blob([args.data], { type: fileType?.mime }));\n\n\t\tconst res = await fetch(this._s3ACL.uploadEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: formData,\n\t\t});\n\n\t\tif (res.ok) {\n\t\t\tconst url = new URL(key, this._s3ACL.imgixEndpoint);\n\t\t\turl.searchParams.set(\"auto\", \"compress,format\");\n\n\t\t\treturn {\n\t\t\t\turl: url.toString(),\n\t\t\t};\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`Unable to upload screenshot with status code: ${res.status}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync deleteScreenshotFolder(\n\t\targs: ScreenshotsManagerDeleteScreenshotFolderArgs,\n\t): Promise<void> {\n\t\tconst res = await this._fetch({\n\t\t\t// We're sending `args.sliceID` as `sliceName` because it's inconsistently\n\t\t\t// named in the ACL Provider API.\n\t\t\tbody: { sliceName: args.sliceID },\n\t\t\tmethod: \"POST\",\n\t\t\turl: new URL(\"delete-folder\", API_ENDPOINTS.AwsAclProvider),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Unable to delete screenshot folder with status code: ${res.status}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async _fetch(args: {\n\t\turl: URL;\n\t\tmethod?: \"GET\" | \"POST\";\n\t\tbody?: unknown;\n\t}): Promise<Response> {\n\t\tconst authenticationToken = await this.user.getAuthenticationToken();\n\t\tconst sliceMachineConfig = await this.project.getSliceMachineConfig();\n\n\t\treturn await fetch(args.url, {\n\t\t\tbody: args.body ? JSON.stringify(args.body) : undefined,\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${authenticationToken}`,\n\t\t\t\tRepository: sliceMachineConfig.repositoryName,\n\t\t\t\t\"User-Agent\": SLICE_MACHINE_USER_AGENT,\n\t\t\t\t...(args.body ? { \"Content-Type\": \"application/json\" } : {}),\n\t\t\t},\n\t\t\tmethod: args.method,\n\t\t});\n\t}\n}\n"],"names":["BaseManager","InternalError","API_ENDPOINTS","error","decode","t","checkIsURLAccessible","FormData","createContentDigest","fileTypeFromBuffer","Blob","fetch","SLICE_MACHINE_USER_AGENT"],"mappings":"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"}
@@ -21,8 +21,8 @@ import Blob from './../../_node_modules/fetch-blob/index.js';
21
21
  import "node:fs";
22
22
  import "node:path";
23
23
  import './../../_node_modules/node-domexception/index.js';
24
- const SLICE_SIMULATOR_WAIT_FOR_TIMEOUT = 1e4;
25
24
  const SLICE_SIMULATOR_WAIT_FOR_SELECTOR = "#__iframe-ready";
25
+ const SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT = 1e4;
26
26
  const SLICE_SIMULATOR_SCREENSHOT_SELECTOR = "#__iframe-renderer";
27
27
  const DEFAULT_SCREENSHOT_VIEWPORT = {
28
28
  width: 1200,
@@ -125,9 +125,9 @@ class ScreenshotsManager extends BaseManager {
125
125
  }
126
126
  const page = await this._browserContext.newPage();
127
127
  page.setViewport(viewport);
128
- await page.goto(url.toString(), { waitUntil: "load" });
128
+ await page.goto(url.toString(), { waitUntil: ["load", "networkidle0"] });
129
129
  await page.waitForSelector(SLICE_SIMULATOR_WAIT_FOR_SELECTOR, {
130
- timeout: SLICE_SIMULATOR_WAIT_FOR_TIMEOUT
130
+ timeout: SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT
131
131
  });
132
132
  const element = await page.$(SLICE_SIMULATOR_SCREENSHOT_SELECTOR);
133
133
  if (!element) {
@@ -178,7 +178,9 @@ class ScreenshotsManager extends BaseManager {
178
178
  }
179
179
  async deleteScreenshotFolder(args) {
180
180
  const res = await this._fetch({
181
- body: { sliceId: args.sliceID },
181
+ // We're sending `args.sliceID` as `sliceName` because it's inconsistently
182
+ // named in the ACL Provider API.
183
+ body: { sliceName: args.sliceID },
182
184
  method: "POST",
183
185
  url: new URL("delete-folder", API_ENDPOINTS.AwsAclProvider)
184
186
  });
@@ -1 +1 @@
1
- {"version":3,"file":"ScreenshotsManager.js","sources":["../../../../src/managers/screenshots/ScreenshotsManager.ts"],"sourcesContent":["import * as t from \"io-ts\";\nimport { fileTypeFromBuffer } from \"file-type\";\nimport fetch, { FormData, Blob, Response } from \"node-fetch\";\n// puppeteer is lazy-loaded in captureSliceSimulatorScreenshot\nimport type { BrowserContext, Viewport } from \"puppeteer\";\n\nimport { checkIsURLAccessible } from \"../../lib/checkIsURLAccessible\";\nimport { createContentDigest } from \"../../lib/createContentDigest\";\nimport { decode } from \"../../lib/decode\";\n\nimport { S3ACL } from \"../../types\";\nimport { SLICE_MACHINE_USER_AGENT } from \"../../constants/SLICE_MACHINE_USER_AGENT\";\nimport { API_ENDPOINTS } from \"../../constants/API_ENDPOINTS\";\nimport { InternalError } from \"../../errors\";\n\nimport { BaseManager } from \"../BaseManager\";\n\nconst SLICE_SIMULATOR_WAIT_FOR_TIMEOUT = 10_000; // ms\nconst SLICE_SIMULATOR_WAIT_FOR_SELECTOR = \"#__iframe-ready\";\nconst SLICE_SIMULATOR_SCREENSHOT_SELECTOR = \"#__iframe-renderer\";\n\nconst DEFAULT_SCREENSHOT_VIEWPORT: Viewport = {\n\twidth: 1200,\n\theight: 800,\n};\n\nfunction assertS3ACLInitialized(\n\ts3ACL: S3ACL | undefined,\n): asserts s3ACL is NonNullable<typeof s3ACL> {\n\tif (s3ACL == undefined) {\n\t\tthrow new Error(\n\t\t\t\"An S3 ACL has not been initialized. Run `SliceMachineManager.screenshots.prototype.initS3ACL()` before re-calling this method.\",\n\t\t);\n\t}\n}\n\nfunction assertBrowserContextInitialized(\n\tbrowserContext: BrowserContext | undefined,\n): asserts browserContext is NonNullable<typeof browserContext> {\n\tif (browserContext == undefined) {\n\t\tthrow new Error(\n\t\t\t\"A browser context has not been initialized. Run `SliceMachineManager.screenshots.prototype.initBrowserContext()` before re-calling this method.\",\n\t\t);\n\t}\n}\n\n/**\n * Encodes a part of a Slice Simulator URL to ensure it can be added to a URL\n * safely.\n *\n * The encoding logic must match Slice Machine UI's URL encoding practices.\n * Today, that requires the following:\n *\n * - Replace \"/\" with \"--\" (e.g. a Slice Library ID of \"./slices\" should turn into\n * \".--slices\")\n *\n * @param urlPart - A part of the URL.\n *\n * @returns `urlPart` encoded for use in a URL.\n */\nconst encodeSliceSimulatorURLPart = (urlPart: string): string => {\n\treturn urlPart.replace(/\\//g, \"--\");\n};\n\ntype ScreenshotsManagerCaptureSliceSimulatorScreenshotArgs = {\n\tsliceMachineUIOrigin: string;\n\tlibraryID: string;\n\tsliceID: string;\n\tvariationID: string;\n\tviewport?: Viewport;\n};\n\ntype ScreenshotsManagerCaptureSliceSimulatorScreenshotReturnType = {\n\tdata: Buffer;\n};\n\ntype ScreenshotsManagerUploadScreenshotArgs = {\n\tdata: Buffer;\n\tkeyPrefix?: string;\n};\n\ntype ScreenshotsManagerUploadScreenshotReturnType = {\n\turl: string;\n};\n\ntype ScreenshotsManagerDeleteScreenshotFolderArgs = {\n\tsliceID: string;\n};\n\nexport class ScreenshotsManager extends BaseManager {\n\tprivate _browserContext: BrowserContext | undefined;\n\tprivate _s3ACL: S3ACL | undefined;\n\n\tasync initBrowserContext(): Promise<void> {\n\t\tif (this._browserContext) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet puppeteer: typeof import(\"puppeteer\");\n\t\ttry {\n\t\t\t// Lazy-load Puppeteer only once it is needed.\n\t\t\tpuppeteer = await import(\"puppeteer\");\n\t\t} catch {\n\t\t\tthrow new InternalError(\n\t\t\t\t\"Screenshots require Puppeteer but Puppeteer was not found. Check that the `puppeteer` package is installed before trying again.\",\n\t\t\t);\n\t\t}\n\n\t\tconst browser = await puppeteer.launch();\n\n\t\tthis._browserContext = await browser.createIncognitoBrowserContext();\n\t}\n\n\tasync initS3ACL(): Promise<void> {\n\t\tif (this._s3ACL) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst awsACLURL = new URL(\"create\", API_ENDPOINTS.AwsAclProvider);\n\t\tconst awsACLRes = await this._fetch({ url: awsACLURL });\n\n\t\tconst awsACLText = await awsACLRes.text();\n\t\tlet awsACLJSON: unknown;\n\t\ttry {\n\t\t\tawsACLJSON = JSON.parse(awsACLText);\n\t\t} catch (error) {\n\t\t\t// Response is not JSON\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid AWS ACL response from ${awsACLURL}: ${awsACLText}`,\n\t\t\t);\n\t\t}\n\n\t\tconst { value: awsACL, error } = decode(\n\t\t\tt.intersection([\n\t\t\t\tt.type({\n\t\t\t\t\tvalues: t.type({\n\t\t\t\t\t\turl: t.string,\n\t\t\t\t\t\tfields: t.record(t.string, t.string),\n\t\t\t\t\t}),\n\t\t\t\t\timgixEndpoint: t.string,\n\t\t\t\t}),\n\t\t\t\tt.partial({\n\t\t\t\t\tmessage: t.string,\n\t\t\t\t\tMessage: t.string,\n\t\t\t\t\terror: t.string,\n\t\t\t\t}),\n\t\t\t]),\n\t\t\tawsACLJSON,\n\t\t);\n\n\t\tif (error) {\n\t\t\tthrow new Error(`Invalid AWS ACL response from ${awsACLURL}`);\n\t\t}\n\n\t\tconst errorMessage = awsACL.error || awsACL.message || awsACL.Message;\n\t\tif (errorMessage) {\n\t\t\tthrow new Error(`Failed to create an AWS ACL: ${errorMessage}`);\n\t\t}\n\n\t\tthis._s3ACL = {\n\t\t\tuploadEndpoint: awsACL.values.url,\n\t\t\trequiredFormDataFields: awsACL.values.fields,\n\t\t\timgixEndpoint: awsACL.imgixEndpoint,\n\t\t};\n\t}\n\n\t// TODO: Abstract to a generic `captureScreenshot()` method that is\n\t// used within a Slice-specific method in SliceManager.\n\tasync captureSliceSimulatorScreenshot(\n\t\targs: ScreenshotsManagerCaptureSliceSimulatorScreenshotArgs,\n\t): Promise<ScreenshotsManagerCaptureSliceSimulatorScreenshotReturnType> {\n\t\tassertBrowserContextInitialized(this._browserContext);\n\n\t\tconst sliceMachineConfig = await this.project.getSliceMachineConfig();\n\n\t\tif (!sliceMachineConfig.localSliceSimulatorURL) {\n\t\t\t// TODO: Provide a more helpful error message.\n\t\t\tthrow new Error(\n\t\t\t\t\"A local Slice Simulator URL must be configured in your Slice Machine configuration file.\",\n\t\t\t);\n\t\t}\n\n\t\tconst { model } = await this.slices.readSlice({\n\t\t\tlibraryID: args.libraryID,\n\t\t\tsliceID: args.sliceID,\n\t\t});\n\t\tif (!model) {\n\t\t\tthrow new Error(\n\t\t\t\t`Did not find a Slice in library \"${args.libraryID}\" with ID \"${args.sliceID}\".`,\n\t\t\t);\n\t\t}\n\n\t\tconst viewport = args.viewport || DEFAULT_SCREENSHOT_VIEWPORT;\n\n\t\t// TODO: Change `model.name` to `args.sliceID`?\n\t\t// Making that change would require changing the screenshot\n\t\t// page path in Slice Machine UI.\n\t\tconst url = new URL(\n\t\t\t`./${encodeSliceSimulatorURLPart(args.libraryID)}/${model.name}/${\n\t\t\t\targs.variationID\n\t\t\t}/screenshot`,\n\t\t\targs.sliceMachineUIOrigin,\n\t\t);\n\t\turl.searchParams.set(\"screenWidth\", viewport.width.toString());\n\t\turl.searchParams.set(\"screenHeight\", viewport.height.toString());\n\n\t\tconst isURLAccessible = await checkIsURLAccessible(url.toString());\n\n\t\tif (!isURLAccessible) {\n\t\t\tthrow new Error(\n\t\t\t\t`Slice Simulator screenshot URL is not accessible: ${url}`,\n\t\t\t);\n\t\t}\n\n\t\tconst page = await this._browserContext.newPage();\n\t\tpage.setViewport(viewport);\n\n\t\tawait page.goto(url.toString(), { waitUntil: \"load\" });\n\t\tawait page.waitForSelector(SLICE_SIMULATOR_WAIT_FOR_SELECTOR, {\n\t\t\ttimeout: SLICE_SIMULATOR_WAIT_FOR_TIMEOUT,\n\t\t});\n\n\t\tconst element = await page.$(SLICE_SIMULATOR_SCREENSHOT_SELECTOR);\n\t\tif (!element) {\n\t\t\tconst baseURL = new URL(url.pathname, url.origin);\n\n\t\t\tthrow new Error(\n\t\t\t\t`Slice Simulator did not find ${SLICE_SIMULATOR_WAIT_FOR_SELECTOR} on the page. Verify the URL is correct: ${baseURL}`,\n\t\t\t);\n\t\t}\n\n\t\tconst data = (await element.screenshot({\n\t\t\tencoding: \"binary\",\n\t\t\tclip: {\n\t\t\t\twidth: viewport.width,\n\t\t\t\theight: viewport.height,\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t},\n\t\t})) as Buffer;\n\n\t\treturn {\n\t\t\tdata,\n\t\t};\n\t}\n\n\tasync uploadScreenshot(\n\t\targs: ScreenshotsManagerUploadScreenshotArgs,\n\t): Promise<ScreenshotsManagerUploadScreenshotReturnType> {\n\t\tassertS3ACLInitialized(this._s3ACL);\n\n\t\tconst formData = new FormData();\n\n\t\tfor (const requiredFormDataFieldKey in this._s3ACL.requiredFormDataFields) {\n\t\t\tformData.append(\n\t\t\t\trequiredFormDataFieldKey,\n\t\t\t\tthis._s3ACL.requiredFormDataFields[requiredFormDataFieldKey],\n\t\t\t);\n\t\t}\n\n\t\tconst contentDigest = createContentDigest(args.data);\n\t\tconst fileType = await fileTypeFromBuffer(args.data);\n\t\tconst fileName = fileType\n\t\t\t? `${contentDigest}.${fileType.ext}`\n\t\t\t: contentDigest;\n\t\tconst key = args.keyPrefix ? `${args.keyPrefix}/${fileName}` : fileName;\n\n\t\tformData.set(\"key\", key);\n\n\t\tif (fileType) {\n\t\t\tformData.set(\"Content-Type\", fileType.mime);\n\t\t}\n\n\t\tformData.set(\"file\", new Blob([args.data], { type: fileType?.mime }));\n\n\t\tconst res = await fetch(this._s3ACL.uploadEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: formData,\n\t\t});\n\n\t\tif (res.ok) {\n\t\t\tconst url = new URL(key, this._s3ACL.imgixEndpoint);\n\t\t\turl.searchParams.set(\"auto\", \"compress,format\");\n\n\t\t\treturn {\n\t\t\t\turl: url.toString(),\n\t\t\t};\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`Unable to upload screenshot with status code: ${res.status}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync deleteScreenshotFolder(\n\t\targs: ScreenshotsManagerDeleteScreenshotFolderArgs,\n\t): Promise<void> {\n\t\tconst res = await this._fetch({\n\t\t\tbody: { sliceId: args.sliceID },\n\t\t\tmethod: \"POST\",\n\t\t\turl: new URL(\"delete-folder\", API_ENDPOINTS.AwsAclProvider),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Unable to delete screenshot folder with status code: ${res.status}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async _fetch(args: {\n\t\turl: URL;\n\t\tmethod?: \"GET\" | \"POST\";\n\t\tbody?: unknown;\n\t}): Promise<Response> {\n\t\tconst authenticationToken = await this.user.getAuthenticationToken();\n\t\tconst sliceMachineConfig = await this.project.getSliceMachineConfig();\n\n\t\treturn await fetch(args.url, {\n\t\t\tbody: args.body ? JSON.stringify(args.body) : undefined,\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${authenticationToken}`,\n\t\t\t\tRepository: sliceMachineConfig.repositoryName,\n\t\t\t\t\"User-Agent\": SLICE_MACHINE_USER_AGENT,\n\t\t\t\t...(args.body ? { \"Content-Type\": \"application/json\" } : {}),\n\t\t\t},\n\t\t\tmethod: args.method,\n\t\t});\n\t}\n}\n"],"names":["error"],"mappings":"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"}
1
+ {"version":3,"file":"ScreenshotsManager.js","sources":["../../../../src/managers/screenshots/ScreenshotsManager.ts"],"sourcesContent":["import * as t from \"io-ts\";\nimport { fileTypeFromBuffer } from \"file-type\";\nimport fetch, { FormData, Blob, Response } from \"node-fetch\";\n// puppeteer is lazy-loaded in captureSliceSimulatorScreenshot\nimport type { BrowserContext, Viewport } from \"puppeteer\";\n\nimport { checkIsURLAccessible } from \"../../lib/checkIsURLAccessible\";\nimport { createContentDigest } from \"../../lib/createContentDigest\";\nimport { decode } from \"../../lib/decode\";\n\nimport { S3ACL } from \"../../types\";\nimport { SLICE_MACHINE_USER_AGENT } from \"../../constants/SLICE_MACHINE_USER_AGENT\";\nimport { API_ENDPOINTS } from \"../../constants/API_ENDPOINTS\";\nimport { InternalError } from \"../../errors\";\n\nimport { BaseManager } from \"../BaseManager\";\n\nconst SLICE_SIMULATOR_WAIT_FOR_SELECTOR = \"#__iframe-ready\";\nconst SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT = 10_000; // ms\nconst SLICE_SIMULATOR_SCREENSHOT_SELECTOR = \"#__iframe-renderer\";\n\nconst DEFAULT_SCREENSHOT_VIEWPORT: Viewport = {\n\twidth: 1200,\n\theight: 800,\n};\n\nfunction assertS3ACLInitialized(\n\ts3ACL: S3ACL | undefined,\n): asserts s3ACL is NonNullable<typeof s3ACL> {\n\tif (s3ACL == undefined) {\n\t\tthrow new Error(\n\t\t\t\"An S3 ACL has not been initialized. Run `SliceMachineManager.screenshots.prototype.initS3ACL()` before re-calling this method.\",\n\t\t);\n\t}\n}\n\nfunction assertBrowserContextInitialized(\n\tbrowserContext: BrowserContext | undefined,\n): asserts browserContext is NonNullable<typeof browserContext> {\n\tif (browserContext == undefined) {\n\t\tthrow new Error(\n\t\t\t\"A browser context has not been initialized. Run `SliceMachineManager.screenshots.prototype.initBrowserContext()` before re-calling this method.\",\n\t\t);\n\t}\n}\n\n/**\n * Encodes a part of a Slice Simulator URL to ensure it can be added to a URL\n * safely.\n *\n * The encoding logic must match Slice Machine UI's URL encoding practices.\n * Today, that requires the following:\n *\n * - Replace \"/\" with \"--\" (e.g. a Slice Library ID of \"./slices\" should turn into\n * \".--slices\")\n *\n * @param urlPart - A part of the URL.\n *\n * @returns `urlPart` encoded for use in a URL.\n */\nconst encodeSliceSimulatorURLPart = (urlPart: string): string => {\n\treturn urlPart.replace(/\\//g, \"--\");\n};\n\ntype ScreenshotsManagerCaptureSliceSimulatorScreenshotArgs = {\n\tsliceMachineUIOrigin: string;\n\tlibraryID: string;\n\tsliceID: string;\n\tvariationID: string;\n\tviewport?: Viewport;\n};\n\ntype ScreenshotsManagerCaptureSliceSimulatorScreenshotReturnType = {\n\tdata: Buffer;\n};\n\ntype ScreenshotsManagerUploadScreenshotArgs = {\n\tdata: Buffer;\n\tkeyPrefix?: string;\n};\n\ntype ScreenshotsManagerUploadScreenshotReturnType = {\n\turl: string;\n};\n\ntype ScreenshotsManagerDeleteScreenshotFolderArgs = {\n\tsliceID: string;\n};\n\nexport class ScreenshotsManager extends BaseManager {\n\tprivate _browserContext: BrowserContext | undefined;\n\tprivate _s3ACL: S3ACL | undefined;\n\n\tasync initBrowserContext(): Promise<void> {\n\t\tif (this._browserContext) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet puppeteer: typeof import(\"puppeteer\");\n\t\ttry {\n\t\t\t// Lazy-load Puppeteer only once it is needed.\n\t\t\tpuppeteer = await import(\"puppeteer\");\n\t\t} catch {\n\t\t\tthrow new InternalError(\n\t\t\t\t\"Screenshots require Puppeteer but Puppeteer was not found. Check that the `puppeteer` package is installed before trying again.\",\n\t\t\t);\n\t\t}\n\n\t\tconst browser = await puppeteer.launch();\n\n\t\tthis._browserContext = await browser.createIncognitoBrowserContext();\n\t}\n\n\tasync initS3ACL(): Promise<void> {\n\t\tif (this._s3ACL) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst awsACLURL = new URL(\"create\", API_ENDPOINTS.AwsAclProvider);\n\t\tconst awsACLRes = await this._fetch({ url: awsACLURL });\n\n\t\tconst awsACLText = await awsACLRes.text();\n\t\tlet awsACLJSON: unknown;\n\t\ttry {\n\t\t\tawsACLJSON = JSON.parse(awsACLText);\n\t\t} catch (error) {\n\t\t\t// Response is not JSON\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid AWS ACL response from ${awsACLURL}: ${awsACLText}`,\n\t\t\t);\n\t\t}\n\n\t\tconst { value: awsACL, error } = decode(\n\t\t\tt.intersection([\n\t\t\t\tt.type({\n\t\t\t\t\tvalues: t.type({\n\t\t\t\t\t\turl: t.string,\n\t\t\t\t\t\tfields: t.record(t.string, t.string),\n\t\t\t\t\t}),\n\t\t\t\t\timgixEndpoint: t.string,\n\t\t\t\t}),\n\t\t\t\tt.partial({\n\t\t\t\t\tmessage: t.string,\n\t\t\t\t\tMessage: t.string,\n\t\t\t\t\terror: t.string,\n\t\t\t\t}),\n\t\t\t]),\n\t\t\tawsACLJSON,\n\t\t);\n\n\t\tif (error) {\n\t\t\tthrow new Error(`Invalid AWS ACL response from ${awsACLURL}`);\n\t\t}\n\n\t\tconst errorMessage = awsACL.error || awsACL.message || awsACL.Message;\n\t\tif (errorMessage) {\n\t\t\tthrow new Error(`Failed to create an AWS ACL: ${errorMessage}`);\n\t\t}\n\n\t\tthis._s3ACL = {\n\t\t\tuploadEndpoint: awsACL.values.url,\n\t\t\trequiredFormDataFields: awsACL.values.fields,\n\t\t\timgixEndpoint: awsACL.imgixEndpoint,\n\t\t};\n\t}\n\n\t// TODO: Abstract to a generic `captureScreenshot()` method that is\n\t// used within a Slice-specific method in SliceManager.\n\tasync captureSliceSimulatorScreenshot(\n\t\targs: ScreenshotsManagerCaptureSliceSimulatorScreenshotArgs,\n\t): Promise<ScreenshotsManagerCaptureSliceSimulatorScreenshotReturnType> {\n\t\tassertBrowserContextInitialized(this._browserContext);\n\n\t\tconst sliceMachineConfig = await this.project.getSliceMachineConfig();\n\n\t\tif (!sliceMachineConfig.localSliceSimulatorURL) {\n\t\t\t// TODO: Provide a more helpful error message.\n\t\t\tthrow new Error(\n\t\t\t\t\"A local Slice Simulator URL must be configured in your Slice Machine configuration file.\",\n\t\t\t);\n\t\t}\n\n\t\tconst { model } = await this.slices.readSlice({\n\t\t\tlibraryID: args.libraryID,\n\t\t\tsliceID: args.sliceID,\n\t\t});\n\t\tif (!model) {\n\t\t\tthrow new Error(\n\t\t\t\t`Did not find a Slice in library \"${args.libraryID}\" with ID \"${args.sliceID}\".`,\n\t\t\t);\n\t\t}\n\n\t\tconst viewport = args.viewport || DEFAULT_SCREENSHOT_VIEWPORT;\n\n\t\t// TODO: Change `model.name` to `args.sliceID`?\n\t\t// Making that change would require changing the screenshot\n\t\t// page path in Slice Machine UI.\n\t\tconst url = new URL(\n\t\t\t`./${encodeSliceSimulatorURLPart(args.libraryID)}/${model.name}/${\n\t\t\t\targs.variationID\n\t\t\t}/screenshot`,\n\t\t\targs.sliceMachineUIOrigin,\n\t\t);\n\t\turl.searchParams.set(\"screenWidth\", viewport.width.toString());\n\t\turl.searchParams.set(\"screenHeight\", viewport.height.toString());\n\n\t\tconst isURLAccessible = await checkIsURLAccessible(url.toString());\n\n\t\tif (!isURLAccessible) {\n\t\t\tthrow new Error(\n\t\t\t\t`Slice Simulator screenshot URL is not accessible: ${url}`,\n\t\t\t);\n\t\t}\n\n\t\tconst page = await this._browserContext.newPage();\n\t\tpage.setViewport(viewport);\n\n\t\tawait page.goto(url.toString(), { waitUntil: [\"load\", \"networkidle0\"] });\n\t\tawait page.waitForSelector(SLICE_SIMULATOR_WAIT_FOR_SELECTOR, {\n\t\t\ttimeout: SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT,\n\t\t});\n\n\t\tconst element = await page.$(SLICE_SIMULATOR_SCREENSHOT_SELECTOR);\n\t\tif (!element) {\n\t\t\tconst baseURL = new URL(url.pathname, url.origin);\n\n\t\t\tthrow new Error(\n\t\t\t\t`Slice Simulator did not find ${SLICE_SIMULATOR_WAIT_FOR_SELECTOR} on the page. Verify the URL is correct: ${baseURL}`,\n\t\t\t);\n\t\t}\n\n\t\tconst data = (await element.screenshot({\n\t\t\tencoding: \"binary\",\n\t\t\tclip: {\n\t\t\t\twidth: viewport.width,\n\t\t\t\theight: viewport.height,\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t},\n\t\t})) as Buffer;\n\n\t\treturn {\n\t\t\tdata,\n\t\t};\n\t}\n\n\tasync uploadScreenshot(\n\t\targs: ScreenshotsManagerUploadScreenshotArgs,\n\t): Promise<ScreenshotsManagerUploadScreenshotReturnType> {\n\t\tassertS3ACLInitialized(this._s3ACL);\n\n\t\tconst formData = new FormData();\n\n\t\tfor (const requiredFormDataFieldKey in this._s3ACL.requiredFormDataFields) {\n\t\t\tformData.append(\n\t\t\t\trequiredFormDataFieldKey,\n\t\t\t\tthis._s3ACL.requiredFormDataFields[requiredFormDataFieldKey],\n\t\t\t);\n\t\t}\n\n\t\tconst contentDigest = createContentDigest(args.data);\n\t\tconst fileType = await fileTypeFromBuffer(args.data);\n\t\tconst fileName = fileType\n\t\t\t? `${contentDigest}.${fileType.ext}`\n\t\t\t: contentDigest;\n\t\tconst key = args.keyPrefix ? `${args.keyPrefix}/${fileName}` : fileName;\n\n\t\tformData.set(\"key\", key);\n\n\t\tif (fileType) {\n\t\t\tformData.set(\"Content-Type\", fileType.mime);\n\t\t}\n\n\t\tformData.set(\"file\", new Blob([args.data], { type: fileType?.mime }));\n\n\t\tconst res = await fetch(this._s3ACL.uploadEndpoint, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: formData,\n\t\t});\n\n\t\tif (res.ok) {\n\t\t\tconst url = new URL(key, this._s3ACL.imgixEndpoint);\n\t\t\turl.searchParams.set(\"auto\", \"compress,format\");\n\n\t\t\treturn {\n\t\t\t\turl: url.toString(),\n\t\t\t};\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`Unable to upload screenshot with status code: ${res.status}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync deleteScreenshotFolder(\n\t\targs: ScreenshotsManagerDeleteScreenshotFolderArgs,\n\t): Promise<void> {\n\t\tconst res = await this._fetch({\n\t\t\t// We're sending `args.sliceID` as `sliceName` because it's inconsistently\n\t\t\t// named in the ACL Provider API.\n\t\t\tbody: { sliceName: args.sliceID },\n\t\t\tmethod: \"POST\",\n\t\t\turl: new URL(\"delete-folder\", API_ENDPOINTS.AwsAclProvider),\n\t\t});\n\t\tif (!res.ok) {\n\t\t\tthrow new Error(\n\t\t\t\t`Unable to delete screenshot folder with status code: ${res.status}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate async _fetch(args: {\n\t\turl: URL;\n\t\tmethod?: \"GET\" | \"POST\";\n\t\tbody?: unknown;\n\t}): Promise<Response> {\n\t\tconst authenticationToken = await this.user.getAuthenticationToken();\n\t\tconst sliceMachineConfig = await this.project.getSliceMachineConfig();\n\n\t\treturn await fetch(args.url, {\n\t\t\tbody: args.body ? JSON.stringify(args.body) : undefined,\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${authenticationToken}`,\n\t\t\t\tRepository: sliceMachineConfig.repositoryName,\n\t\t\t\t\"User-Agent\": SLICE_MACHINE_USER_AGENT,\n\t\t\t\t...(args.body ? { \"Content-Type\": \"application/json\" } : {}),\n\t\t\t},\n\t\t\tmethod: args.method,\n\t\t});\n\t}\n}\n"],"names":["error"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slicemachine/manager",
3
- "version": "0.1.1-dev-plugins-m3.3",
3
+ "version": "0.1.1-dev-plugins-m3.5",
4
4
  "description": "Manage all aspects of a Slice Machine project.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -65,7 +65,7 @@
65
65
  "@prismicio/custom-types-client": "^1.1.0",
66
66
  "@prismicio/mocks": "2.0.0-alpha.2",
67
67
  "@prismicio/types-internal": "2.0.0-alpha.9",
68
- "@slicemachine/plugin-kit": "0.1.8-dev-plugins-m3.3",
68
+ "@slicemachine/plugin-kit": "0.1.8-dev-plugins-m3.5",
69
69
  "@wooorm/starry-night": "^1.6.0",
70
70
  "analytics-node": "^6.2.0",
71
71
  "cookie": "^0.5.0",
@@ -138,5 +138,5 @@
138
138
  "publishConfig": {
139
139
  "access": "public"
140
140
  },
141
- "gitHead": "f434f3c72383406a27b39e56aa6b451e53eb38af"
141
+ "gitHead": "4e30e50a6bf774f0b3453d73dee7a325e3ea9729"
142
142
  }
@@ -15,8 +15,8 @@ import { InternalError } from "../../errors";
15
15
 
16
16
  import { BaseManager } from "../BaseManager";
17
17
 
18
- const SLICE_SIMULATOR_WAIT_FOR_TIMEOUT = 10_000; // ms
19
18
  const SLICE_SIMULATOR_WAIT_FOR_SELECTOR = "#__iframe-ready";
19
+ const SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT = 10_000; // ms
20
20
  const SLICE_SIMULATOR_SCREENSHOT_SELECTOR = "#__iframe-renderer";
21
21
 
22
22
  const DEFAULT_SCREENSHOT_VIEWPORT: Viewport = {
@@ -215,9 +215,9 @@ export class ScreenshotsManager extends BaseManager {
215
215
  const page = await this._browserContext.newPage();
216
216
  page.setViewport(viewport);
217
217
 
218
- await page.goto(url.toString(), { waitUntil: "load" });
218
+ await page.goto(url.toString(), { waitUntil: ["load", "networkidle0"] });
219
219
  await page.waitForSelector(SLICE_SIMULATOR_WAIT_FOR_SELECTOR, {
220
- timeout: SLICE_SIMULATOR_WAIT_FOR_TIMEOUT,
220
+ timeout: SLICE_SIMULATOR_WAIT_FOR_SELECTOR_TIMEOUT,
221
221
  });
222
222
 
223
223
  const element = await page.$(SLICE_SIMULATOR_SCREENSHOT_SELECTOR);
@@ -296,7 +296,9 @@ export class ScreenshotsManager extends BaseManager {
296
296
  args: ScreenshotsManagerDeleteScreenshotFolderArgs,
297
297
  ): Promise<void> {
298
298
  const res = await this._fetch({
299
- body: { sliceId: args.sliceID },
299
+ // We're sending `args.sliceID` as `sliceName` because it's inconsistently
300
+ // named in the ACL Provider API.
301
+ body: { sliceName: args.sliceID },
300
302
  method: "POST",
301
303
  url: new URL("delete-folder", API_ENDPOINTS.AwsAclProvider),
302
304
  });