363 lines
11 KiB
JavaScript
363 lines
11 KiB
JavaScript
"use strict";
|
|
|
|
(function initPageProbe() {
|
|
const EVENT_SOURCE = "S3_CAPTURE_PROBE";
|
|
const GRAPHQL_URL_FRAGMENT = "surfaces-graphql.splice.com/graphql";
|
|
let lastAudioResponseUrl = "";
|
|
|
|
function emit(kind, payload) {
|
|
try {
|
|
window.postMessage(
|
|
{
|
|
source: EVENT_SOURCE,
|
|
kind,
|
|
payload: payload || {}
|
|
},
|
|
"*"
|
|
);
|
|
} catch (error) {
|
|
// Ignore probe emission errors.
|
|
}
|
|
}
|
|
|
|
function safeString(value) {
|
|
return typeof value === "string" ? value : "";
|
|
}
|
|
|
|
function normalizeUrl(rawUrl) {
|
|
try {
|
|
return new URL(rawUrl, window.location.href).toString();
|
|
} catch (error) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function normalizeFileName(raw) {
|
|
if (typeof raw !== "string") {
|
|
return "";
|
|
}
|
|
const trimmed = raw.trim();
|
|
if (!trimmed) {
|
|
return "";
|
|
}
|
|
const parts = trimmed.split("/");
|
|
return parts[parts.length - 1] || trimmed;
|
|
}
|
|
|
|
function extractSampleIdFromUrl(url) {
|
|
try {
|
|
const parsed = new URL(url);
|
|
const marker = "/audio_samples/";
|
|
const idx = parsed.pathname.indexOf(marker);
|
|
if (idx < 0) {
|
|
return "";
|
|
}
|
|
const rest = parsed.pathname.slice(idx + marker.length);
|
|
const parts = rest.split("/").filter(Boolean);
|
|
const first = (parts[0] || "").replace(/-scrambled$/i, "");
|
|
const second = (parts[1] || "").split(".")[0];
|
|
const hashRe = /^[a-f0-9]{64}$/i;
|
|
if (hashRe.test(first)) {
|
|
return first.toLowerCase();
|
|
}
|
|
if (hashRe.test(second)) {
|
|
return second.toLowerCase();
|
|
}
|
|
return "";
|
|
} catch (error) {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function extractCoverImageFromGraphqlItem(item) {
|
|
if (!item || typeof item !== "object") {
|
|
return "";
|
|
}
|
|
const ownFiles = Array.isArray(item.files) ? item.files : [];
|
|
for (const file of ownFiles) {
|
|
if (!file || typeof file !== "object") {
|
|
continue;
|
|
}
|
|
if (String(file.asset_file_type_slug || "").toLowerCase() !== "cover_image") {
|
|
continue;
|
|
}
|
|
const url = normalizeUrl(file.url || "");
|
|
if (url) {
|
|
return url;
|
|
}
|
|
}
|
|
const parents = item.parents && Array.isArray(item.parents.items) ? item.parents.items : [];
|
|
for (const parent of parents) {
|
|
const files = parent && Array.isArray(parent.files) ? parent.files : [];
|
|
for (const file of files) {
|
|
if (!file || typeof file !== "object") {
|
|
continue;
|
|
}
|
|
if (String(file.asset_file_type_slug || "").toLowerCase() !== "cover_image") {
|
|
continue;
|
|
}
|
|
const url = normalizeUrl(file.url || "");
|
|
if (url) {
|
|
return url;
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function extractPackNameFromGraphqlItem(item) {
|
|
if (!item || typeof item !== "object" || !item.parents || !Array.isArray(item.parents.items)) {
|
|
return "";
|
|
}
|
|
for (const parent of item.parents.items) {
|
|
if (!parent || typeof parent !== "object") {
|
|
continue;
|
|
}
|
|
const name = typeof parent.name === "string" ? parent.name.trim() : "";
|
|
if (name) {
|
|
return name;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function extractPreviewContextsFromGraphql(json) {
|
|
const contexts = [];
|
|
const items = json &&
|
|
json.data &&
|
|
json.data.assetsSearch &&
|
|
Array.isArray(json.data.assetsSearch.items)
|
|
? json.data.assetsSearch.items
|
|
: [];
|
|
for (const item of items) {
|
|
if (!item || typeof item !== "object") {
|
|
continue;
|
|
}
|
|
const sampleId = typeof item.uuid === "string" ? item.uuid.toLowerCase() : "";
|
|
const itemName = normalizeFileName(String(item.name || ""));
|
|
const itemKey = typeof item.key === "string" ? item.key.trim().toUpperCase() : "";
|
|
const itemBpm = item.bpm == null ? "" : String(item.bpm).trim();
|
|
const itemTags = Array.isArray(item.tags)
|
|
? Array.from(new Set(item.tags.map((tag) => (tag && tag.label ? String(tag.label).trim() : "")).filter(Boolean)))
|
|
: [];
|
|
const itemCoverImageUrl = extractCoverImageFromGraphqlItem(item);
|
|
const itemPackName = extractPackNameFromGraphqlItem(item);
|
|
const files = Array.isArray(item.files) ? item.files : [];
|
|
for (const file of files) {
|
|
if (!file || typeof file !== "object") {
|
|
continue;
|
|
}
|
|
const typeSlug = String(file.asset_file_type_slug || "").toLowerCase();
|
|
const fileUrl = normalizeUrl(file.url || "");
|
|
if (typeSlug !== "preview_mp3" || !fileUrl) {
|
|
continue;
|
|
}
|
|
contexts.push({
|
|
sampleId: sampleId || extractSampleIdFromUrl(fileUrl),
|
|
previewUrl: fileUrl,
|
|
fileName: itemName,
|
|
key: itemKey,
|
|
bpm: itemBpm,
|
|
tags: itemTags,
|
|
coverImageUrl: itemCoverImageUrl,
|
|
packName: itemPackName
|
|
});
|
|
}
|
|
}
|
|
return contexts;
|
|
}
|
|
|
|
function emitGraphqlContextsFromJson(json) {
|
|
const contexts = extractPreviewContextsFromGraphql(json);
|
|
if (contexts.length) {
|
|
emit("graphql-contexts", { contexts });
|
|
}
|
|
}
|
|
|
|
function markLastAudioUrl(url, contentType) {
|
|
const maybeUrl = safeString(url);
|
|
const maybeType = safeString(contentType).toLowerCase();
|
|
if (!maybeUrl) {
|
|
return;
|
|
}
|
|
if (maybeType.startsWith("audio/") || maybeUrl.includes("audio_samples/")) {
|
|
lastAudioResponseUrl = maybeUrl;
|
|
}
|
|
}
|
|
|
|
function patchFetch() {
|
|
if (typeof window.fetch !== "function") {
|
|
return;
|
|
}
|
|
const currentFetch = window.fetch;
|
|
if (currentFetch.__s3CapturePatched) {
|
|
return;
|
|
}
|
|
|
|
const wrappedFetch = function wrappedFetch(input, init) {
|
|
const requestUrl =
|
|
typeof input === "string"
|
|
? input
|
|
: input && typeof input.url === "string"
|
|
? input.url
|
|
: "";
|
|
|
|
emit("fetch-request", { requestUrl });
|
|
|
|
return currentFetch.call(this, input, init).then(
|
|
(response) => {
|
|
const responseUrl = response && typeof response.url === "string" ? response.url : "";
|
|
const contentType = response && response.headers ? response.headers.get("content-type") : "";
|
|
markLastAudioUrl(responseUrl, contentType);
|
|
emit("fetch-response", {
|
|
requestUrl,
|
|
responseUrl,
|
|
status: response ? response.status : 0,
|
|
contentType: contentType || ""
|
|
});
|
|
if (requestUrl && requestUrl.includes(GRAPHQL_URL_FRAGMENT) && response && typeof response.clone === "function") {
|
|
response.clone().json().then((json) => {
|
|
emitGraphqlContextsFromJson(json);
|
|
}).catch(() => {
|
|
// Ignore non-JSON GraphQL edge cases.
|
|
});
|
|
}
|
|
return response;
|
|
},
|
|
(error) => {
|
|
emit("fetch-error", {
|
|
requestUrl,
|
|
error: error && error.message ? error.message : "fetch failed"
|
|
});
|
|
throw error;
|
|
}
|
|
);
|
|
};
|
|
|
|
wrappedFetch.__s3CapturePatched = true;
|
|
window.fetch = wrappedFetch;
|
|
}
|
|
|
|
function patchXhr() {
|
|
if (!window.XMLHttpRequest || !window.XMLHttpRequest.prototype) {
|
|
return;
|
|
}
|
|
|
|
const proto = window.XMLHttpRequest.prototype;
|
|
if (proto.open.__s3CapturePatched) {
|
|
return;
|
|
}
|
|
|
|
const originalOpen = proto.open;
|
|
const originalSend = proto.send;
|
|
|
|
proto.open = function patchedOpen(method, url) {
|
|
this.__s3CaptureRequestUrl = safeString(url);
|
|
emit("xhr-open", {
|
|
method: safeString(method),
|
|
requestUrl: this.__s3CaptureRequestUrl
|
|
});
|
|
return originalOpen.apply(this, arguments);
|
|
};
|
|
proto.open.__s3CapturePatched = true;
|
|
|
|
proto.send = function patchedSend() {
|
|
const xhr = this;
|
|
xhr.addEventListener(
|
|
"loadend",
|
|
function onLoadEnd() {
|
|
const responseUrl = safeString(xhr.responseURL);
|
|
const contentType = safeString(xhr.getResponseHeader("content-type"));
|
|
markLastAudioUrl(responseUrl || xhr.__s3CaptureRequestUrl, contentType);
|
|
emit("xhr-response", {
|
|
requestUrl: safeString(xhr.__s3CaptureRequestUrl),
|
|
responseUrl,
|
|
status: xhr.status || 0,
|
|
contentType
|
|
});
|
|
const requestUrl = safeString(xhr.__s3CaptureRequestUrl);
|
|
if (!requestUrl || !requestUrl.includes(GRAPHQL_URL_FRAGMENT)) {
|
|
return;
|
|
}
|
|
try {
|
|
let json = null;
|
|
if (xhr.responseType === "json" && xhr.response && typeof xhr.response === "object") {
|
|
json = xhr.response;
|
|
} else {
|
|
if (xhr.responseType && xhr.responseType !== "text" && xhr.responseType !== "") {
|
|
return;
|
|
}
|
|
const text = safeString(xhr.responseText);
|
|
if (!text) {
|
|
return;
|
|
}
|
|
json = JSON.parse(text);
|
|
}
|
|
emitGraphqlContextsFromJson(json);
|
|
} catch (error) {
|
|
// Ignore non-JSON GraphQL edge cases.
|
|
}
|
|
},
|
|
{ once: true }
|
|
);
|
|
return originalSend.apply(this, arguments);
|
|
};
|
|
}
|
|
|
|
function patchCreateObjectURL() {
|
|
if (!window.URL || typeof window.URL.createObjectURL !== "function") {
|
|
return;
|
|
}
|
|
const current = window.URL.createObjectURL;
|
|
if (current.__s3CapturePatched) {
|
|
return;
|
|
}
|
|
|
|
const wrapped = function wrappedCreateObjectURL(blob) {
|
|
const objectUrl = current.call(this, blob);
|
|
const blobType = blob && typeof blob.type === "string" ? blob.type : "";
|
|
const blobSize = blob && typeof blob.size === "number" ? blob.size : 0;
|
|
emit("blob-created", {
|
|
blobUrl: objectUrl,
|
|
blobType,
|
|
blobSize,
|
|
linkedUrl: lastAudioResponseUrl
|
|
});
|
|
return objectUrl;
|
|
};
|
|
|
|
wrapped.__s3CapturePatched = true;
|
|
window.URL.createObjectURL = wrapped;
|
|
}
|
|
|
|
function patchDecodeAudioData(ctor) {
|
|
if (!ctor || !ctor.prototype || typeof ctor.prototype.decodeAudioData !== "function") {
|
|
return;
|
|
}
|
|
const current = ctor.prototype.decodeAudioData;
|
|
if (current.__s3CapturePatched) {
|
|
return;
|
|
}
|
|
|
|
const wrapped = function wrappedDecodeAudioData(audioData) {
|
|
const byteLength =
|
|
audioData && typeof audioData.byteLength === "number" ? audioData.byteLength : 0;
|
|
emit("decode-audio-data", {
|
|
byteLength,
|
|
linkedUrl: lastAudioResponseUrl
|
|
});
|
|
return current.apply(this, arguments);
|
|
};
|
|
|
|
wrapped.__s3CapturePatched = true;
|
|
ctor.prototype.decodeAudioData = wrapped;
|
|
}
|
|
|
|
patchFetch();
|
|
patchXhr();
|
|
patchCreateObjectURL();
|
|
patchDecodeAudioData(window.AudioContext);
|
|
patchDecodeAudioData(window.OfflineAudioContext);
|
|
emit("probe-ready", { href: window.location.href });
|
|
})();
|