Created
October 19, 2023 11:13
-
-
Save boeboe/8d8d322a255e13cddbee426801093e03 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var _process, _process$env; | |
function ownKeys(object, enumerableOnly) { | |
var keys = Object.keys(object); | |
if (Object.getOwnPropertySymbols) { | |
var symbols = Object.getOwnPropertySymbols(object); | |
enumerableOnly && (symbols = symbols.filter(function(sym) { | |
return Object.getOwnPropertyDescriptor(object, sym).enumerable | |
})), | |
keys.push.apply(keys, symbols) | |
} | |
return keys | |
} | |
function _objectSpread(target) { | |
for (var i = 1; i < arguments.length; i++) { | |
var source = null != arguments[i] ? arguments[i] : {}; | |
i % 2 ? ownKeys(Object(source), !0).forEach(function(key) { | |
_defineProperty(target, key, source[key]) | |
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function(key) { | |
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)) | |
}) | |
} | |
return target | |
} | |
function _defineProperty(obj, key, value) { | |
return key in obj ? Object.defineProperty(obj, key, { | |
value, | |
enumerable: !0, | |
configurable: !0, | |
writable: !0 | |
}) : obj[key] = value, | |
obj | |
} | |
self.__serviceWorkerCacheStoragePrefix = "https://slack.com/fake-url-for-service-worker-item-storage", | |
self.__serviceWorkerCacheKey = "gantry-1697711245", | |
self.__serviceWorkerManifest = [{ | |
url: "https://a.slack-edge.com/bv1-10/lato-italic-5bbcd6c.woff2" | |
}, { | |
url: "https://a.slack-edge.com/bv1-10/NotoSansTC-Regular.min-9f3f03b.woff2" | |
}]; | |
let CACHE_KEY = self.__serviceWorkerCacheKey || "dev"; | |
const CACHE_STORAGE_PREFIX = self.__serviceWorkerCacheStoragePrefix || "" | |
, CACHE_KEY_PREFIX = "gantry-" | |
, CACHE_KEY_QS_NAME = "cacheKey" | |
, shouldRecordTelemetry = Math.floor(101 * Math.random()) < 5; | |
let shouldTrace = Math.floor(101 * Math.random()) < 5; | |
const MAXIMUM_CACHE_AGE = 345600 | |
, ACCEPTABLE_CACHE_AGE = 86400 | |
, MINIMUM_BUCKETS = 3 | |
, MAXIMUM_BUCKETS = 5 | |
, FAKE_SERVICE_WORKER_ENDPOINT = "/api/api.test?service-worker-test" | |
, MAX_REQUESTS_IN_FLIGHT = 4 | |
, gantryClientURLMatch = /app\.(?:[^.]+\.)?(slack|slack-gov).com\/([a-z-0-9]+)\/T[A-Z0-9]{8,}/; | |
let simpleMetrics, simpleTracer, manifestURLs = self.__serviceWorkerManifest && self.__serviceWorkerManifest.map(entry=>entry.url), selfProfilingEnabled = !1; | |
const SELF_PROFILING_KEY = "self-profiling" | |
, idbKeyval = (()=>{ | |
let dbInstance; | |
const DB_NAME = "serviceworkerFlags" | |
, OBJECT_STORE_NAME = "keyval"; | |
async function withStore(type, callback) { | |
const db = await (dbInstance || (dbInstance = new Promise((resolve,reject)=>{ | |
const openreq = indexedDB.open(DB_NAME, 1); | |
openreq.onerror = (()=>{ | |
reject(openreq.error) | |
} | |
), | |
openreq.onupgradeneeded = (()=>{ | |
openreq.result.createObjectStore(OBJECT_STORE_NAME) | |
} | |
), | |
openreq.onsuccess = (()=>{ | |
resolve(openreq.result) | |
} | |
) | |
} | |
)), | |
dbInstance); | |
return new Promise((resolve,reject)=>{ | |
const transaction = db.transaction(OBJECT_STORE_NAME, type); | |
transaction.oncomplete = (()=>resolve()), | |
transaction.onerror = (()=>reject(transaction.error)), | |
callback(transaction.objectStore(OBJECT_STORE_NAME)) | |
} | |
) | |
} | |
return { | |
async get(key) { | |
let request; | |
return await withStore("readonly", store=>{ | |
request = store.get(key) | |
} | |
), | |
request.result | |
}, | |
set: (key,value)=>withStore("readwrite", store=>{ | |
store.put(value, key) | |
} | |
), | |
delete: key=>withStore("readwrite", store=>{ | |
store.delete(key) | |
} | |
) | |
} | |
} | |
)(); | |
async function messageHandler(event) { | |
if (event.data && "self_profiling" === event.data.type) { | |
const isEnabled = !0 === event.data.enabled; | |
selfProfilingEnabled = isEnabled; | |
try { | |
isEnabled ? await idbKeyval.set(SELF_PROFILING_KEY, 1) : await idbKeyval.delete(SELF_PROFILING_KEY) | |
} catch (e) { | |
log(event, "error setting self_profiling_key", e) | |
} | |
} else if (event.data && "canvas_prefetch" === event.data.type) { | |
const fetches = maxParallelIterator(event.data.urls, url=>dipAndCache(url, event).then(result=>!!result || fetchAndCache(url, event)), MAX_REQUESTS_IN_FLIGHT); | |
try { | |
const responses = await Promise.all(fetches); | |
log(event, "All canvas files fetched", responses) | |
} catch (err) { | |
log(event, "Error fetching canvas files for cache", err) | |
} | |
} | |
} | |
function errorHandler(event) { | |
if (log(event, `errorHandler triggered for event, type "${null == event ? void 0 : event.type}" error "${null == event ? void 0 : event.message}"`), | |
simpleTracer && shouldTrace) { | |
const errorSpan = simpleTracer.startSpan("sw:error_event"); | |
errorSpan.addTags({ | |
state: null == event ? void 0 : event.type, | |
error: null == event ? void 0 : event.message | |
}), | |
errorSpan.close(), | |
simpleTracer.report([errorSpan]) | |
} else | |
log(event, `Not firing traces for this error, tracer exists: ${!!simpleTracer}, shouldTrace: ${!!shouldTrace}`) | |
} | |
function installHandler(event) { | |
const installStart = performance.now(); | |
log(event, "install event", event.target); | |
try { | |
simpleMetrics = new SimpleMetrics(event), | |
simpleTracer = getSimpleTracer(event) | |
} catch (e) { | |
log(event, "Error instantiating metrics", e) | |
} | |
const installSpan = simpleTracer.startSpan("sw:install"); | |
return installSpan.addTags({ | |
asset_volume: manifestURLs.length | |
}), | |
simpleMetrics && shouldRecordTelemetry && (simpleMetrics.recordCount("sw_install"), | |
simpleMetrics.recordCount("sw_cached_asset_volume", manifestURLs.length), | |
getValidCacheKeys().then(keys=>{ | |
simpleMetrics.recordCount("sw_cache_bucket_count", keys.length) | |
} | |
)), | |
messageClients("installStart"), | |
getValidCacheKeys().then(cacheKeys=>{ | |
if (installSpan.addTags({ | |
cache_bucket_count: cacheKeys.length | |
}), | |
cacheKeys.length > 0) { | |
if ("1" === new URL(event.target.location.href).searchParams.get("new_enough_acceptable")) { | |
const currTime = Math.round(Date.now() / 1e3); | |
if (cacheKeys.map(key=>parseInt(key.replace(/[^\d]+/, ""), 10)).filter(key=>!isNaN(key)).sort().reverse()[0] > currTime - ACCEPTABLE_CACHE_AGE) | |
return simpleMetrics && shouldRecordTelemetry && simpleMetrics.recordCount("sw_install_new_enough_acceptable"), | |
log(event, "Skipped fetching assets for cache because what we have is new enough"), | |
Promise.resolve({}); | |
simpleMetrics.recordCount("sw_install_not_new_enough_acceptable"), | |
log(event, "Not skipping fetching assets because cached assets are too old") | |
} | |
} | |
const fetches = maxParallelIterator(manifestURLs, url=>dipAndCache(url, event).then(result=>!!result || fetchAndCache(url, event)), MAX_REQUESTS_IN_FLIGHT); | |
return Promise.all(fetches).then(responses=>{ | |
const installDuration = performance.now() - installStart; | |
return simpleMetrics && shouldRecordTelemetry && simpleMetrics.recordTiming("sw_install_timing", installDuration), | |
log(event, "All files fetched and put in cache", `${installDuration}ms`), | |
messageClients("installEnd"), | |
self.skipWaiting(), | |
responses | |
} | |
).catch(err=>{ | |
log(event, "Error fetching all files for cache", err), | |
messageClients("installEnd"), | |
shouldTrace && (installSpan.addTags({ | |
state: "error", | |
error: null == err ? void 0 : err.message | |
}), | |
installSpan.close(), | |
simpleTracer.report([installSpan])) | |
} | |
) | |
} | |
) | |
} | |
async function activateHandler(event) { | |
let activateSpan, timer; | |
log(event, "activate event", event), | |
simpleMetrics && shouldRecordTelemetry && simpleMetrics.recordCount("sw_activate"), | |
simpleTracer && shouldTrace && (activateSpan = simpleTracer.startSpan("sw:activate")), | |
clients.claim(); | |
const setFlagStateWithTimeout = Promise.race([idbKeyval.get(SELF_PROFILING_KEY), new Promise(resolve=>{ | |
timer = setTimeout(()=>{ | |
simpleMetrics && simpleMetrics.recordCount("sw_idb_timeout"), | |
resolve(0) | |
} | |
, 500) | |
} | |
)]).then(value=>{ | |
clearTimeout(timer), | |
selfProfilingEnabled = 1 === value | |
} | |
, ()=>{ | |
selfProfilingEnabled = !1 | |
} | |
); | |
return event.waitUntil(setFlagStateWithTimeout), | |
getInvalidCacheKeys().then(cacheKeys=>deleteCacheKeys(event, cacheKeys, activateSpan)) | |
} | |
function fetchHandler(event) { | |
let fetchSpan; | |
const spans = []; | |
simpleTracer && shouldTrace && (fetchSpan = simpleTracer.startSpan("sw:fetch"), | |
spans.push(fetchSpan)); | |
try { | |
var _event$request; | |
const fetchStart = performance.now() | |
, requestURL = null === (_event$request = event.request) || void 0 === _event$request ? void 0 : _event$request.url; | |
if ("string" != typeof requestURL) | |
throw simpleTracer && shouldTrace && (fetchSpan.addTags({ | |
state: "error", | |
url: requestURL, | |
reason: "invalid url string" | |
}), | |
fetchSpan.close(), | |
simpleTracer.report(spans)), | |
new Error(`${requestURL} is not a valid url string`); | |
if (requestURL.endsWith(FAKE_SERVICE_WORKER_ENDPOINT)) { | |
const fakeServiceWorkerEndpointResponse = { | |
ok: !0, | |
totes_fake: !0 | |
}; | |
return simpleTracer && shouldTrace && (fetchSpan.addTags({ | |
state: "success", | |
url: requestURL, | |
reason: "fake_sw_endpoint" | |
}), | |
fetchSpan.close(), | |
simpleTracer.report(spans)), | |
event.respondWith(Promise.resolve(new Response(JSON.stringify(fakeServiceWorkerEndpointResponse),{ | |
headers: { | |
"Content-Type": "application/json" | |
} | |
}))) | |
} | |
const parsedFetchURL = new URL(requestURL) | |
, fetchURL = requestURL.split("?")[0] | |
, cacheMatchRequest = new Request(fetchURL); | |
if (fetchURL.match(gantryClientURLMatch)) | |
return handleInitialPageFetch(event); | |
if (!doesExistInManifest(event, fetchURL) && !isCachedCanvasAsset(event, fetchURL)) | |
return Promise.resolve(); | |
const cacheKey = parsedFetchURL.searchParams.get(CACHE_KEY_QS_NAME) || CACHE_KEY; | |
return event.respondWith(caches.open(cacheKey).then(cache=>cache.match(cacheMatchRequest)).then(response=>clients.get(event.clientId).then(client=>{ | |
const url = client ? client.url : event.request.url | |
, {preventCachedResponse, preventCachedResponseForDev} = shouldGetCachedResponse(url); | |
if (response && !preventCachedResponse) { | |
if (!hasCacheBucketExpired()) { | |
if (log(event, "responding from cache", fetchURL), | |
simpleMetrics && shouldRecordTelemetry && simpleMetrics.recordTiming("sw_fetch_hit_timing", performance.now() - fetchStart), | |
selfProfilingEnabled) { | |
const clonedHeaders = new Headers(response.headers); | |
return clonedHeaders.append("document-policy", "js-profiling"), | |
new Response(response.body,{ | |
status: response.status, | |
statusText: response.statusText, | |
headers: clonedHeaders | |
}) | |
} | |
return response | |
} | |
log(event, "asset is too old to use", fetchURL), | |
simpleMetrics && shouldRecordTelemetry && simpleMetrics.recordCount("sw_too_old_cache_hit"), | |
simpleTracer && shouldTrace && fetchSpan.addTags({ | |
asset_expired: !0 | |
}) | |
} | |
let fetchRequestSpan; | |
var _fetchSpan, _fetchSpan2; | |
(log(event, `responding with fetch ${response && preventCachedResponse ? ` (${preventCachedResponseForDev ? "because this is dev" : "because of force_cold_boot"})` : ""}`, fetchURL), | |
simpleTracer && shouldTrace) && (fetchRequestSpan = simpleTracer.startSpan("sw:fetch_request", { | |
parentSpanId: null === (_fetchSpan = fetchSpan) || void 0 === _fetchSpan ? void 0 : _fetchSpan.id, | |
traceId: null === (_fetchSpan2 = fetchSpan) || void 0 === _fetchSpan2 ? void 0 : _fetchSpan2.traceId | |
})); | |
return fetch(event.request.clone()).catch(err=>(log(event, "ERROR updating cache from network", err.message), | |
simpleTracer && shouldTrace && (fetchRequestSpan.addTags({ | |
state: "error", | |
error: null == err ? void 0 : err.message, | |
reason: "error updating cache from network" | |
}), | |
fetchRequestSpan.close(), | |
spans.push(fetchRequestSpan)), | |
Promise.reject(err))) | |
} | |
)).catch(err=>(log(event, "ERROR opening and matching in cache", err.message), | |
simpleTracer && shouldTrace && (fetchSpan.addTags({ | |
state: "error", | |
error: null == err ? void 0 : err.message, | |
reason: "error opening and matching cache" | |
}), | |
fetchSpan.close(), | |
simpleTracer.report(spans)), | |
fetch(event.request.url)))) | |
} catch (e) { | |
return log(event, "ERROR exception thrown in the fetch handler", e.message, event), | |
simpleTracer && shouldTrace && (fetchSpan.addTags({ | |
state: "error", | |
error: null == e ? void 0 : e.message, | |
reason: "fetch handler error" | |
}), | |
fetchSpan.close(), | |
simpleTracer.report(spans)), | |
fetch(event.request.url) | |
} | |
} | |
function handleInitialPageFetch(event) { | |
var _event$target, _event$target$locatio; | |
const requestURL = event.request.url | |
, parsedFetchURL = new URL(requestURL) | |
, fetchURL = requestURL.split("?")[0] | |
, origin = null == event ? void 0 : null === (_event$target = event.target) || void 0 === _event$target ? void 0 : null === (_event$target$locatio = _event$target.location) || void 0 === _event$target$locatio ? void 0 : _event$target$locatio.origin | |
, isDev = !(null == origin || !origin.match(/dev[0-9]*/)) | |
, isQa = !(null == origin || !origin.match(/qa[0-9]*/)); | |
log(event, "normalizing request URL for cache", fetchURL); | |
const isNumberedDev = !(null == origin || !origin.match(/dev[0-9]+\.*/)) | |
, isNumberedQa = !(null == origin || !origin.match(/qa[0-9]+\.*/)); | |
let fetchURLPath = "/boot/"; | |
(isDev || isQa) && (fetchURLPath = "/dev-cdn/buildy/assets/"), | |
(isNumberedDev || isNumberedQa) && (fetchURLPath = "/dev-cdn/buildy/js/static/.buildy/"); | |
const isJsPath = !!parsedFetchURL.searchParams.get("js_path") | |
, urlMatch = fetchURL.match(gantryClientURLMatch); | |
return event.respondWith(async function() { | |
let appName = urlMatch[2]; | |
const spaceParam = parsedFetchURL.searchParams.get("space"); | |
"docs" === appName && "1" === spaceParam && (appName = "quip"); | |
const experimentSoGv2StorageKey = `${CACHE_STORAGE_PREFIX}/is_sonic_on_gantry_v2_enabled`; | |
if ("client" === appName) | |
try { | |
const cache = await caches.open(CACHE_KEY) | |
, response = await cache.match(experimentSoGv2StorageKey); | |
response && await response.json() && (log(event, "Sonic on Gantry-v2 Enabled, serving client-v2.html"), | |
appName = "client-v2") | |
} catch (e) { | |
log(event, "Failed to retrieve experiment state from cache", experimentSoGv2StorageKey, e), | |
appName = "client" | |
} | |
const fetchURLFilename = getBootFileNameForGantryApp(appName, isJsPath) | |
, normalizedURL = `${fetchURLPath}${fetchURLFilename}` | |
, cacheMatchRequest = new Request(normalizedURL) | |
, cacheKey = parsedFetchURL.searchParams.get(CACHE_KEY_QS_NAME) || CACHE_KEY | |
, {preventCachedResponse, preventCachedResponseForDev} = shouldGetCachedResponse(requestURL); | |
if (!doesExistInManifest(event, normalizedURL)) | |
return fetch(event.request.clone()); | |
if (preventCachedResponse || preventCachedResponseForDev) | |
return log(event, `responding with fetch (${preventCachedResponseForDev ? "because this is dev" : "because of force_cold_boot"})`, normalizedURL), | |
fetch(event.request.clone()); | |
try { | |
const cache = await caches.open(cacheKey) | |
, response = await cache.match(cacheMatchRequest); | |
if (response) { | |
if (!hasCacheBucketExpired()) { | |
if (log(event, "responding from cache (normalized)", normalizedURL), | |
selfProfilingEnabled) { | |
const clonedHeaders = new Headers(response.headers); | |
return clonedHeaders.append("document-policy", "js-profiling"), | |
new Response(response.body,{ | |
status: response.status, | |
statusText: response.statusText, | |
headers: clonedHeaders | |
}) | |
} | |
return response | |
} | |
log(event, "asset is too old to use", normalizedURL) | |
} | |
log(event, "responding with fetch", fetchURL); | |
try { | |
return fetch(event.request.clone()) | |
} catch (err) { | |
throw log(event, "ERROR updating cache from network", err.message), | |
err | |
} | |
} catch (err) { | |
return log(event, "ERROR opening and matching in cache", err.message), | |
fetch(event.request.clone()) | |
} | |
}()) | |
} | |
function isCachedCanvasAsset(event, normalizedURL) { | |
const isCanvasAsset = /canvas_blob/.test(normalizedURL); | |
return isCanvasAsset || debug(event, `requested path ${normalizedURL} is not a canvas cached asset`), | |
isCanvasAsset | |
} | |
function doesExistInManifest(event, normalizedURL) { | |
const manifestMatchURL = 0 === normalizedURL.indexOf("http") ? new URL(normalizedURL).pathname : normalizedURL; | |
return !manifestURLs.every(url=>!url.includes(manifestMatchURL)) || (debug(event, `requested path ${manifestMatchURL} is not in the manifest, exiting early`), | |
!1) | |
} | |
function shouldGetCachedResponse(url) { | |
var _parsedURL$origin; | |
const parsedURL = new URL(url) | |
, isDev = !(null === (_parsedURL$origin = parsedURL.origin) || void 0 === _parsedURL$origin || !_parsedURL$origin.match(/dev[0-9]*/)) | |
, forceColdBootParam = parsedURL.searchParams.get("force_cold_boot") | |
, isJsPath = !!parsedURL.searchParams.get("js_path") | |
, forcingWarmBoot = "0" === forceColdBootParam | |
, forcingColdBootForDev = isDev && !forcingWarmBoot; | |
return { | |
preventCachedResponse: "1" === forceColdBootParam || forcingColdBootForDev || isJsPath && !forcingWarmBoot, | |
preventCachedResponseForDev: forcingColdBootForDev | |
} | |
} | |
function getCacheKeys() { | |
return caches.keys().then(keys=>keys.filter(cacheKey=>cacheKey.startsWith(CACHE_KEY_PREFIX) && cacheKey !== CACHE_KEY).sort((a,b)=>a > b ? -1 : 1)) | |
} | |
function getValidCacheKeys() { | |
const currTime = Math.round(Date.now() / 1e3); | |
return getCacheKeys().then(keys=>keys.filter((cacheKey,index)=>index < MINIMUM_BUCKETS || !(index >= MAXIMUM_BUCKETS) && parseInt(cacheKey.replace(/[^\d]+/, ""), 10) > currTime - MAXIMUM_CACHE_AGE)) | |
} | |
function getInvalidCacheKeys() { | |
const currTime = Math.round(Date.now() / 1e3); | |
return getCacheKeys().then(keys=>keys.slice(MINIMUM_BUCKETS).filter((cacheKey,index)=>MINIMUM_BUCKETS + index >= MAXIMUM_BUCKETS || parseInt(cacheKey.replace(/[^\d]+/, ""), 10) < currTime - MAXIMUM_CACHE_AGE)) | |
} | |
let purgeExcessCacheKeysPromise; | |
function purgeExcessCacheKeys(event) { | |
return purgeExcessCacheKeysPromise || (log(event, "Removing old cache buckets because of a cache write failure"), | |
simpleMetrics && shouldRecordTelemetry && simpleMetrics.recordCount("sw_purge_excess_cache_due_to_failed_write"), | |
purgeExcessCacheKeysPromise = getCacheKeys().then(keys=>{ | |
const keysToPurge = keys.slice(MINIMUM_BUCKETS); | |
return deleteCacheKeys(event, keysToPurge) | |
} | |
).finally(()=>{ | |
log(event, "Removing old cache buckets complete!"), | |
purgeExcessCacheKeysPromise = void 0 | |
} | |
)), | |
purgeExcessCacheKeysPromise | |
} | |
function deleteCacheKeys(event, cacheKeys) { | |
let initialSpan = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : simpleTracer && shouldTrace ? simpleTracer.startSpan("sw:delete_cache_keys") : void 0; | |
const purgeSpans = [] | |
, cacheDeletions = cacheKeys.map(cacheKey=>{ | |
let purgeBucketSpan; | |
return simpleTracer && shouldTrace && (purgeBucketSpan = simpleTracer.startSpan("sw:purge_failed", { | |
traceId: null == initialSpan ? void 0 : initialSpan.traceId, | |
parentSpanId: null == initialSpan ? void 0 : initialSpan.id | |
})), | |
caches.delete(cacheKey).then(()=>{ | |
log(event, "purged cache of old cache bucket", cacheKey) | |
} | |
).catch(e=>{ | |
log(event, "failed to purge cache of old cache bucket", cacheKey, e), | |
simpleTracer && shouldTrace && (purgeBucketSpan.addTags({ | |
state: "error", | |
error: null == e ? void 0 : e.message | |
}), | |
purgeBucketSpan.close(), | |
purgeSpans.push(purgeBucketSpan)) | |
} | |
) | |
} | |
) | |
, cacheDeletionP = Promise.all(cacheDeletions); | |
return simpleTracer && shouldTrace && cacheDeletionP.finally(()=>{ | |
if (!purgeSpans.length) | |
return; | |
initialSpan && initialSpan.close(); | |
const spans = initialSpan ? [initialSpan, ...purgeSpans] : purgeSpans; | |
simpleTracer.report(spans) | |
} | |
), | |
cacheDeletionP | |
} | |
function dipAndCache(url, event) { | |
return url.match(/\.html$/) ? Promise.resolve(!1) : getValidCacheKeys().then(keys=>{ | |
if (0 === keys.length) | |
return Promise.resolve(!1); | |
const cacheKey = keys[0]; | |
log(event, `checking if asset is in an existing cache bucket: ${cacheKey}`, url); | |
const cacheMatchRequest = new Request(url,{ | |
credentials: "same-origin", | |
cache: "default" | |
}); | |
return Promise.all(keys.map(key=>caches.open(key))).then(openCaches=>Promise.all(openCaches.map(openCache=>openCache.match(cacheMatchRequest)))).then(matches=>{ | |
const match = matches.find(element=>!!element); | |
return match ? (log(event, "asset found in existing cache bucket, adding to new bucket", url), | |
caches.open(CACHE_KEY).then(cache=>(log(event, "putting asset in cache", url), | |
cache.put(cacheMatchRequest, match).catch(()=>{ | |
purgeExcessCacheKeys(event) | |
} | |
), | |
!0))) : (log(event, "asset not found in an existing cache bucket", url), | |
!1) | |
} | |
) | |
} | |
).catch(err=>{ | |
throw log(event, "ERROR dipping and caching asset", err), | |
err | |
} | |
) | |
} | |
function fetchAndCache(url, event) { | |
const requestCache = url.match(/\.html$/) ? "no-cache" : "default"; | |
if (!!url.match(/\/api\//)) | |
return log(event, "Caching API requests is not supported."), | |
null; | |
const request = new Request(url,{ | |
credentials: "same-origin", | |
cache: requestCache | |
}); | |
return log(event, "fetching asset", url), | |
fetch(request).then(networkResponse=>networkResponse.ok ? caches.open(CACHE_KEY).then(cache=>(log(event, "putting asset in cache", url), | |
cache.put(request, networkResponse).catch(error=>{ | |
throw purgeExcessCacheKeys(event), | |
error | |
} | |
))) : (log(event, "not caching asset as it returned a non 200 code", url), | |
null)).catch(err=>{ | |
throw log(event, "ERROR fetching and caching asset", err), | |
err | |
} | |
) | |
} | |
function getBootFileNameForGantryApp(appName, isJsPath) { | |
return `${appName}${isJsPath ? ".jspath" : ""}.html` | |
} | |
function hasCacheBucketExpired() { | |
const currentTime = Math.round(Date.now() / 1e3); | |
let cacheBucketTimeStamp; | |
try { | |
cacheBucketTimeStamp = parseInt(CACHE_KEY.replace(CACHE_KEY_PREFIX, ""), 10) | |
} catch (e) { | |
cacheBucketTimeStamp = Math.round(Date.now() / 1e3) | |
} | |
return navigator.onLine && cacheBucketTimeStamp < currentTime - MAXIMUM_CACHE_AGE | |
} | |
function messageClients(msg) { | |
clients.matchAll({ | |
includeUncontrolled: !0 | |
}).then(clients=>{ | |
clients.forEach(client=>{ | |
client.postMessage(msg) | |
} | |
) | |
} | |
) | |
} | |
self.addEventListener("install", installHandler), | |
self.addEventListener("error", errorHandler), | |
self.addEventListener("activate", activateHandler), | |
self.addEventListener("fetch", fetchHandler), | |
self.addEventListener("message", messageHandler); | |
const enableFeatureForUser = percentage=>Math.floor(100 * Math.random()) < percentage; | |
function log(event) { | |
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) | |
args[_key - 1] = arguments[_key]; | |
return outputToConsole("info", event, ...args) | |
} | |
function debug(event) { | |
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) | |
args[_key2 - 1] = arguments[_key2]; | |
return outputToConsole("debug", event, ...args) | |
} | |
function outputToConsole(type, event) { | |
for (var _len3 = arguments.length, args = new Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) | |
args[_key3 - 2] = arguments[_key3]; | |
return clients.get(event.clientId).then(client=>{ | |
var _event$target2; | |
let url; | |
if (client ? url = client.url : null != event && event.request ? url = event.request.url : null != event && null !== (_event$target2 = event.target) && void 0 !== _event$target2 && _event$target2.location && (url = event.target.location.href), | |
!url) | |
return !1; | |
if (shouldLog(url, type)) { | |
const logDate = makeLogDate(); | |
return console["info" === type ? "log" : type](`${logDate} [SERVICE-WORKER]`, ...args), | |
!0 | |
} | |
return !1 | |
} | |
) | |
} | |
function shouldLog(urlString, forType) { | |
const CONSOLE_FNS = { | |
0: "debug", | |
1: "info", | |
2: "warn", | |
3: "error" | |
} | |
, forTypeIdx = Object.keys(CONSOLE_FNS).findIndex(typeIdx=>CONSOLE_FNS[typeIdx] === forType) | |
, parsedUrl = new URL(urlString) | |
, isDev = parsedUrl.host.match(/dev[0-9]*/) | |
, logLevel = parsedUrl.searchParams.get("log_level") || null; | |
if (logLevel) { | |
return Object.keys(CONSOLE_FNS).findIndex(typeIdx=>CONSOLE_FNS[typeIdx] === logLevel) >= forTypeIdx | |
} | |
return isDev ? forTypeIdx >= 2 : forTypeIdx >= 1 | |
} | |
function makeLogDate() { | |
const date = new Date | |
, mo = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][date.getMonth()] | |
, d = date.getDate() | |
, time = { | |
h: date.getHours(), | |
mi: date.getMinutes(), | |
s: date.getSeconds(), | |
ms: date.getMilliseconds() | |
}; | |
return Object.keys(time).map(moment=>("ms" === moment ? time[moment] < 10 ? time[moment] = `${time[moment]}00` : time[moment] < 100 && (time[moment] = `${time[moment]}0`) : time[moment] < 10 && (time[moment] = `0 ${time[moment]}`), | |
null)), | |
`${mo}-${d} ${time.h}:${time.mi}:${time.s}.${time.ms}` | |
} | |
function shouldReportToDev(event) { | |
var _event$target3, _event$target3$locati; | |
const hostname = null == event ? void 0 : null === (_event$target3 = event.target) || void 0 === _event$target3 ? void 0 : null === (_event$target3$locati = _event$target3.location) || void 0 === _event$target3$locati ? void 0 : _event$target3$locati.hostname; | |
return (null == hostname ? void 0 : hostname.match(/(^|\.)(dev|qa)[0-9]*\./)) || (null == hostname ? void 0 : hostname.match(/(^|\.)slack-gov-dev.com$/)) | |
} | |
function isGovSlack(event) { | |
var _event$target4, _event$target4$locati; | |
const hostname = null == event ? void 0 : null === (_event$target4 = event.target) || void 0 === _event$target4 ? void 0 : null === (_event$target4$locati = _event$target4.location) || void 0 === _event$target4$locati ? void 0 : _event$target4$locati.hostname; | |
return (null == hostname ? void 0 : hostname.match(/(^|\.)slack-gov.com$/)) || (null == hostname ? void 0 : hostname.match(/(^|\.)slack-gov-dev.com$/)) | |
} | |
function getBeaconDomain(event) { | |
const isGov = isGovSlack(event); | |
return shouldReportToDev(event) ? isGov ? "slack-gov-dev.com" : "dev.slack.com" : isGov ? "slack-gov.com" : "slack.com" | |
} | |
function getBeaconUrl(event) { | |
return `https://${getBeaconDomain(event)}/beacon/timing` | |
} | |
function getTraceDomain(event) { | |
const isGov = isGovSlack(event); | |
return shouldReportToDev(event) ? isGov ? "slackb-gov-dev.com" : "dev.slackb.com" : isGov ? "slackb-gov.com" : "slackb.com" | |
} | |
function getTraceUrl(event) { | |
return `https://${getTraceDomain(event)}/traces/v1/list_of_spans/json` | |
} | |
const METRICS_POST_INTERVAL = 25e3 | |
, MAX_URL_LENGTH = 2e3; | |
class SimpleMetrics { | |
constructor(event) { | |
this.isEnabled = enableFeatureForUser(100), | |
this.beaconURL = getBeaconUrl(event), | |
this.metricsQueue = {}, | |
this.event = event, | |
log(this.event, "SimpleMetrics starting interval"), | |
setInterval(this.beaconMetrics.bind(this), METRICS_POST_INTERVAL) | |
} | |
beaconMetrics() { | |
if (!this.isEnabled) | |
return !1; | |
const metricsKeys = Object.keys(this.metricsQueue); | |
if (0 === metricsKeys.length) | |
return !1; | |
let data = []; | |
const urls = [] | |
, version = "dev" === CACHE_KEY ? CACHE_KEY : parseInt(CACHE_KEY.replace(CACHE_KEY_PREFIX, ""), 10) | |
, makeBeaconURL = beaconData=>`${this.beaconURL}?ver=${version}&data=${(items=>encodeURIComponent(items.join(";")))(beaconData)}`; | |
log(this.event, "SimpleMetrics beaconing interval", this.metricsQueue); | |
for (let i = 0; i < metricsKeys.length; i++) { | |
const label = metricsKeys[i] | |
, item = `${label}:${this.metricsQueue[label].join(",")}`; | |
makeBeaconURL(data.concat(item)).length <= MAX_URL_LENGTH ? data.push(item) : (urls.push(makeBeaconURL(data)), | |
data = [item]) | |
} | |
return data.length > 0 && urls.push(makeBeaconURL(data)), | |
log(this.event, "SimpleMetrics calling URLs", urls), | |
urls.forEach(url=>{ | |
const request = new Request(url,{ | |
mode: "no-cors" | |
}); | |
fetch(request) | |
} | |
), | |
this.metricsQueue = {}, | |
urls | |
} | |
recordCount(label) { | |
let value = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : 1; | |
if (!this.isEnabled) | |
return; | |
const metricsLabel = `${label}|count`; | |
this.metricsQueue[metricsLabel] || (this.metricsQueue[metricsLabel] = []), | |
this.metricsQueue[metricsLabel].push(value) | |
} | |
recordTiming(label, value) { | |
if (!this.isEnabled) | |
return; | |
const metricsLabel = `${label}|timing`; | |
this.metricsQueue[metricsLabel] || (this.metricsQueue[metricsLabel] = []), | |
this.metricsQueue[metricsLabel].push(Math.round(value)) | |
} | |
} | |
const TAG_TYPES = { | |
string: [0, "v_str"], | |
boolean: [1, "v_bool"], | |
number: [2, "v_int64"] | |
}; | |
function getSimpleTracer(event) { | |
return new SimpleTracer(event) | |
} | |
const BEACON_TRACE_INTERVAL = 6e4; | |
class SimpleTracer { | |
constructor(event) { | |
this.traceURL = getTraceUrl(event), | |
this.processSpanForReport = this.processSpanForReport.bind(this), | |
this.report = this.report.bind(this), | |
this.sharedTags = this.getSharedTags(navigator), | |
this.tracesQueue = [], | |
setInterval(this.ratelimitedBeaconTraces.bind(this), BEACON_TRACE_INTERVAL) | |
} | |
getSharedTags(navigator) { | |
const userAgent = null == navigator ? void 0 : navigator.userAgent | |
, environmentData = getEnvironmentData(userAgent) | |
, version = "dev" === CACHE_KEY ? CACHE_KEY : parseInt(CACHE_KEY.replace(CACHE_KEY_PREFIX, ""), 10); | |
return _objectSpread(_objectSpread({}, environmentData), {}, { | |
user_agent: userAgent, | |
version, | |
service_name: "desktop", | |
service_subcomponent: "service_worker" | |
}) | |
} | |
startSpan(name, options) { | |
const span = new SimpleSpan(this,options); | |
return span.setOperationName(name), | |
span | |
} | |
ratelimitedBeaconTraces() { | |
if (0 === this.tracesQueue.length) | |
return; | |
const trace = this.tracesQueue[0]; | |
this.beaconSpans(trace), | |
this.tracesQueue = [] | |
} | |
report(spans) { | |
let tags = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}; | |
const spansForReporting = spans.map(this.processSpanForReport) | |
, sharedTags = this.processTagsForBeacon(_objectSpread(_objectSpread({}, tags), this.sharedTags)); | |
this.tracesQueue.push({ | |
spans: spansForReporting, | |
tags: sharedTags | |
}) | |
} | |
beaconSpans() { | |
let {spans, tags={}} = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; | |
const data = { | |
spans, | |
tags | |
}; | |
fetch(new Request(this.traceURL,{ | |
method: "POST", | |
body: JSON.stringify(data) | |
})) | |
} | |
processSpanForReport() { | |
let span = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; | |
if (!(span && span instanceof SimpleSpan)) | |
return null; | |
const {id, traceId, parentSpanId, name, startTimeMs, closeTimeMs, tags={}} = span | |
, startTsMicros = 1e3 * startTimeMs | |
, durationMicros = 1e3 * (closeTimeMs ? closeTimeMs - startTimeMs : 0); | |
return closeTimeMs || (tags._flush = !0), | |
{ | |
id: btoa(id), | |
trace_id: btoa(traceId), | |
parent_id: btoa(parentSpanId), | |
name, | |
start_timestamp_micros: startTsMicros, | |
duration_micros: durationMicros || 1, | |
tags: this.processTagsForBeacon(tags) | |
} | |
} | |
processTagsForBeacon(tags) { | |
if (!tags) | |
return []; | |
return Object.keys(tags).map(key=>{ | |
const value = tags[key]; | |
return this.convertToTracerTag(key, value) | |
} | |
) | |
} | |
convertToTracerTag(key, value) { | |
const type = typeof value; | |
return TAG_TYPES[type] ? { | |
key, | |
[TAG_TYPES[type][1]]: value, | |
v_type: TAG_TYPES[type][0] | |
} : {} | |
} | |
} | |
function getEnvironmentData(userAgent) { | |
if (!userAgent) | |
return {}; | |
const isSSB = !!/(Slack)/g.test(userAgent); | |
if (!isSSB) | |
return { | |
is_ssb: !1 | |
}; | |
const parts = userAgent.split("/"); | |
return { | |
is_ssb: isSSB, | |
app_version: parts[parts.length - 1], | |
user_agent: userAgent | |
} | |
} | |
class SimpleSpan { | |
constructor(tracer) { | |
let {tags={}, parentSpanId="", traceId} = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}; | |
this.tracer = tracer, | |
this.tags = tags, | |
this.parentSpanId = parentSpanId, | |
this.id = simpleUUIDGenerator(), | |
this.traceId = traceId || this.id, | |
this.startTimeMs = Date.now() | |
} | |
setOperationName(name) { | |
return this.name = name, | |
this | |
} | |
addTags(tags) { | |
return this.tags = _objectSpread(_objectSpread({}, this.tags), tags), | |
this | |
} | |
close() { | |
return this.closeTimeMs = Date.now(), | |
this | |
} | |
} | |
function simpleUUIDGenerator() { | |
let dt = (new Date).getTime(); | |
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c=>{ | |
const r = (dt + 16 * Math.random()) % 16 | 0; | |
return dt = Math.floor(dt / 16), | |
("x" === c ? r : 3 & r | 8).toString(16) | |
} | |
).replace(/-/g, "") | |
} | |
const test = { | |
makeLogDate, | |
SimpleMetrics, | |
SimpleTracer, | |
manifestURLs, | |
FAKE_SERVICE_WORKER_ENDPOINT, | |
CACHE_KEY | |
}; | |
function maxParallelIterator(source, handler, maxParallel) { | |
const blockers = [] | |
, unblockNext = ()=>{ | |
const next = blockers.shift(); | |
next && next() | |
} | |
, output = source.map(input=>{ | |
return new Promise(resolve=>{ | |
blockers.push(resolve) | |
} | |
).then(()=>handler(input)).finally(unblockNext) | |
} | |
); | |
for (let i = 0; i < maxParallel; i++) | |
unblockNext(); | |
return output | |
} | |
Object.defineProperty(test, "log", { | |
get: ()=>log, | |
set: v=>{ | |
log = v | |
} | |
}), | |
Object.defineProperty(test, "debug", { | |
get: ()=>debug, | |
set: v=>{ | |
debug = v | |
} | |
}), | |
Object.defineProperty(test, "shouldTrace", { | |
get: ()=>shouldTrace, | |
set: v=>{ | |
shouldTrace = v | |
} | |
}), | |
Object.defineProperty(test, "simpleTracer", { | |
get: ()=>simpleTracer, | |
set: v=>{ | |
simpleTracer = v | |
} | |
}), | |
Object.defineProperty(test, "simpleMetrics", { | |
get: ()=>simpleMetrics, | |
set: v=>{ | |
simpleMetrics = v | |
} | |
}), | |
Object.defineProperty(test, "manifestURLs", { | |
get: ()=>manifestURLs, | |
set: v=>{ | |
manifestURLs = v | |
} | |
}), | |
Object.defineProperty(test, "getSimpleTracer", { | |
get: ()=>getSimpleTracer, | |
set: v=>{ | |
getSimpleTracer = v | |
} | |
}), | |
Object.defineProperty(test, "hasCacheBucketExpired", { | |
get: ()=>hasCacheBucketExpired, | |
set: v=>{ | |
hasCacheBucketExpired = v | |
} | |
}), | |
Object.defineProperty(test, "getValidCacheKeys", { | |
get: ()=>getValidCacheKeys, | |
set: v=>{ | |
getValidCacheKeys = v | |
} | |
}), | |
Object.defineProperty(test, "getInvalidCacheKeys", { | |
get: ()=>getInvalidCacheKeys, | |
set: v=>{ | |
getInvalidCacheKeys = v | |
} | |
}), | |
Object.defineProperty(test, "dipAndCache", { | |
get: ()=>dipAndCache, | |
set: v=>{ | |
dipAndCache = v | |
} | |
}), | |
Object.defineProperty(test, "fetchAndCache", { | |
get: ()=>fetchAndCache, | |
set: v=>{ | |
fetchAndCache = v | |
} | |
}), | |
Object.defineProperty(test, "CACHE_KEY", { | |
get: ()=>fetchAndCache, | |
set: v=>{ | |
CACHE_KEY = v | |
} | |
}), | |
Object.defineProperty(test, "purgeExcessCacheKeysPromise", { | |
get: ()=>purgeExcessCacheKeysPromise, | |
set: v=>{ | |
purgeExcessCacheKeysPromise = v | |
} | |
}), | |
"undefined" == typeof ServiceWorkerGlobalScope && "test" === (null === (_process = process) || void 0 === _process ? void 0 : null === (_process$env = _process.env) || void 0 === _process$env ? void 0 : _process$env.NODE_ENV) && (module.exports = { | |
CACHE_KEY, | |
gantryClientURLMatch, | |
installHandler, | |
errorHandler, | |
activateHandler, | |
fetchHandler, | |
test, | |
maxParallelIterator | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment