Replace @zestyxyz/babylonjs-sdk with local ES-module ZestyBanner
All checks were successful
Build / build (push) Successful in 1m46s
All checks were successful
Build / build (push) Successful in 1m46s
The SDK referenced a global BABYLON namespace which broke ES module imports. Rewrote as three local modules (zestyBanner, zestyNetworking, zestyFormats) using proper named imports from @babylonjs/core, with safe camera access. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6ce2b86569
commit
a6715c62bc
77
src/ads/zestyBanner.ts
Normal file
77
src/ads/zestyBanner.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import {
|
||||
MeshBuilder, ActionManager, ExecuteCodeAction,
|
||||
StandardMaterial, Texture, WebXRState,
|
||||
} from '@babylonjs/core';
|
||||
import type { Scene, Mesh, WebXRDefaultExperience } from '@babylonjs/core';
|
||||
import { formats } from './zestyFormats';
|
||||
import {
|
||||
fetchCampaignAd, sendOnLoadMetric, sendOnClickMetric,
|
||||
AD_REFRESH_INTERVAL,
|
||||
} from './zestyNetworking';
|
||||
import type { AdData } from './zestyNetworking';
|
||||
|
||||
function getCamera(scene: Scene, xr?: WebXRDefaultExperience) {
|
||||
if (xr?.baseExperience?.state === WebXRState.IN_XR) {
|
||||
return xr.baseExperience.camera;
|
||||
}
|
||||
return scene.cameras.length > 0 ? scene.cameras[0] : null;
|
||||
}
|
||||
|
||||
async function loadAd(
|
||||
banner: Mesh, scene: Scene, adUnitId: string,
|
||||
format: string, adRef: { current: AdData }
|
||||
): Promise<void> {
|
||||
const ad = await fetchCampaignAd(adUnitId, format);
|
||||
adRef.current = ad;
|
||||
const mat = banner.material as StandardMaterial;
|
||||
mat.diffuseTexture?.dispose();
|
||||
const tex = new Texture(ad.assetUrl, scene);
|
||||
tex.hasAlpha = true;
|
||||
mat.diffuseTexture = tex;
|
||||
if (ad.campaignId) sendOnLoadMetric(adUnitId, ad.campaignId);
|
||||
}
|
||||
|
||||
function openUrl(url: string): void {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
export function createZestyBanner(
|
||||
adUnitId: string, format: string, height: number,
|
||||
scene: Scene, xr?: WebXRDefaultExperience
|
||||
): Mesh {
|
||||
const fmt = formats[format] ?? formats['billboard'];
|
||||
const planeOpts = { width: fmt.width * height, height };
|
||||
const banner = MeshBuilder.CreatePlane('zestybanner', planeOpts, scene);
|
||||
|
||||
const mat = new StandardMaterial('zestyMat', scene);
|
||||
mat.diffuseTexture = new Texture(fmt.defaultImage, scene);
|
||||
(mat.diffuseTexture as Texture).hasAlpha = true;
|
||||
banner.material = mat;
|
||||
|
||||
const adRef = { current: { assetUrl: '', ctaUrl: '', campaignId: '' } };
|
||||
loadAd(banner, scene, adUnitId, format, adRef);
|
||||
|
||||
banner.actionManager = new ActionManager(scene);
|
||||
banner.actionManager.registerAction(
|
||||
new ExecuteCodeAction(ActionManager.OnPickTrigger, () => {
|
||||
if (!adRef.current.ctaUrl) return;
|
||||
if (xr?.baseExperience) {
|
||||
xr.baseExperience.sessionManager.exitXRAsync()
|
||||
.then(() => openUrl(adRef.current.ctaUrl));
|
||||
} else {
|
||||
openUrl(adRef.current.ctaUrl);
|
||||
}
|
||||
if (adRef.current.campaignId) {
|
||||
sendOnClickMetric(adUnitId, adRef.current.campaignId);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
setInterval(() => {
|
||||
const cam = getCamera(scene, xr);
|
||||
if (!cam || !scene.isActiveMesh(banner)) return;
|
||||
loadAd(banner, scene, adUnitId, format, adRef);
|
||||
}, AD_REFRESH_INTERVAL);
|
||||
|
||||
return banner;
|
||||
}
|
||||
20
src/ads/zestyFormats.ts
Normal file
20
src/ads/zestyFormats.ts
Normal file
@ -0,0 +1,20 @@
|
||||
const CDN_BASE = 'https://cdn.zesty.xyz/sdk/assets';
|
||||
|
||||
export interface FormatInfo {
|
||||
width: number;
|
||||
height: number;
|
||||
defaultImage: string;
|
||||
}
|
||||
|
||||
export const formats: Record<string, FormatInfo> = {
|
||||
billboard: {
|
||||
width: 3.88,
|
||||
height: 1,
|
||||
defaultImage: `${CDN_BASE}/zesty-default-billboard.png`,
|
||||
},
|
||||
'medium-rectangle': {
|
||||
width: 1.2,
|
||||
height: 1,
|
||||
defaultImage: `${CDN_BASE}/zesty-default-medium-rectangle.png`,
|
||||
},
|
||||
};
|
||||
78
src/ads/zestyNetworking.ts
Normal file
78
src/ads/zestyNetworking.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { formats } from './zestyFormats';
|
||||
|
||||
const API_BASE = 'https://api.zesty.market/api';
|
||||
const GRAPHQL_URL = 'https://market.zesty.xyz/graphql';
|
||||
|
||||
export const AD_REFRESH_INTERVAL = 30000;
|
||||
|
||||
export interface AdData {
|
||||
assetUrl: string;
|
||||
ctaUrl: string;
|
||||
campaignId: string;
|
||||
}
|
||||
|
||||
function detectPlatform(): { name: string; confidence: string } {
|
||||
const ua = navigator.userAgent;
|
||||
if (/OculusBrowser/i.test(ua)) return { name: 'Oculus', confidence: 'high' };
|
||||
if (/Wolvic/i.test(ua)) return { name: 'Wolvic', confidence: 'high' };
|
||||
if (/Pico/i.test(ua)) return { name: 'Pico', confidence: 'medium' };
|
||||
return { name: 'Desktop', confidence: 'low' };
|
||||
}
|
||||
|
||||
export async function fetchCampaignAd(
|
||||
adUnitId: string, format: string
|
||||
): Promise<AdData> {
|
||||
try {
|
||||
const url = encodeURI(window.location.href).replace(/\/$/, '');
|
||||
const res = await fetch(
|
||||
`${API_BASE}/ad?ad_unit_id=${adUnitId}&url=${url}`
|
||||
);
|
||||
if (res.status === 200) {
|
||||
const json = await res.json();
|
||||
if (json.Ads?.length) {
|
||||
return {
|
||||
assetUrl: json.Ads[0].asset_url,
|
||||
ctaUrl: json.Ads[0].cta_url,
|
||||
campaignId: json.CampaignId ?? '',
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.warn('Zesty: failed to fetch ad, using default');
|
||||
}
|
||||
const fmt = formats[format] ?? formats['billboard'];
|
||||
return { assetUrl: fmt.defaultImage, ctaUrl: '', campaignId: '' };
|
||||
}
|
||||
|
||||
async function sendMetric(
|
||||
eventType: string, adUnitId: string, campaignId: string
|
||||
): Promise<void> {
|
||||
const { name, confidence } = detectPlatform();
|
||||
const query = `mutation { increment(
|
||||
eventType: ${eventType},
|
||||
spaceId: "${adUnitId}",
|
||||
campaignId: "${campaignId}",
|
||||
platform: { name: ${name}, confidence: ${confidence} }
|
||||
) { message } }`;
|
||||
try {
|
||||
await fetch(GRAPHQL_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query }),
|
||||
});
|
||||
} catch {
|
||||
console.warn(`Zesty: failed to send ${eventType} metric`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendOnLoadMetric(
|
||||
adUnitId: string, campaignId: string
|
||||
): Promise<void> {
|
||||
await sendMetric('visits', adUnitId, campaignId);
|
||||
}
|
||||
|
||||
export async function sendOnClickMetric(
|
||||
adUnitId: string, campaignId: string
|
||||
): Promise<void> {
|
||||
await sendMetric('clicks', adUnitId, campaignId);
|
||||
}
|
||||
@ -5,6 +5,7 @@ import {
|
||||
import { DefaultScene } from "./defaultScene";
|
||||
import { InputControlManager } from "../ship/input/inputControlManager";
|
||||
import log from './logger';
|
||||
import { createZestyBanner } from '../ads/zestyBanner';
|
||||
|
||||
const XR_RENDERING_GROUP = 3;
|
||||
const FADE_DELAY_MS = 500;
|
||||
@ -54,7 +55,18 @@ async function createXRExperience(): Promise<void> {
|
||||
disablePointerSelection: true // Disable to re-enable with custom options
|
||||
});
|
||||
log.debug(WebXRFeaturesManager.GetAvailableFeatures());
|
||||
try {
|
||||
const banner = createZestyBanner("a2170882-f232-4da0-9315-747ee049e642",
|
||||
"billboard", 10, DefaultScene.MainScene, DefaultScene.XR);
|
||||
banner.position.z = 50;
|
||||
banner.position.y = 5;
|
||||
banner.rotation.y = Math.PI;
|
||||
banner.renderOverlay = true;
|
||||
banner.overlayColor = Color3.Red();
|
||||
|
||||
} catch (e) {
|
||||
log.debug("Zesty banner init failed:", e);
|
||||
}
|
||||
// Enable pointer selection with renderingGroupId so laser is never occluded
|
||||
DefaultScene.XR.baseExperience.featuresManager.enableFeature(
|
||||
WebXRFeatureName.POINTER_SELECTION,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user