mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:10:42 +00:00
Migrate from publift to playwire ads (#3039)
## Description: Use playwire ad integration ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
+13
-5
@@ -75,11 +75,11 @@
|
||||
defer
|
||||
></script>
|
||||
|
||||
<!-- Publift/Fuse ads -->
|
||||
<script
|
||||
async
|
||||
src="https://cdn.fuseplatform.net/publift/tags/2/4121/fuse.js"
|
||||
></script>
|
||||
<script data-cfasync="false">
|
||||
window.ramp = window.ramp || {};
|
||||
window.ramp.que = window.ramp.que || [];
|
||||
window.ramp.passiveMode = true;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
window.googletag = window.googletag || { cmd: [] };
|
||||
@@ -266,6 +266,7 @@
|
||||
<player-panel></player-panel>
|
||||
<spawn-timer></spawn-timer>
|
||||
<immunity-timer></immunity-timer>
|
||||
<in-game-header-ad></in-game-header-ad>
|
||||
<game-info-modal></game-info-modal>
|
||||
<alert-frame></alert-frame>
|
||||
<chat-modal></chat-modal>
|
||||
@@ -361,5 +362,12 @@
|
||||
data-cf-beacon='{"token": "03d93e6fefb349c28ee69b408fa25a13"}'
|
||||
></script>
|
||||
<script type="module" src="/src/client/Main.ts"></script>
|
||||
<footer>
|
||||
<script
|
||||
data-cfasync="false"
|
||||
async
|
||||
src="//cdn.intergient.com/1025558/75940/ramp.js"
|
||||
></script>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+71
-81
@@ -1,70 +1,46 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { isInIframe } from "./Utils";
|
||||
|
||||
const LEFT_FUSE = "gutter-ad-container-left";
|
||||
const RIGHT_FUSE = "gutter-ad-container-right";
|
||||
// Minimum screen width to show ads (larger than typical Chromebook)
|
||||
const MIN_SCREEN_WIDTH = 1400;
|
||||
|
||||
@customElement("gutter-ads")
|
||||
export class GutterAds extends LitElement {
|
||||
@state()
|
||||
private isVisible: boolean = false;
|
||||
|
||||
@state()
|
||||
private adLoaded: boolean = false;
|
||||
|
||||
private leftAdType: string = "standard_iab_left2";
|
||||
private rightAdType: string = "standard_iab_rght1";
|
||||
private leftContainerId: string = "gutter-ad-container-left";
|
||||
private rightContainerId: string = "gutter-ad-container-right";
|
||||
private margin: string = "10px";
|
||||
|
||||
// Override createRenderRoot to disable shadow DOM
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private readonly boundUserMeHandler = (event: Event) =>
|
||||
this.onUserMe((event as CustomEvent<UserMeResponse | false>).detail);
|
||||
static styles = css``;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
document.addEventListener(
|
||||
"userMeResponse",
|
||||
this.boundUserMeHandler as EventListener,
|
||||
);
|
||||
}
|
||||
|
||||
private onUserMe(userMeResponse: UserMeResponse | false): void {
|
||||
const flares =
|
||||
userMeResponse === false ? [] : (userMeResponse.player.flares ?? []);
|
||||
const hasFlare = flares.some((flare) => flare.startsWith("pattern:"));
|
||||
if (hasFlare) {
|
||||
console.log("No ads because you have patterns");
|
||||
window.enableAds = false;
|
||||
} else {
|
||||
console.log("No flares, showing ads");
|
||||
this.show();
|
||||
window.enableAds = true;
|
||||
}
|
||||
}
|
||||
|
||||
private isScreenLargeEnough(): boolean {
|
||||
return window.innerWidth >= MIN_SCREEN_WIDTH;
|
||||
document.addEventListener("userMeResponse", () => {
|
||||
if (window.adsEnabled) {
|
||||
console.log("showing gutter ads");
|
||||
this.show();
|
||||
} else {
|
||||
console.log("not showing gutter ads");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Called after the component's DOM is first rendered
|
||||
firstUpdated() {
|
||||
// DOM is guaranteed to be available here
|
||||
console.log("GutterAd DOM is ready");
|
||||
console.log("GutterAdModal DOM is ready");
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
if (!this.isScreenLargeEnough()) {
|
||||
console.log("Screen too small for gutter ads, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInIframe()) {
|
||||
console.log("In iframe, showing gutter ads");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("showing GutterAds");
|
||||
this.isVisible = true;
|
||||
this.requestUpdate();
|
||||
|
||||
@@ -74,58 +50,57 @@ export class GutterAds extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.isVisible = false;
|
||||
console.log("hiding GutterAds");
|
||||
this.destroyAds();
|
||||
document.removeEventListener(
|
||||
"userMeResponse",
|
||||
this.boundUserMeHandler as EventListener,
|
||||
);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private loadAds(): void {
|
||||
console.log("loading ramp ads");
|
||||
// Ensure the container elements exist before loading ads
|
||||
const leftContainer = this.querySelector(`#${LEFT_FUSE}`);
|
||||
const rightContainer = this.querySelector(`#${RIGHT_FUSE}`);
|
||||
const leftContainer = this.querySelector(`#${this.leftContainerId}`);
|
||||
const rightContainer = this.querySelector(`#${this.rightContainerId}`);
|
||||
|
||||
if (!leftContainer || !rightContainer) {
|
||||
console.warn("Ad containers not found in DOM");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!window.fusetag) {
|
||||
console.warn("Fuse tag not available");
|
||||
if (!window.ramp) {
|
||||
console.warn("Playwire RAMP not available");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.adLoaded) {
|
||||
console.log("Ads already loaded, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("registering zones");
|
||||
window.fusetag.que.push(() => {
|
||||
window.fusetag.registerZone(LEFT_FUSE);
|
||||
window.fusetag.registerZone(RIGHT_FUSE);
|
||||
window.ramp.que.push(() => {
|
||||
try {
|
||||
window.ramp.spaAddAds([
|
||||
{
|
||||
type: this.leftAdType,
|
||||
selectorId: this.leftContainerId,
|
||||
},
|
||||
{
|
||||
type: this.rightAdType,
|
||||
selectorId: this.rightContainerId,
|
||||
},
|
||||
]);
|
||||
this.adLoaded = true;
|
||||
console.log(
|
||||
"Playwire ads loaded:",
|
||||
this.leftAdType,
|
||||
this.rightAdType,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load fuse ads:", error);
|
||||
this.hide();
|
||||
console.error("Failed to load Playwire ads:", error);
|
||||
}
|
||||
}
|
||||
|
||||
private destroyAds(): void {
|
||||
if (!window.fusetag) {
|
||||
return;
|
||||
}
|
||||
window.fusetag.que.push(() => {
|
||||
window.fusetag.destroyZone(LEFT_FUSE);
|
||||
window.fusetag.destroyZone(RIGHT_FUSE);
|
||||
});
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -134,11 +109,26 @@ export class GutterAds extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="fixed left-0 top-1/2 -translate-y-1/2 z-10">
|
||||
<div id="${LEFT_FUSE}" data-fuse="lhs_sticky_vrec"></div>
|
||||
<!-- Left Gutter Ad -->
|
||||
<div
|
||||
class="hidden xl:flex fixed left-0 top-1/2 transform -translate-y-1/2 w-[160px] min-h-[600px] z-[100] pointer-events-auto items-center justify-center"
|
||||
style="margin-left: ${this.margin};"
|
||||
>
|
||||
<div
|
||||
id="${this.leftContainerId}"
|
||||
class="w-full h-full flex items-center justify-center p-2"
|
||||
></div>
|
||||
</div>
|
||||
<div class="fixed right-0 top-1/2 -translate-y-1/2 z-10">
|
||||
<div id="${RIGHT_FUSE}" data-fuse="rhs_sticky_vrec"></div>
|
||||
|
||||
<!-- Right Gutter Ad -->
|
||||
<div
|
||||
class="hidden xl:flex fixed right-0 top-1/2 transform -translate-y-1/2 w-[160px] min-h-[600px] z-[100] pointer-events-auto items-center justify-center"
|
||||
style="margin-right: ${this.margin};"
|
||||
>
|
||||
<div
|
||||
id="${this.rightContainerId}"
|
||||
class="w-full h-full flex items-center justify-center p-2"
|
||||
></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
+13
-45
@@ -7,7 +7,7 @@ import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { GameType } from "../core/game/Game";
|
||||
import { UserSettings } from "../core/game/UserSettings";
|
||||
import "./AccountModal";
|
||||
import { getUserMe } from "./Api";
|
||||
import { getUserMe, hasLinkedAccount } from "./Api";
|
||||
import { userAuth } from "./Auth";
|
||||
import { joinLobby } from "./ClientGameRunner";
|
||||
import { fetchCosmetics } from "./Cosmetics";
|
||||
@@ -163,19 +163,12 @@ declare global {
|
||||
GIT_COMMIT: string;
|
||||
INSTANCE_ID: string;
|
||||
turnstile: any;
|
||||
enableAds: boolean;
|
||||
adsEnabled: boolean;
|
||||
PageOS: {
|
||||
session: {
|
||||
newPageView: () => void;
|
||||
};
|
||||
};
|
||||
fusetag: {
|
||||
registerZone: (id: string) => void;
|
||||
destroyZone: (id: string) => void;
|
||||
pageInit: (options?: any) => void;
|
||||
que: Array<() => void>;
|
||||
destroySticky: () => void;
|
||||
};
|
||||
ramp: {
|
||||
que: Array<() => void>;
|
||||
passiveMode: boolean;
|
||||
@@ -184,7 +177,7 @@ declare global {
|
||||
settings?: {
|
||||
slots?: any;
|
||||
};
|
||||
spaNewPage: (url: string) => void;
|
||||
spaNewPage: (url?: string) => void;
|
||||
};
|
||||
showPage?: (pageId: string) => void;
|
||||
}
|
||||
@@ -475,15 +468,14 @@ class Client {
|
||||
|
||||
const onUserMe = async (userMeResponse: UserMeResponse | false) => {
|
||||
// Check if user has actual authentication (discord or email), not just a publicId
|
||||
const loggedIn =
|
||||
userMeResponse !== false &&
|
||||
userMeResponse !== null &&
|
||||
typeof userMeResponse === "object" &&
|
||||
userMeResponse.user &&
|
||||
(userMeResponse.user.discord !== undefined ||
|
||||
userMeResponse.user.email !== undefined);
|
||||
updateMatchmakingButton(loggedIn);
|
||||
const isLinked: boolean = hasLinkedAccount(userMeResponse);
|
||||
updateMatchmakingButton(isLinked);
|
||||
updateAccountNavButton(userMeResponse);
|
||||
const adsEnabled =
|
||||
!crazyGamesSDK.isOnCrazyGames() &&
|
||||
((userMeResponse || null)?.player?.flares?.length ?? 0) === 0;
|
||||
console.log("ads enabled: ", adsEnabled);
|
||||
window.adsEnabled = adsEnabled;
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("userMeResponse", {
|
||||
detail: userMeResponse,
|
||||
@@ -653,8 +645,6 @@ class Client {
|
||||
updateSliderProgress(slider);
|
||||
slider.addEventListener("input", () => updateSliderProgress(slider));
|
||||
});
|
||||
|
||||
this.initializeFuseTag();
|
||||
}
|
||||
|
||||
private handleUrl() {
|
||||
@@ -847,7 +837,6 @@ class Client {
|
||||
if (startingModal && startingModal instanceof GameStartingModal) {
|
||||
startingModal.show();
|
||||
}
|
||||
this.gutterAds.hide();
|
||||
},
|
||||
() => {
|
||||
this.joinModal.close();
|
||||
@@ -858,6 +847,9 @@ class Client {
|
||||
(ad as HTMLElement).style.display = "none";
|
||||
});
|
||||
|
||||
if (window.PageOS?.session?.newPageView) {
|
||||
window.PageOS.session.newPageView();
|
||||
}
|
||||
crazyGamesSDK.loadingStop();
|
||||
crazyGamesSDK.gameplayStart();
|
||||
document.body.classList.add("in-game");
|
||||
@@ -902,8 +894,6 @@ class Client {
|
||||
document.body.classList.remove("in-game");
|
||||
|
||||
crazyGamesSDK.gameplayStop();
|
||||
|
||||
this.gutterAds.hide();
|
||||
this.publicLobby.leaveLobby();
|
||||
}
|
||||
|
||||
@@ -925,28 +915,6 @@ class Client {
|
||||
}
|
||||
}
|
||||
|
||||
private initializeFuseTag() {
|
||||
const tryInitFuseTag = (): boolean => {
|
||||
if (window.fusetag && typeof window.fusetag.pageInit === "function") {
|
||||
console.log("initializing fuse tag");
|
||||
window.fusetag.que.push(() => {
|
||||
window.fusetag.pageInit({
|
||||
blockingFuseIds: ["lhs_sticky_vrec", "rhs_sticky_vrec"],
|
||||
});
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (tryInitFuseTag()) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private async getTurnstileToken(
|
||||
lobby: JoinLobbyEvent,
|
||||
): Promise<string | null> {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { RefreshGraphicsEvent as RedrawGraphicsEvent } from "../InputHandler";
|
||||
import { FrameProfiler } from "./FrameProfiler";
|
||||
import { TransformHandler } from "./TransformHandler";
|
||||
import { UIState } from "./UIState";
|
||||
import { AdTimer } from "./layers/AdTimer";
|
||||
import { AlertFrame } from "./layers/AlertFrame";
|
||||
import { BuildMenu } from "./layers/BuildMenu";
|
||||
import { ChatDisplay } from "./layers/ChatDisplay";
|
||||
@@ -20,6 +19,7 @@ import { GameLeftSidebar } from "./layers/GameLeftSidebar";
|
||||
import { GameRightSidebar } from "./layers/GameRightSidebar";
|
||||
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
|
||||
import { ImmunityTimer } from "./layers/ImmunityTimer";
|
||||
import { InGameHeaderAd } from "./layers/InGameHeaderAd";
|
||||
import { Layer } from "./layers/Layer";
|
||||
import { Leaderboard } from "./layers/Leaderboard";
|
||||
import { MainRadialMenu } from "./layers/MainRadialMenu";
|
||||
@@ -244,6 +244,14 @@ export function createRenderer(
|
||||
}
|
||||
immunityTimer.game = game;
|
||||
|
||||
const inGameHeaderAd = document.querySelector(
|
||||
"in-game-header-ad",
|
||||
) as InGameHeaderAd;
|
||||
if (!(inGameHeaderAd instanceof InGameHeaderAd)) {
|
||||
console.error("in-game header ad not found");
|
||||
}
|
||||
inGameHeaderAd.game = game;
|
||||
|
||||
// When updating these layers please be mindful of the order.
|
||||
// Try to group layers by the return value of shouldTransform.
|
||||
// Not grouping the layers may cause excessive calls to context.save() and context.restore().
|
||||
@@ -287,7 +295,7 @@ export function createRenderer(
|
||||
playerPanel,
|
||||
headsUpMessage,
|
||||
multiTabModal,
|
||||
new AdTimer(game),
|
||||
inGameHeaderAd,
|
||||
alertFrame,
|
||||
performanceOverlay,
|
||||
];
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
const AD_SHOW_TICKS = 2 * 60 * 10; // 2 minute
|
||||
|
||||
export class AdTimer implements Layer {
|
||||
private isHidden: boolean = false;
|
||||
|
||||
constructor(private g: GameView) {}
|
||||
|
||||
init() {}
|
||||
|
||||
public async tick() {
|
||||
if (this.isHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gameTicks = this.g.ticks() - this.g.config().numSpawnPhaseTurns();
|
||||
if (gameTicks > AD_SHOW_TICKS) {
|
||||
console.log("destroying sticky ads");
|
||||
window.fusetag?.que?.push(() => {
|
||||
window.fusetag?.destroySticky?.();
|
||||
});
|
||||
this.isHidden = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
const AD_SHOW_TICKS = 2 * 60 * 10; // 2 minutes
|
||||
const HEADER_AD_TYPE = "standard_iab_head1";
|
||||
const HEADER_AD_CONTAINER_ID = "header-ad-container";
|
||||
const TWO_XL_BREAKPOINT = 1536;
|
||||
|
||||
@customElement("in-game-header-ad")
|
||||
export class InGameHeaderAd extends LitElement implements Layer {
|
||||
public game: GameView;
|
||||
|
||||
private isHidden: boolean = false;
|
||||
private adLoaded: boolean = false;
|
||||
private shouldShow: boolean = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.showHeaderAd();
|
||||
}
|
||||
|
||||
private showHeaderAd(): void {
|
||||
// Don't show header ad on screens smaller than 2xl
|
||||
if (window.innerWidth < TWO_XL_BREAKPOINT) {
|
||||
return;
|
||||
}
|
||||
if (!window.adsEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.shouldShow = true;
|
||||
this.requestUpdate();
|
||||
|
||||
// Wait for the element to render before loading the ad
|
||||
this.updateComplete.then(() => {
|
||||
this.loadAd();
|
||||
});
|
||||
}
|
||||
|
||||
private loadAd(): void {
|
||||
if (!window.ramp) {
|
||||
console.warn("Playwire RAMP not available for header ad");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
window.ramp.que.push(() => {
|
||||
try {
|
||||
window.ramp.spaAddAds([
|
||||
{
|
||||
type: HEADER_AD_TYPE,
|
||||
selectorId: HEADER_AD_CONTAINER_ID,
|
||||
},
|
||||
]);
|
||||
this.adLoaded = true;
|
||||
console.log("Header ad loaded:", HEADER_AD_TYPE);
|
||||
} catch (e) {
|
||||
console.error("Failed to add header ad:", e);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load header ad:", error);
|
||||
}
|
||||
}
|
||||
|
||||
private hideHeaderAd(): void {
|
||||
this.shouldShow = false;
|
||||
this.adLoaded = false;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
public tick() {
|
||||
if (this.isHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gameTicks =
|
||||
this.game.ticks() - this.game.config().numSpawnPhaseTurns();
|
||||
if (gameTicks > AD_SHOW_TICKS) {
|
||||
console.log("destroying header ad and refreshing PageOS");
|
||||
this.hideHeaderAd();
|
||||
this.isHidden = true;
|
||||
|
||||
if (window.PageOS?.session?.newPageView) {
|
||||
window.PageOS.session.newPageView();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.shouldShow) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div
|
||||
id="${HEADER_AD_CONTAINER_ID}"
|
||||
class="hidden 2xl:flex fixed top-0 left-1/2 -translate-x-1/2 z-[100] justify-center items-center pointer-events-auto p-0 -mt-[20px]"
|
||||
></div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user