ruffle/web/js-src/public-api.js

186 lines
6.5 KiB
JavaScript
Raw Normal View History

2019-10-14 01:32:50 +00:00
import { Version } from "./version.js";
/**
* Represents the Ruffle public API.
*
* The public API exists primarily to allow multiple installs of Ruffle on a
* page (e.g. an extension install and a local one) to cooperate. In an ideal
* situation, all Ruffle sources on the page install themselves into a single
* public API, and then the public API picks the newest version by default.
*
* This API *is* versioned, in case we need to upgrade it. However, it must be
* backwards- and forwards-compatible with all known sources.
*/
export class PublicAPI {
/**
* Construct the Ruffle public API.
*
* Do not use this function to negotiate a public API. Instead, use
2019-10-14 01:32:50 +00:00
* `public_api` to register your Ruffle source with an existing public API
* if it exists.
*
* Constructing a Public API will also trigger it to initialize Ruffle once
* the page loads, if the API has not already been superceded.
*
* @param {object} prev What used to be in the public API slot.
*
* This is used to upgrade from a prior version of the public API, or from
* a user-defined configuration object placed in the public API slot.
*/
constructor(prev) {
this.sources = {};
this.config = {};
this.invoked = false;
2019-10-14 01:32:50 +00:00
this.newest_name = false;
if (prev !== undefined) {
if (prev.constructor.name === PublicAPI.name) {
/// We're upgrading from a previous API to a new one.
this.sources = prev.sources;
this.config = prev.config;
this.invoked = prev.invoked;
this.conflict = prev.conflict;
2019-10-14 01:32:50 +00:00
this.newest_name = prev.newest_name;
prev.superceded();
2019-10-14 18:56:30 +00:00
} else if (prev.constructor === Object && prev.config !== undefined) {
/// We're the first, install user configuration
2019-10-14 18:56:30 +00:00
this.config = prev.config;
} else {
/// We're the first, but conflicting with someone else.
this.conflict = prev;
}
}
if (document.readyState === "loading") {
window.addEventListener("DOMContentLoaded", this.init.bind(this));
} else {
window.setTimeout(this.init.bind(this), 0);
}
}
/**
* The version of the public API.
*
* This allows a page with an old version of the Public API to be upgraded
* to a new version of the API. The public API is intended to be changed
* very infrequently, if at all, but this provides an escape mechanism for
* newer Ruffle sources to upgrade older installations.
*/
get version() {
return "0.1.0";
}
/**
2019-10-14 01:32:50 +00:00
* Register a given source with the Ruffle Public API.
*
* @param {string} name The name of the source.
* @param {object} api The public API object. This must conform to the shape
* of `SourceAPI`.
*/
2019-10-14 01:32:50 +00:00
register_source(name, api) {
2019-10-13 03:04:45 +00:00
this.sources[name] = api;
2019-10-14 01:32:50 +00:00
}
/**
* Determine the name of the newest registered source in the Public API.
*
* @returns {(string|bool)} The name of the source, or `false` if no source
* has yet to be registered.
*/
newest_source_name() {
let newest_name = false, newest_version = Version.from_semver("0.0.0");
for (let k in this.sources) {
if (this.sources.hasOwnProperty(k)) {
let k_version = Version.from_semver(this.sources[k].version);
if (k_version.has_precedence_over(newest_version)) {
newest_name = k;
newest_version = k_version;
}
}
}
return newest_name;
}
/**
* Negotiate and start Ruffle.
2019-10-14 18:56:30 +00:00
*
2019-10-16 02:02:03 +00:00
* This function reads the config parameter to determine which polyfills
* should be enabled. If the configuration parameter is missing, then we
* use a built-in set of defaults sufficient to fool sites with static
* content and weak plugin detection.
*/
init() {
if (!this.invoked) {
this.invoked = true;
this.newest_name = this.newest_source_name();
if (this.newest_name === false) {
throw new Error("No registered Ruffle source!");
}
2019-10-14 01:32:50 +00:00
2019-10-16 02:02:03 +00:00
let polyfills = this.config.polyfills;
if (polyfills === undefined) {
polyfills = ["plugin-detect", "static-content"];
}
2019-10-16 02:02:03 +00:00
this.sources[this.newest_name].polyfill(polyfills);
}
}
/**
* Indicates that this version of the public API has been superceded by a
* newer version.
*
* This should only be called by a newer version of the Public API.
* Identical versions of the Public API should not supercede older versions
* of that same API.
*
2019-10-16 02:02:03 +00:00
* Unfortunately, we can't disable polyfills after-the-fact, so this
* only lets you disable the init event...
*/
superceded() {
this.invoked = true;
}
/**
* Join a source into the public API, if it doesn't already exist.
*
* @param {*} prev_ruffle The previous iteration of the Ruffle API.
*
* The `prev_ruffle` param lists the previous object in the RufflePlayer
* slot. We perform some checks to see if this is a Ruffle public API or a
* conflicting object. If this is conflicting, then a new public API will
* be constructed (see the constructor information for what happens to
* `prev_ruffle`).
*
* Note that Public API upgrades are deliberately not enabled in this
* version of Ruffle, since there is no Public API to upgrade from.
*
* @param {string|undefined} source_name The name of this particular
* Ruffle source.
*
* @param {object|undefined} source_api The Ruffle source to add.
*
* If both parameters are provided they will be used to define a new Ruffle
2019-10-14 01:32:50 +00:00
* source to register with the public API.
*
* @returns {object} The Ruffle Public API.
*/
static negotiate(prev_ruffle, source_name, source_api) {
let public_api;
if (prev_ruffle !== undefined && prev_ruffle.constructor.name == PublicAPI.name) {
public_api = prev_ruffle;
} else {
public_api = new PublicAPI(prev_ruffle);
}
if (source_name !== undefined && source_api !== undefined) {
2019-10-14 01:32:50 +00:00
public_api.register_source(source_name, source_api);
}
return public_api;
};
}