Load JS view assets from CDN when applicable (#1958)

This commit is contained in:
Jonatan Kłosko 2023-06-07 14:26:51 +02:00 committed by GitHub
parent a4558cc591
commit de4d46f3be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 53 additions and 12 deletions

View file

@ -40,8 +40,12 @@ import { initializeIframeSource } from "./js_view/iframe";
*
* * `data-ref` - a unique identifier used as messages scope
*
* * `data-assets-base-path` - the path to resolve all relative paths
* against in the iframe
* * `data-assets-base-path` - the base path to fetch assets from
* within the iframe (and resolve all relative paths against)
*
* * `data-assets-cdn-url` - a URL to CDN location to fetch assets
* from. Only used if specified and the entrypoint script can be
* successfully accessed, also only when Livebook runs on https
*
* * `data-js-path` - a relative path for the initial view-specific
* JS module
@ -179,6 +183,7 @@ const JSView = {
return {
ref: getAttributeOrThrow(this.el, "data-ref"),
assetsBasePath: getAttributeOrThrow(this.el, "data-assets-base-path"),
assetsCdnUrl: getAttributeOrDefault(this.el, "data-assets-cdn-url", null),
jsPath: getAttributeOrThrow(this.el, "data-js-path"),
sessionToken: getAttributeOrThrow(this.el, "data-session-token"),
connectToken: getAttributeOrThrow(this.el, "data-connect-token"),
@ -334,17 +339,17 @@ const JSView = {
handleChildMessage(message, onReady) {
if (message.type === "ready" && !this.childReady) {
const assetsBaseUrl = window.location.origin + this.props.assetsBasePath;
this.getAssetsBaseUrl().then((assetsBaseUrl) => {
this.postMessage({
type: "readyReply",
token: this.childToken,
baseUrl: assetsBaseUrl,
jsPath: this.props.jsPath,
});
this.postMessage({
type: "readyReply",
token: this.childToken,
baseUrl: assetsBaseUrl,
jsPath: this.props.jsPath,
this.childReady = true;
onReady();
});
this.childReady = true;
onReady();
} else {
// Note: we use a random token to authorize child messages
// and do our best to make this token unavailable for the
@ -386,6 +391,21 @@ const JSView = {
}
},
getAssetsBaseUrl() {
// Livebook may be running behind an authentication proxy, in
// which case the internal assets URL is not accessible from
// within the iframe (served from a different origin). To
// workaround this, we fallback to a CDN for the assets if
// available for the given package.
return cachedPublicEndpointCheck().then((isPublicAccessible) => {
if (!isPublicAccessible && this.props.assetsCdnUrl) {
return this.props.assetsCdnUrl;
} else {
return window.location.origin + this.props.assetsBasePath;
}
});
},
postMessage(message) {
this.iframe.contentWindow.postMessage(message, "*");
},
@ -486,4 +506,20 @@ const JSView = {
},
};
/**
* Checks if Livebook public endpoint is accessible without auth cookies.
*
* Returns a promise that resolves to a boolean. The request is sent only
* once and the response is cached.
*/
function cachedPublicEndpointCheck() {
cachedPublicEndpointCheck.promise =
cachedPublicEndpointCheck.promise ||
fetch("/public/health")
.then((response) => response.status === 200)
.catch((error) => false);
return cachedPublicEndpointCheck.promise;
}
export default JSView;

View file

@ -285,7 +285,8 @@ defprotocol Livebook.Runtime do
assets: %{
archive_path: String.t(),
hash: String.t(),
js_path: String.t()
js_path: String.t(),
cdn_url: String.t() | nil
}
}

View file

@ -18,6 +18,7 @@ defmodule LivebookWeb.JSViewComponent do
phx-update="ignore"
data-ref={@js_view.ref}
data-assets-base-path={~p"/public/sessions/#{@session_id}/assets/#{@js_view.assets.hash}/"}
data-assets-cdn-url={cdn_url(@js_view.assets[:cdn_url])}
data-js-path={@js_view.assets.js_path}
data-session-token={session_token(@session_id, @client_id)}
data-connect-token={connect_token(@js_view.pid)}
@ -29,6 +30,9 @@ defmodule LivebookWeb.JSViewComponent do
"""
end
defp cdn_url(nil), do: nil
defp cdn_url(url), do: url <> "/"
defp session_token(session_id, client_id) do
Phoenix.Token.sign(LivebookWeb.Endpoint, "session", %{
session_id: session_id,