/** * @template {AdLadPlugin} TPlugins * @typedef AdLadOptions * @property {TPlugins[]} [plugins] The list of plugins that will be supported. * Only a single plugin of this list will be activated depending on which options have been provided to AdLad. * By default each plugin will tell AdLad whether it wishes to be active. * If more than one plugin wishes to be active, the last plugin of this list will be picked. * @property {string} [plugin] The name of the plugin to use. * This will override which plugin was chosen by the order of the `plugins` argument, * and will activate the provided plugin regardless of whether it choose to be active or not. * If the passed value is not an existing plugin, an error will be thrown. * An exception to this is the value `"none"`, which will cause no plugin to be selected. * `"none"` can still be overridden by the query string. * @property {boolean} [useTestAds] Set to true when in development, when plugins support test ads, * they will use those instead of production ads. * @property {boolean} [allowQueryStringPluginSelection] When set to true (which is the default) * allows changing the selected plugin using the `?adlad=` query string. * You can change the key of the query string parameter using `pluginSelectQueryStringKey`. * @property {string} [pluginSelectQueryStringKey] The key used for selecting plugins using the query string. * @property {AdLadInvalidQueryStringBehaviour} [invalidQueryStringPluginBehaviour] The behaviour when * an invalid plugin name is passed in the `?adlad=` query string. Defaults to `"fallback"`. * - `"error"` throws an error during instantiation of the `AdLad` class. * This will make your game completely unplayable when an invalid query string is passed. * Preventing users from playing your game with ads disabled. * - `"fallback"` switches back to behaviour from the `plugins` and `plugin` parameters, * making sure the game is still playable but also disallows disabling ads. This is the default behaviour. * - `"none"` No plugin is picked when an invalid value is provided. This completely disables ads. * While this makes it possible for users to easily disable ads when they are aware of the query string, * this might also be useful when you wish to debug your game without the distraction of third party requests and errors. * For example, you would be able to set `?adlad=none` to completely disable plugins this way. */ /** * Utility to provide methods with autocompletion and type checking based on which plugins are used. * @template {AdLadPlugin} TPlugins * @template {string} TOptionsName * @template {number} TArgIndex * @typedef {OmitMissing>} CollectPluginArgs */ /** * Utility that removes `never` and `undefined` types from an object. * @template T * @typedef {{ [K in keyof T as T[K] extends never | undefined ? never : K]: T[K] }} OmitMissing */ /** * @template {AdLadPlugin} TPlugins * @template {string} TOptionsName * @template {number} TArgIndex * @typedef {{ * [TPluginName in TPlugins["name"]]: GetMaybeParameters< * GetPluginFromUnionByName, * TOptionsName, * TArgIndex * >; * }} CollectPluginArgsWithNever */ /** * Filters all plugins that do not match the given name from a union. * @template TPluginsUnion * @template {string} TPluginName * @typedef {TPluginsUnion extends {name: TPluginName} ? TPluginsUnion : never} GetPluginFromUnionByName */ /** * Gets the type of a parameter from the provided method on a plugin. * Results in `never` when either the method or the parameter doesn't exist. * @template {AdLadPlugin} TPlugin * @template {string} TOptionsName * @template {number} TArgIndex * @typedef {TOptionsName extends keyof TPlugin * ? TPlugin[TOptionsName] extends (...args: any[]) => any * ? Parameters extends infer Params * ? Params extends any[] * ? Params[TArgIndex] * : never * : never * : never * : never} GetMaybeParameters */ /** * @template T * @typedef {{ * [K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise : T[K]; * }} PromisifyProps */ /** * @template U * @typedef {(U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never} UnionToIntersection */ /** * @template {AdLadPlugin} TPlugins * @typedef {TPlugins extends {customRequests: any} * ? TPlugins["customRequests"] extends {[x: string]: any} * ? TPlugins["customRequests"] * : never * : never} GetCustomRequestCommands */ /** * @template {AdLadPlugin} TPlugins */ export class AdLad { /** * You can instantiate the AdLad class with a list of plugins that should be supported. * You can then make function calls to the AdLad instance, * which will pass on requests such as showing full screen ads to the plugin that is currently active. * Only a single plugin can be active, which will be picked during instantiation. * * There are three methods for picking which plugin should be active: * * - First, every plugin can self report to AdLad whether it wishes to be active. * Most plugins always want to be active, * but some plugins can be configured to only be active depending on the domain the page is embedded on. * If multiple plugins wish to be active, the first plugin from the provided list will be picked. * This allows you to set a priority each plugin. * - Secondly, you can pick a plugin using the `plugin` option. * This will override the priority list, and the request for plugins to be active or not will be ignored. * - Lastly, plugins can be activated using the `?adlad=` query string. This will override all previous options. * However, because this can potentially be abused by players in order to disable ads, * there are some configurations available in order to control or completely disable this functionality. * * Using the query string to select plugins is the recommended method. * Doing it like | TPlugins[]} [options] */ constructor(options?: AdLadOptions | TPlugins[] | undefined); /** @private @type {AdLadPlugin?} */ private _plugin; /** @private @type {boolean} */ private _pluginInitializeFailed; /** @private */ private _manualNeedsPause; /** @private */ private _manualNeedsMute; /** @private */ private _needsPauseState; /** @private */ private _needsMuteState; /** @private */ private _canShowFullScreenAdState; /** @private */ private _canShowRewardedAdState; /** @private */ private _canShowBannerAdState; /** @private */ private _pluginInitializePromise; /** @private */ private _isShowingAd; /** @private */ private _loadingState; /** @private */ private _gameplayStartState; /** @private */ private _lastGameplayStartState; /** @private @type {[unknown] | []} */ private _lastGameplayStartCallArgs; /** @private @type {[unknown] | []} */ private _lastGameplayStopCallArgs; /** @private @type {Set} */ private _createdBanners; /** @private */ private _disposed; gameplayStart: OmitMissing> extends infer TOptions ? TOptions extends Record ? () => boolean : (options: TOptions) => boolean : never; gameplayStop: OmitMissing> extends infer TOptions ? TOptions extends Record ? () => boolean : (options: TOptions) => boolean : never; dispose(): void; /** * @private */ private _assertNotDispossed; /** * The name of the plugin that is currently active or `null` when no plugin is active. */ get activePlugin(): TPlugins["name"] | null; /** * Creates a signature that either takes a single parameter, when one of the plugins also takes a parameter. * Or no parameter at all when none of the plugins do. * @template {keyof AdLadPlugin} T * @template TReturn * @typedef {CollectPluginArgs extends infer TOptions ? * TOptions extends Record ? * () => TReturn : * (options: TOptions) => TReturn : * never} CreatePluginCallSignature */ /** * @private */ private _updateGameplayStartState; loadStart(): void; loadStop(): void; /** * This is `true` when an ad is playing or about to play and your game should be paused. * The difference between this and {@linkcode needsMute} is that this becomes `true` a little sooner, the moment * an ad is requested. Though the actual order in which the two change might differ per plugin. * Use {@linkcode onNeedsPauseChange} to listen for changes. */ get needsPause(): boolean; /** * This is `true` when an ad is playing and your audio should be muted. * The difference between this and {@linkcode needsPause} is that this becomes `true` a little later when an ad * is actually playing. Though the actual order in which the two change might differ per plugin. * Use {@linkcode onNeedsMuteChange} to listen for changes. */ get needsMute(): boolean; /** @typedef {(needsPause: boolean) => void} OnNeedsPauseChangeCallback */ /** @typedef {(needsMute: boolean) => void} OnNeedsMuteChangeCallback */ /** * Registers a callback that is fired when {@linkcode needsPause} changes. * Use this to pause your game during ads. * @param {OnNeedsPauseChangeCallback} cb */ onNeedsPauseChange(cb: (needsPause: boolean) => void): void; /** * Use this to unregister callbacks registered with {@linkcode onNeedsPauseChange}. * @param {OnNeedsPauseChangeCallback} cb */ removeOnNeedsPauseChange(cb: (needsPause: boolean) => void): void; /** * Registers a callback that is fired when {@linkcode needsPause} changes. * Use this to mute your game during ads. * @param {OnNeedsMuteChangeCallback} cb */ onNeedsMuteChange(cb: (needsMute: boolean) => void): void; /** * Use this to unregister callbacks registered with {@linkcode onNeedsMuteChange}. * @param {OnNeedsMuteChangeCallback} cb */ removeOnNeedsMuteChange(cb: (needsMute: boolean) => void): void; /** * Helper function for showing full screen and rewarded ads. * @private * @param {((userOptions: any) => Promise) | undefined} showFn * @param {Object. | undefined} pluginOptions * @returns {Promise} */ private _showPluginFullScreenAd; /** * This is true when a plugin has initialized and supports the {@linkcode showFullScreenAd} method. * When this is true, that is not a guarantee that {@linkcode showFullScreenAd} will always show an ad. * It might still fail due to adblockers, time constraints, unknown reasons, etc. */ get canShowFullScreenAd(): boolean; /** @typedef {(canShowFullScreenAd: boolean) => void} OnCanShowFullScreenAdChangeCallback */ /** * Registers a callback that is fired when {@linkcode canShowFullScreenAd} changes. * @param {OnCanShowFullScreenAdChangeCallback} cb */ onCanShowFullScreenAdChange(cb: (canShowFullScreenAd: boolean) => void): void; /** * Use this to unregister callbacks registered with {@linkcode onCanShowFullScreenAdChange}. * @param {OnCanShowFullScreenAdChangeCallback} cb */ removeOnCanShowFullScreenAdChange(cb: (canShowFullScreenAd: boolean) => void): void; /** * Waits for the plugin to initialize and shows a full screen ad once it's ready. * @param {Object} options * @param {CollectPluginArgs} [options.pluginOptions] * @returns {Promise} */ showFullScreenAd(options?: { pluginOptions?: OmitMissing> | undefined; }): Promise; /** * This is true when a plugin has initialized and supports the {@linkcode showRewardedAd} method. * When this is true, that is not a guarantee that {@linkcode showRewardedAd} will always show an ad. * It might still fail due to adblockers, time constraints, unknown reasons, etc. */ get canShowRewardedAd(): boolean; /** @typedef {(canShowRewardedAd: boolean) => void} OnCanShowRewardedAdChangeCallback */ /** * Registers a callback that is fired when {@linkcode canShowRewardedAd} changes. * @param {OnCanShowRewardedAdChangeCallback} cb */ onCanShowRewardedAdChange(cb: (canShowRewardedAd: boolean) => void): void; /** * Use this to unregister callbacks registered with {@linkcode onCanShowRewardedAdChange}. * @param {OnCanShowRewardedAdChangeCallback} cb */ removeOnCanShowRewardedAdChange(cb: (canShowRewardedAd: boolean) => void): void; /** * Waits for the plugin to initialize and shows a rewarded ad once it's ready. * @param {Object} options * @param {CollectPluginArgs} [options.pluginOptions] * @returns {Promise} */ showRewardedAd(options?: { pluginOptions?: OmitMissing> | undefined; }): Promise; /** * This is true when a plugin has initialized and supports the {@linkcode showBannerAd} method. * When this is true, that is not a guarantee that {@linkcode showBannerAd} will always show an ad. * It might still fail due to adblockers, time constraints, unknown reasons, etc. */ get canShowBannerAd(): boolean; /** @typedef {(canShowBannerAd: boolean) => void} OnCanShowBannerAdChangeCallback */ /** * Registers a callback that is fired when {@linkcode canShowBannerAd} changes. * @param {OnCanShowBannerAdChangeCallback} cb */ onCanShowBannerAdChange(cb: (canShowBannerAd: boolean) => void): void; /** * Use this to unregister callbacks registered with {@linkcode onCanShowBannerAdChange}. * @param {OnCanShowBannerAdChangeCallback} cb */ removeOnCanShowBannerAdChange(cb: (canShowBannerAd: boolean) => void): void; /** * @param {HTMLElement | string} element * @param {Object} options * @param {CollectPluginArgs} [options.pluginOptions] */ showBannerAd(element: HTMLElement | string, options?: { pluginOptions?: OmitMissing> | undefined; }): Promise; /** * @param {HTMLElement | string} element * @param {Object} options * @param {CollectPluginArgs} [options.pluginOptions] */ destroyBannerAd(element: HTMLElement | string, options?: { pluginOptions?: OmitMissing> | undefined; }): Promise; /** * Sends a custom request to the plugin that is currently active. * Every plugin can handle these requests according their own specification. * This allows plugins to extend their functionality with features that are not built-in into AdLad. * For example, an sdk might have support for a `happytime()` call or getting an invite link. * * ## Example usage * * ```js * adLad.customRequests.myCoolCustomRequest("foo"); * ``` * * Properties of this object will always be callable, even if the active plugin doesn't implement the custom request. * In the example above, the call will essentially be a no-op if the active plugin doesn't implement `myCoolCustomRequest`. * You can still call it and you don't have to check if the function exists. * * Plugins often share a similar signature for similar requests. * When two plugins require the same arguments, you can use this just fine * and your request will be forwarded to the plugin that is currently active. * But it's possible that two plugins have name clashes between commands and both require a different set of parameters. * In that case you can use {@linkcode customRequestSpecific} to target your parameters to a specific plugin. */ get customRequests(): PromisifyProps>>; /** * Similar to {@linkcode customRequests} but targets a specific plugin. * If the specified plugin is not the active plugin, this is a no-op. * * ## Example usage * ```js * adLad.customRequestsForPlugin("plugin-name").myCoolCustomRequest("foo"); * ``` * @template {TPlugins["name"]} TPluginName * @param {TPluginName} plugin */ customRequestsForPlugin(plugin: TPluginName): PromisifyProps["customRequests"]>; } export type AdErrorReason = "no-active-plugin" | "not-supported" | "no-ad-available" | "adblocker" | "time-constraint" | "user-dismissed" | "already-playing" | "unknown"; export type ShowFullScreenAdResult = { /** * - When this is `true` when, ad was shown. In this case `errorReason` will be `null`. * - When this is `false`, `errorReason` is a non `null` value, though it might be `"unknown"` when the error reason wasn't known. * - When this is `null`, the plugin wasn't sure if an ad was shown. In this case `errorReason` will be `null`. */ didShowAd: boolean | null; /** * The reason why an ad wasn't shown, * this is a string when `didShowAd` was `false`, and `null` otherwise. * A list of possible values can be found at {@linkcode AdErrorReason }. */ errorReason: AdErrorReason | null; }; export type AdLadPluginInitializeContext = { /** * When this is true, the plugin should show test ads when supported by the ad provider. */ useTestAds: boolean; /** * Update the `needsPause` state of AdLad. * You can call this as often as you like, if you call this with the same `needsPause` state twice in a row, an event is only fired once. * Requires {@linkcode AdLadPlugin.manualNeedsPause } to be true and will throw otherwise. */ setNeedsPause: (needsPause: boolean) => void; /** * Update the `needsMute` state of AdLad. * You can call this as often as you like, if you call this with the same `needsMute` state twice in a row, an event is only fired once. * Requires {@linkcode AdLadPlugin.manualNeedsMute } to be true and will throw otherwise. */ setNeedsMute: (needsMute: boolean) => void; /** * Update the `canShowFullScreenAd` state of AdLad. * You can call this as often as you like, if you call this with the same `canShowFullScreenAd` state twice in a row, an event is only fired once. * Requires {@linkcode AdLadPlugin.showFullScreenAd } to be implemented, otherwise this has no effect. * By default `canShowFullScreenAd` is true when `showFullScreenAd` is implemented, * so if you want this to be false from the start, make sure to call this within your initialize hook. */ setCanShowFullScreenAd: (canShowFullScreenAd: boolean) => void; /** * Update the `canShowRewardedAd` state of AdLad. * You can call this as often as you like, if you call this with the same `canShowRewardedAd` state twice in a row, an event is only fired once. * Requires {@linkcode AdLadPlugin.showRewardedAd } to be implemented, otherwise this has no effect. * By default `canShowRewardedAd` is true when `showRewardedAd` is implemented, * so if you want this to be false from the start, make sure to call this within your initialize hook. */ setCanShowRewardedAd: (canShowRewardedAd: boolean) => void; /** * Adds a script tag to the page and returns a promise that resolves once the script tag has loaded. */ loadScriptTag: (src: string) => Promise; }; export type ShowBannerAdPluginOptions = { el: HTMLElement; id: string; width: number; height: number; }; export type DestroyBannerAdPluginOptions = { el: HTMLElement; id: string; }; export type AdLadPlugin = { /** * The name of your plugin, may only contain lowercase `a-z`, `_` or `-`. May not start or end with `_` or `-`. */ name: string; /** * While it is recommended to users to manually choose a plugin either * via the {@linkcode AdLadOptions.plugin } or via the query string, if this is not done, * plugin developers can tell AdLad whether they wish their plugin to be the active one. * You can lock at the domain for instance or whether the page is currently embedded on a game portal. * When more than one plugin returns true, the last plugin that was provided will be picked. */ shouldBeActive?: (() => boolean) | undefined; /** * Gets called the moment AdLad is instantiated and your * plugin is chosen as the active plugin. If you return a promise, no other hooks will be called until the hook resolves. * You may throw an error or reject the promise, plugin hooks will still be called in that case. * But it's important to not leave the promise hanging indefinitely, * because this will cause calls by the user to stay hanging as well, potentially locking up the game forever. */ initialize?: ((context: AdLadPluginInitializeContext) => void | Promise) | undefined; /** * Hook that gets called when the user * wants to show a full screen non rewarded ad. This should return a promise that resolves once the ad is no longer visible. * The return result should contain info about whether an ad was shown. * You can check {@linkcode ShowFullScreenAdResult } to see which rules your result should abide. But your * result will be sanitized in case you don't. If your hook rejects, `errorReason: "unknown"` is automatically returned. */ showFullScreenAd?: ((userOptions: any) => Promise) | undefined; /** * Hook that gets called when the user * wants to show a rewarded ad. This should return a promise that resolves once the ad is no longer visible. * The return result should contain info about whether an ad was shown. * You can check {@linkcode ShowFullScreenAdResult } to see which rules your result should abide. But your * result will be sanitized in case you don't. If your hook rejects, `errorReason: "unknown"` is automatically returned. */ showRewardedAd?: ((userOptions: any) => Promise) | undefined; /** * Hook that gets called when the user wishes * to show a banner ad. */ showBannerAd?: ((options: ShowBannerAdPluginOptions, userOptions: any) => void | Promise) | undefined; /** * Hook that gets called when the user wishes * to destroy a banner ad. */ destroyBannerAd?: ((options: DestroyBannerAdPluginOptions, userOptions: any) => void | Promise) | undefined; /** * Hook that gets called when gameplay has started. * This will never be called twice in a row without `gameplayStop` being called first, except when the game starts for the first time. */ gameplayStart?: ((options: any) => void | Promise) | undefined; /** * Hook that gets called when gameplay has stopped. * This will never be called twice in a row without `gameplayStop` being called first. */ gameplayStop?: ((options: any) => void | Promise) | undefined; /** * Hook that gets called when loading has started. * This is called automatically once your `initialize` hook promise resolves. * This will never be called twice in a row without `loadStop` being called first, except when the game loads for the first time. */ loadStart?: (() => void | Promise) | undefined; /** * Hook that gets called when loading has stopped. * This will never be called twice in a row without `loadStart` being called first. */ loadStop?: (() => void | Promise) | undefined; /** * Set to `true` (default is `false`) when you manually want to * let AdLad know about the `needsPause` value and its events. By default `needsPause` is automatically managed and * set to `true` during full screen ads. But when you enable manual management you have more control over this. * For example, you could make sure `needsPause` never becomes `true` when no ad is shown, even though `showFullScreenAd` was called. */ manualNeedsPause?: boolean | undefined; /** * Set to `true` (default is `false`) when you manually want to * let AdLad know about the `needsMute` value and its events. By default `needsMute` is automatically managed and * set to `true` during full screen ads. But when you enable manual management you have more control over this. * For example, you could make sure `needsMute` becomes true once the ad actually loads, instead of the moment it is requested. */ manualNeedsMute?: boolean | undefined; customRequests?: { [x: string]: (...args: any[]) => any; } | undefined; }; export type AdLadInvalidQueryStringBehaviour = "error" | "fallback" | "none"; export type AdLadOptions = { /** * The list of plugins that will be supported. * Only a single plugin of this list will be activated depending on which options have been provided to AdLad. * By default each plugin will tell AdLad whether it wishes to be active. * If more than one plugin wishes to be active, the last plugin of this list will be picked. */ plugins?: TPlugins[] | undefined; /** * The name of the plugin to use. * This will override which plugin was chosen by the order of the `plugins` argument, * and will activate the provided plugin regardless of whether it choose to be active or not. * If the passed value is not an existing plugin, an error will be thrown. * An exception to this is the value `"none"`, which will cause no plugin to be selected. * `"none"` can still be overridden by the query string. */ plugin?: string | undefined; /** * Set to true when in development, when plugins support test ads, * they will use those instead of production ads. */ useTestAds?: boolean | undefined; /** * When set to true (which is the default) * allows changing the selected plugin using the `?adlad=` query string. * You can change the key of the query string parameter using `pluginSelectQueryStringKey`. */ allowQueryStringPluginSelection?: boolean | undefined; /** * The key used for selecting plugins using the query string. */ pluginSelectQueryStringKey?: string | undefined; /** * The behaviour when * an invalid plugin name is passed in the `?adlad=` query string. Defaults to `"fallback"`. * - `"error"` throws an error during instantiation of the `AdLad` class. * This will make your game completely unplayable when an invalid query string is passed. * Preventing users from playing your game with ads disabled. * - `"fallback"` switches back to behaviour from the `plugins` and `plugin` parameters, * making sure the game is still playable but also disallows disabling ads. This is the default behaviour. * - `"none"` No plugin is picked when an invalid value is provided. This completely disables ads. * While this makes it possible for users to easily disable ads when they are aware of the query string, * this might also be useful when you wish to debug your game without the distraction of third party requests and errors. * For example, you would be able to set `?adlad=none` to completely disable plugins this way. */ invalidQueryStringPluginBehaviour?: AdLadInvalidQueryStringBehaviour | undefined; }; /** * Utility to provide methods with autocompletion and type checking based on which plugins are used. */ export type CollectPluginArgs = OmitMissing>; /** * Utility that removes `never` and `undefined` types from an object. */ export type OmitMissing = { [K in keyof T as T[K] extends undefined ? never : K]: T[K]; }; export type CollectPluginArgsWithNever = { [TPluginName in TPlugins["name"]]: GetMaybeParameters, TOptionsName, TArgIndex>; }; /** * Filters all plugins that do not match the given name from a union. */ export type GetPluginFromUnionByName = TPluginsUnion extends { name: TPluginName; } ? TPluginsUnion : never; /** * Gets the type of a parameter from the provided method on a plugin. * Results in `never` when either the method or the parameter doesn't exist. */ export type GetMaybeParameters = TOptionsName extends keyof TPlugin ? TPlugin[TOptionsName] extends (...args: any[]) => any ? Parameters extends infer Params ? Params extends any[] ? Params[TArgIndex] : never : never : never : never; export type PromisifyProps = { [K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise : T[K]; }; export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never; export type GetCustomRequestCommands = TPlugins extends { customRequests: any; } ? TPlugins["customRequests"] extends { [x: string]: any; } ? TPlugins["customRequests"] : never : never;