2020-11-12 22:32:53 +00:00
|
|
|
import { Version } from "./version";
|
|
|
|
import { VersionRange } from "./version-range";
|
|
|
|
import { SourceAPI } from "./source-api";
|
2020-11-17 21:05:25 +00:00
|
|
|
import { Config } from "./config";
|
2019-10-14 01:32:50 +00:00
|
|
|
|
2019-10-09 01:42:40 +00:00
|
|
|
/**
|
2019-10-13 02:01:01 +00:00
|
|
|
* Represents the Ruffle public API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-13 02:01:01 +00:00
|
|
|
* The public API exists primarily to allow multiple installs of Ruffle on a
|
2019-10-14 18:37:58 +00:00
|
|
|
* 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.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-14 18:37:58 +00:00
|
|
|
* This API *is* versioned, in case we need to upgrade it. However, it must be
|
|
|
|
* backwards- and forwards-compatible with all known sources.
|
2019-10-09 01:42:40 +00:00
|
|
|
*/
|
2020-11-12 22:32:53 +00:00
|
|
|
export class PublicAPI {
|
|
|
|
private sources: Record<string, SourceAPI>;
|
2020-11-17 21:05:25 +00:00
|
|
|
private config: Config;
|
2020-11-12 22:32:53 +00:00
|
|
|
private invoked: boolean;
|
2020-11-17 21:11:37 +00:00
|
|
|
private newestName: string | null;
|
2020-11-17 21:05:25 +00:00
|
|
|
private conflict: Record<string, unknown> | null;
|
2020-11-12 22:32:53 +00:00
|
|
|
|
2019-10-13 02:01:01 +00:00
|
|
|
/**
|
|
|
|
* Construct the Ruffle public API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-13 02:01:01 +00:00
|
|
|
* 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
|
2019-10-13 02:01:01 +00:00
|
|
|
* if it exists.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-14 22:45:01 +00:00
|
|
|
* Constructing a Public API will also trigger it to initialize Ruffle once
|
2020-09-19 14:27:24 +00:00
|
|
|
* the page loads, if the API has not already been superseded.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:00:02 +00:00
|
|
|
* @param prev What used to be in the public API slot.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-13 02:01:01 +00:00
|
|
|
* 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.
|
2020-11-17 21:00:02 +00:00
|
|
|
*
|
|
|
|
* @protected
|
2019-10-13 02:01:01 +00:00
|
|
|
*/
|
2020-11-17 21:10:10 +00:00
|
|
|
protected constructor(prev: PublicAPI | null | Record<string, unknown>) {
|
2019-10-13 02:01:01 +00:00
|
|
|
this.sources = {};
|
|
|
|
this.config = {};
|
|
|
|
this.invoked = false;
|
2020-11-17 21:11:37 +00:00
|
|
|
this.newestName = null;
|
2020-11-17 21:05:25 +00:00
|
|
|
this.conflict = null;
|
2019-10-13 02:01:01 +00:00
|
|
|
|
2020-11-12 22:32:53 +00:00
|
|
|
if (prev !== undefined && prev !== null) {
|
2020-11-17 21:10:10 +00:00
|
|
|
if (prev instanceof PublicAPI) {
|
2019-10-13 02:01:01 +00:00
|
|
|
/// 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;
|
2020-11-17 21:11:37 +00:00
|
|
|
this.newestName = prev.newestName;
|
2019-10-14 18:37:58 +00:00
|
|
|
|
2020-09-19 14:27:24 +00:00
|
|
|
prev.superseded();
|
2020-05-12 22:24:41 +00:00
|
|
|
} else if (
|
|
|
|
prev.constructor === Object &&
|
2020-11-17 21:10:10 +00:00
|
|
|
prev.config instanceof Object
|
2020-05-12 22:24:41 +00:00
|
|
|
) {
|
2019-10-13 02:01:01 +00:00
|
|
|
/// We're the first, install user configuration
|
2019-10-14 18:56:30 +00:00
|
|
|
this.config = prev.config;
|
2019-10-13 02:01:01 +00:00
|
|
|
} else {
|
|
|
|
/// We're the first, but conflicting with someone else.
|
|
|
|
this.conflict = prev;
|
|
|
|
}
|
|
|
|
}
|
2019-10-14 18:37:58 +00:00
|
|
|
|
2019-10-14 22:45:01 +00:00
|
|
|
if (document.readyState === "loading") {
|
|
|
|
window.addEventListener("DOMContentLoaded", this.init.bind(this));
|
|
|
|
} else {
|
|
|
|
window.setTimeout(this.init.bind(this), 0);
|
|
|
|
}
|
2019-10-14 18:37:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The version of the public API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:00:02 +00:00
|
|
|
* This is *not* the version of Ruffle itself.
|
|
|
|
*
|
2019-10-14 18:37:58 +00:00
|
|
|
* 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.
|
|
|
|
*/
|
2020-11-17 21:00:02 +00:00
|
|
|
get version(): string {
|
2019-10-14 18:37:58 +00:00
|
|
|
return "0.1.0";
|
2019-10-13 02:01:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-10-14 01:32:50 +00:00
|
|
|
* Register a given source with the Ruffle Public API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:00:02 +00:00
|
|
|
* @param name The name of the source.
|
|
|
|
* @param api The public API object. This must conform to the shape
|
2019-10-13 02:01:01 +00:00
|
|
|
* of `SourceAPI`.
|
|
|
|
*/
|
2020-11-17 21:06:54 +00:00
|
|
|
registerSource(name: string, api: SourceAPI): void {
|
2019-10-13 03:04:45 +00:00
|
|
|
this.sources[name] = api;
|
2019-10-14 01:32:50 +00:00
|
|
|
}
|
2020-05-12 22:24:41 +00:00
|
|
|
|
2019-10-14 01:32:50 +00:00
|
|
|
/**
|
|
|
|
* Determine the name of the newest registered source in the Public API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:00:02 +00:00
|
|
|
* @returns The name of the source, or `null` if no source
|
2019-10-14 01:32:50 +00:00
|
|
|
* has yet to be registered.
|
|
|
|
*/
|
2020-11-17 21:07:20 +00:00
|
|
|
newestSourceName(): string | null {
|
2020-11-17 21:11:37 +00:00
|
|
|
let newestName = null,
|
|
|
|
newestVersion = Version.fromSemver("0.0.0");
|
2019-10-14 01:32:50 +00:00
|
|
|
|
2020-11-12 22:32:53 +00:00
|
|
|
for (const k in this.sources) {
|
2020-05-12 22:01:25 +00:00
|
|
|
if (Object.prototype.hasOwnProperty.call(this.sources, k)) {
|
2020-11-13 21:49:38 +00:00
|
|
|
const k_version = Version.fromSemver(this.sources[k].version);
|
2020-11-17 21:11:37 +00:00
|
|
|
if (k_version.hasPrecedenceOver(newestVersion)) {
|
|
|
|
newestName = k;
|
|
|
|
newestVersion = k_version;
|
2019-10-14 01:32:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-17 21:11:37 +00:00
|
|
|
return newestName;
|
2019-10-13 02:01:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Negotiate and start Ruffle.
|
2020-05-12 22:24:41 +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.
|
2019-10-13 02:01:01 +00:00
|
|
|
*/
|
2020-11-17 21:00:02 +00:00
|
|
|
init(): void {
|
2019-10-14 18:37:58 +00:00
|
|
|
if (!this.invoked) {
|
|
|
|
this.invoked = true;
|
2020-11-17 21:11:37 +00:00
|
|
|
this.newestName = this.newestSourceName();
|
2019-10-14 18:37:58 +00:00
|
|
|
|
2020-11-17 21:11:37 +00:00
|
|
|
if (this.newestName === null) {
|
2019-10-14 18:37:58 +00:00
|
|
|
throw new Error("No registered Ruffle source!");
|
|
|
|
}
|
2019-10-14 01:32:50 +00:00
|
|
|
|
2020-11-12 22:32:53 +00:00
|
|
|
const polyfills = this.config.polyfills;
|
2020-08-12 02:22:06 +00:00
|
|
|
if (polyfills !== false) {
|
2020-11-17 21:11:37 +00:00
|
|
|
this.sources[this.newestName].polyfill();
|
2019-10-14 18:37:58 +00:00
|
|
|
}
|
2019-10-13 02:01:01 +00:00
|
|
|
}
|
2019-10-14 18:37:58 +00:00
|
|
|
}
|
|
|
|
|
2019-10-20 19:13:45 +00:00
|
|
|
/**
|
|
|
|
* Look up the newest Ruffle source and return it's API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:00:02 +00:00
|
|
|
* @returns An instance of the Source API.
|
2019-10-20 19:13:45 +00:00
|
|
|
*/
|
2020-11-17 21:00:02 +00:00
|
|
|
newest(): SourceAPI | null {
|
2020-11-17 21:07:20 +00:00
|
|
|
const name = this.newestSourceName();
|
2020-11-12 22:32:53 +00:00
|
|
|
return name != null ? this.sources[name] : null;
|
2019-10-20 19:13:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up a specific Ruffle version (or any version satisfying a given set
|
|
|
|
* of requirements) and return it's API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:00:02 +00:00
|
|
|
* @param ver_requirement A set of semantic version requirement
|
2019-10-20 19:13:45 +00:00
|
|
|
* strings that the player version must satisfy.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:00:02 +00:00
|
|
|
* @returns An instance of the Source API, if one or more
|
2019-10-20 19:13:45 +00:00
|
|
|
* sources satisfied the requirement.
|
|
|
|
*/
|
2020-11-17 21:00:02 +00:00
|
|
|
satisfying(ver_requirement: string): SourceAPI | null {
|
2020-11-13 22:20:57 +00:00
|
|
|
const requirement = VersionRange.fromRequirementString(ver_requirement);
|
2020-11-17 21:11:37 +00:00
|
|
|
let valid = null;
|
2019-10-20 19:13:45 +00:00
|
|
|
|
2020-11-12 22:32:53 +00:00
|
|
|
for (const k in this.sources) {
|
2020-05-12 22:01:25 +00:00
|
|
|
if (Object.prototype.hasOwnProperty.call(this.sources, k)) {
|
2020-11-13 21:49:38 +00:00
|
|
|
const version = Version.fromSemver(this.sources[k].version);
|
2019-10-20 19:13:45 +00:00
|
|
|
|
2020-11-13 22:20:07 +00:00
|
|
|
if (requirement.satisfiedBy(version)) {
|
2020-11-17 21:11:37 +00:00
|
|
|
valid = this.sources[k];
|
2019-10-20 19:13:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-17 21:11:37 +00:00
|
|
|
return valid;
|
2019-10-20 19:13:45 +00:00
|
|
|
}
|
|
|
|
|
2019-11-05 19:53:36 +00:00
|
|
|
/**
|
|
|
|
* Look up the newest Ruffle version compatible with the `local` source, if
|
|
|
|
* it's installed. Otherwise, use the latest version.
|
2020-11-17 21:00:02 +00:00
|
|
|
*
|
|
|
|
* @returns An instance of the Source API
|
2019-11-05 19:53:36 +00:00
|
|
|
*/
|
2020-11-17 21:08:01 +00:00
|
|
|
localCompatible(): SourceAPI | null {
|
2019-11-05 19:53:36 +00:00
|
|
|
if (this.sources.local !== undefined) {
|
2020-01-13 20:28:30 +00:00
|
|
|
return this.satisfying("^" + this.sources.local.version);
|
2019-11-05 19:53:36 +00:00
|
|
|
} else {
|
2020-01-13 20:28:30 +00:00
|
|
|
return this.newest();
|
2019-11-05 19:53:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up the newest Ruffle version with the exact same version as the
|
|
|
|
* `local` source, if it's installed. Otherwise, use the latest version.
|
2020-11-17 21:00:02 +00:00
|
|
|
*
|
|
|
|
* @returns An instance of the Source API
|
2019-11-05 19:53:36 +00:00
|
|
|
*/
|
2020-11-17 21:00:02 +00:00
|
|
|
local(): SourceAPI | null {
|
2019-11-05 19:53:36 +00:00
|
|
|
if (this.sources.local !== undefined) {
|
2020-01-13 20:28:30 +00:00
|
|
|
return this.satisfying("=" + this.sources.local.version);
|
2019-11-05 19:53:36 +00:00
|
|
|
} else {
|
2020-01-13 20:28:30 +00:00
|
|
|
return this.newest();
|
2019-11-05 19:53:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 18:37:58 +00:00
|
|
|
/**
|
2020-09-19 14:27:24 +00:00
|
|
|
* Indicates that this version of the public API has been superseded by a
|
2019-10-14 18:37:58 +00:00
|
|
|
* newer version.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-14 18:37:58 +00:00
|
|
|
* This should only be called by a newer version of the Public API.
|
2020-09-19 14:27:24 +00:00
|
|
|
* Identical versions of the Public API should not supersede older versions
|
2019-10-14 18:37:58 +00:00
|
|
|
* of that same API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-16 02:02:03 +00:00
|
|
|
* Unfortunately, we can't disable polyfills after-the-fact, so this
|
2019-10-14 22:45:01 +00:00
|
|
|
* only lets you disable the init event...
|
2020-11-17 21:00:02 +00:00
|
|
|
*
|
|
|
|
* @protected
|
2019-10-14 18:37:58 +00:00
|
|
|
*/
|
2020-11-17 21:00:02 +00:00
|
|
|
protected superseded(): void {
|
2019-10-14 18:37:58 +00:00
|
|
|
this.invoked = true;
|
2019-10-13 02:01:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Join a source into the public API, if it doesn't already exist.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:11:37 +00:00
|
|
|
* @param prevRuffle The previous iteration of the Ruffle API.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:11:37 +00:00
|
|
|
* The `prevRuffle` param lists the previous object in the RufflePlayer
|
2019-10-13 02:01:01 +00:00
|
|
|
* 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
|
2020-11-17 21:11:37 +00:00
|
|
|
* `prevRuffle`).
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-14 18:37:58 +00:00
|
|
|
* Note that Public API upgrades are deliberately not enabled in this
|
|
|
|
* version of Ruffle, since there is no Public API to upgrade from.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:11:37 +00:00
|
|
|
* @param sourceName The name of this particular
|
2019-10-13 02:01:01 +00:00
|
|
|
* Ruffle source.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:11:37 +00:00
|
|
|
* @param sourceAPI The Ruffle source to add.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2019-10-13 02:01:01 +00:00
|
|
|
* 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.
|
2020-05-12 22:24:41 +00:00
|
|
|
*
|
2020-11-17 21:00:02 +00:00
|
|
|
* @returns The Ruffle Public API.
|
2019-10-13 02:01:01 +00:00
|
|
|
*/
|
2020-11-12 22:32:53 +00:00
|
|
|
static negotiate(
|
2020-11-17 21:11:37 +00:00
|
|
|
prevRuffle: PublicAPI | null | Record<string, unknown>,
|
|
|
|
sourceName: string | undefined,
|
|
|
|
sourceAPI: SourceAPI | undefined
|
2020-11-17 21:00:02 +00:00
|
|
|
): PublicAPI {
|
2020-11-17 21:11:37 +00:00
|
|
|
let publicAPI: PublicAPI;
|
|
|
|
if (prevRuffle instanceof PublicAPI) {
|
|
|
|
publicAPI = prevRuffle;
|
2019-10-13 02:01:01 +00:00
|
|
|
} else {
|
2020-11-17 21:11:37 +00:00
|
|
|
publicAPI = new PublicAPI(prevRuffle);
|
2019-10-09 01:42:40 +00:00
|
|
|
}
|
2020-05-12 22:24:41 +00:00
|
|
|
|
2020-11-17 21:11:37 +00:00
|
|
|
if (sourceName !== undefined && sourceAPI !== undefined) {
|
|
|
|
publicAPI.registerSource(sourceName, sourceAPI);
|
2020-05-12 22:24:41 +00:00
|
|
|
|
2020-03-26 00:00:12 +00:00
|
|
|
// Install the faux plugin detection immediately.
|
|
|
|
// This is necessary because scripts such as SWFObject check for the
|
|
|
|
// Flash Player immediately when they load.
|
|
|
|
// TODO: Maybe there's a better place for this.
|
2020-11-17 21:11:37 +00:00
|
|
|
const polyfills = publicAPI.config.polyfills;
|
2020-08-12 02:22:06 +00:00
|
|
|
if (polyfills !== false) {
|
2020-11-17 21:11:37 +00:00
|
|
|
sourceAPI.pluginPolyfill();
|
2020-03-26 00:00:12 +00:00
|
|
|
}
|
2019-10-13 02:01:01 +00:00
|
|
|
}
|
|
|
|
|
2020-11-17 21:11:37 +00:00
|
|
|
return publicAPI;
|
2020-05-12 21:28:00 +00:00
|
|
|
}
|
2020-11-12 22:32:53 +00:00
|
|
|
}
|