1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2026-03-02 03:39:21 +00:00

Initial commit

This commit is contained in:
Tobias Springer
2020-05-09 16:45:23 +02:00
commit 93c6ea683d
304 changed files with 56031 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
import { AdProviderInterface } from "../ad_provider";
/**
* Stores information about where we are iframed
*/
export class EmbedProvider {
/**
* @returns {string}
*/
getId() {
abstract;
return "";
}
/**
* Whether this provider supports ads
* @returns {boolean}
*/
getSupportsAds() {
return false;
}
/**
* Returns the ad provider
* @returns {typeof AdProviderInterface}
*/
getAdProvider() {
abstract;
return null;
}
/**
* Whetherexternal links are supported
* @returns {boolean}
*/
getSupportsExternalLinks() {
return true;
}
/**
* Returns whether this provider is iframed
* @returns {boolean}
*/
getIsIframed() {
return true;
}
}

View File

@@ -0,0 +1,16 @@
import { AdinplayAdProvider } from "../../ad_providers/adinplay";
import { ShapezioWebsiteEmbedProvider } from "./shapezio_website";
export class ArmorgamesEmbedProvider extends ShapezioWebsiteEmbedProvider {
getId() {
return "armorgames";
}
getAdProvider() {
return AdinplayAdProvider;
}
getIsIframed() {
return true;
}
}

View File

@@ -0,0 +1,11 @@
import { ShapezioWebsiteEmbedProvider } from "./shapezio_website";
export class CrazygamesEmbedProvider extends ShapezioWebsiteEmbedProvider {
getId() {
return "crazygames";
}
getIsIframed() {
return true;
}
}

View File

@@ -0,0 +1,20 @@
import { AdinplayAdProvider } from "../../ad_providers/adinplay";
import { ShapezioWebsiteEmbedProvider } from "./shapezio_website";
export class GamedistributionEmbedProvider extends ShapezioWebsiteEmbedProvider {
getId() {
return "gamedistribution";
}
getAdProvider() {
return AdinplayAdProvider;
}
getSupportsExternalLinks() {
return false;
}
getIsIframed() {
return true;
}
}

View File

@@ -0,0 +1,15 @@
import { ShapezioWebsiteEmbedProvider } from "./shapezio_website";
export class IogamesSpaceEmbedProvider extends ShapezioWebsiteEmbedProvider {
getId() {
return "iogames.space";
}
getShowUpvoteHints() {
return true;
}
getIsIframed() {
return true;
}
}

View File

@@ -0,0 +1,24 @@
import { NoAdProvider } from "../../ad_providers/no_ad_provider";
import { EmbedProvider } from "../embed_provider";
export class KongregateEmbedProvider extends EmbedProvider {
getId() {
return "kongregate";
}
getSupportsAds() {
return false;
}
getAdProvider() {
return NoAdProvider;
}
getSupportsExternalLinks() {
return true;
}
getIsIframed() {
return true;
}
}

View File

@@ -0,0 +1,11 @@
import { ShapezioWebsiteEmbedProvider } from "./shapezio_website";
export class MiniclipEmbedProvider extends ShapezioWebsiteEmbedProvider {
getId() {
return "miniclip";
}
getIsIframed() {
return true;
}
}

View File

@@ -0,0 +1,24 @@
import { EmbedProvider } from "../embed_provider";
import { AdinplayAdProvider } from "../../ad_providers/adinplay";
export class ShapezioWebsiteEmbedProvider extends EmbedProvider {
getId() {
return "shapezio";
}
getSupportsAds() {
return true;
}
getAdProvider() {
return AdinplayAdProvider;
}
getIsIframed() {
return false;
}
getSupportsExternalLinks() {
return true;
}
}

View File

@@ -0,0 +1,69 @@
import { GameAnalyticsInterface } from "../game_analytics";
import { createLogger } from "../../core/logging";
import { ShapeDefinition } from "../../game/shape_definition";
import { gameCreationAction } from "../../states/ingame";
const logger = createLogger("ga_com");
export class GameAnalyticsDotCom extends GameAnalyticsInterface {
/**
* @returns {Promise<void>}
*/
initialize() {
try {
const ga = window.gameanalytics.GameAnalytics;
ga.configureBuild(G_APP_ENVIRONMENT + "@" + G_BUILD_VERSION + "@" + G_BUILD_COMMIT_HASH);
ga.setEnabledInfoLog(G_IS_DEV);
ga.setEnabledVerboseLog(G_IS_DEV);
// @ts-ignore
ga.initialize(window.ga_comKey, window.ga_comToken);
// start new session
ga.startSession();
} catch (ex) {
logger.warn("ga_com init error:", ex);
}
return Promise.resolve();
}
/**
* @param {ShapeDefinition} definition
*/
handleShapeDelivered(definition) {
const hash = definition.getHash();
logger.log("Deliver:", hash);
}
/**
* Handles the given level completed
* @param {number} level
*/
handleLevelCompleted(level) {
logger.log("Complete level", level);
try {
const gaD = window.gameanalytics;
const ga = gaD.GameAnalytics;
ga.addProgressionEvent(gaD.EGAProgressionStatus.Complete, "story", "" + level);
} catch (ex) {
logger.error("ga_com lvl complete error:", ex);
}
}
/**
* Handles the given upgrade completed
* @param {string} id
* @param {number} level
*/
handleUpgradeUnlocked(id, level) {
logger.log("Unlock upgrade", id, level);
try {
const gaD = window.gameanalytics;
const ga = gaD.GameAnalytics;
ga.addProgressionEvent(gaD.EGAProgressionStatus.Complete, "upgrade", id, "" + level);
} catch (ex) {
logger.error("ga_com upgrade unlock error:", ex);
}
}
}

View File

@@ -0,0 +1,101 @@
import { AnalyticsInterface } from "../analytics";
import { Math_random, performanceNow } from "../../core/builtins";
import { createLogger } from "../../core/logging";
const logger = createLogger("ga");
export class GoogleAnalyticsImpl extends AnalyticsInterface {
initialize() {
this.lastUiClickTracked = -1000;
setInterval(() => this.internalTrackAfkEvent(), 120 * 1000);
// Analytics is already loaded in the html
return Promise.resolve();
}
setUserContext(userName) {
try {
if (window.gtag) {
logger.log("📊 Setting user context:", userName);
window.gtag("set", {
player: userName,
});
}
} catch (ex) {
logger.warn("📊 Failed to set user context:", ex);
}
}
trackStateEnter(stateId) {
const nonInteractionStates = [
"LoginState",
"MainMenuState",
"PreloadState",
"RegisterState",
"WatchAdState",
];
try {
if (window.gtag) {
logger.log("📊 Tracking state enter:", stateId);
window.gtag("event", "enter_state", {
event_category: "ui",
event_label: stateId,
non_interaction: nonInteractionStates.indexOf(stateId) >= 0,
});
}
} catch (ex) {
logger.warn("📊 Failed to track state analytcis:", ex);
}
}
trackDecision(decisionName) {
try {
if (window.gtag) {
logger.log("📊 Tracking decision:", decisionName);
window.gtag("event", "decision", {
event_category: "ui",
event_label: decisionName,
non_interaction: true,
});
}
} catch (ex) {
logger.warn("📊 Failed to track state analytcis:", ex);
}
}
trackUiClick(elementName) {
// Only track a fraction of clicks to not annoy google analytics
if (Math_random() < 0.9) {
return;
}
const stateKey = this.app.stateMgr.getCurrentState().key;
const fullSelector = stateKey + ">" + elementName;
try {
if (window.gtag) {
logger.log("📊 Tracking click on:", fullSelector);
window.gtag("event", "click", {
event_category: "ui",
event_label: fullSelector,
});
}
} catch (ex) {
logger.warn("📊 Failed to track ui click:", ex);
}
}
/**
* Tracks an event so GA keeps track of the user
*/
internalTrackAfkEvent() {
if (window.gtag) {
window.gtag("event", "afk", {
event_category: "ping",
event_label: "timed",
});
}
}
}

View File

@@ -0,0 +1,146 @@
import { MusicInstanceInterface, SoundInstanceInterface, SoundInterface } from "../sound";
import { cachebust } from "../../core/cachebust";
import { createLogger } from "../../core/logging";
const { Howl, Howler } = require("howler");
const logger = createLogger("sound/browser");
class SoundInstance extends SoundInstanceInterface {
constructor(key, url) {
super(key, url);
this.howl = null;
this.instance = null;
}
load() {
return Promise.race([
new Promise((resolve, reject) => {
setTimeout(reject, G_IS_DEV ? 5000 : 60000);
}),
new Promise(resolve => {
this.howl = new Howl({
src: cachebust("res/sounds/" + this.url),
autoplay: false,
loop: false,
volume: 0,
preload: true,
onload: () => {
resolve();
},
onloaderror: (id, err) => {
logger.warn("Sound", this.url, "failed to load:", id, err);
this.howl = null;
resolve();
},
onplayerror: (id, err) => {
logger.warn("Sound", this.url, "failed to play:", id, err);
},
});
}),
]);
}
play(volume) {
if (this.howl) {
if (!this.instance) {
this.instance = this.howl.play();
} else {
this.howl.play(this.instance);
this.howl.seek(0, this.instance);
}
this.howl.volume(volume, this.instance);
}
}
deinitialize() {
if (this.howl) {
this.howl.unload();
this.howl = null;
this.instance = null;
}
}
}
class MusicInstance extends MusicInstanceInterface {
constructor(key, url) {
super(key, url);
this.howl = null;
this.instance = null;
this.playing = false;
}
load() {
return Promise.race([
new Promise((resolve, reject) => {
setTimeout(reject, G_IS_DEV ? 5000 : 60000);
}),
new Promise((resolve, reject) => {
this.howl = new Howl({
src: cachebust("res/sounds/music/" + this.url),
autoplay: false,
loop: true,
html5: true,
volume: 1,
preload: true,
pool: 2,
onload: () => {
resolve();
},
onloaderror: (id, err) => {
logger.warn(this, "Music", this.url, "failed to load:", id, err);
this.howl = null;
resolve();
},
onplayerror: (id, err) => {
logger.warn(this, "Music", this.url, "failed to play:", id, err);
},
});
}),
]);
}
stop() {
if (this.howl && this.instance) {
this.playing = false;
this.howl.pause(this.instance);
}
}
isPlaying() {
return this.playing;
}
play() {
if (this.howl) {
this.playing = true;
if (this.instance) {
this.howl.play(this.instance);
} else {
this.instance = this.howl.play();
}
}
}
deinitialize() {
if (this.howl) {
this.howl.unload();
this.howl = null;
this.instance = null;
}
}
}
export class SoundImplBrowser extends SoundInterface {
constructor(app) {
super(app, SoundInstance, MusicInstance);
}
initialize() {
return super.initialize();
}
deinitialize() {
return super.deinitialize().then(() => Howler.unload());
}
}

View File

@@ -0,0 +1,97 @@
import { FILE_NOT_FOUND, StorageInterface } from "../storage";
import { createLogger } from "../../core/logging";
const logger = createLogger("storage/browser");
const LOCAL_STORAGE_UNAVAILABLE = "local-storage-unavailable";
const LOCAL_STORAGE_NO_WRITE_PERMISSION = "local-storage-no-write-permission";
let randomDelay = () => 0;
if (G_IS_DEV) {
// Random delay for testing
// randomDelay = () => 500;
}
export class StorageImplBrowser extends StorageInterface {
constructor(app) {
super(app);
this.currentBusyFilename = false;
}
initialize() {
return new Promise((resolve, reject) => {
// Check for local storage availability in general
if (!window.localStorage) {
alert("Local storage is not available! Please upgrade to a newer browser!");
reject(LOCAL_STORAGE_UNAVAILABLE);
}
// Check if we can set and remove items
try {
window.localStorage.setItem("storage_availability_test", "1");
window.localStorage.removeItem("storage_availability_test");
} catch (e) {
alert(
"It seems we don't have permission to write to local storage! Please update your browsers settings or use a different browser!"
);
reject(LOCAL_STORAGE_NO_WRITE_PERMISSION);
return;
}
setTimeout(resolve, 0);
});
}
writeFileAsync(filename, contents) {
if (this.currentBusyFilename === filename) {
logger.warn("Attempt to write", filename, "while write process is not finished!");
}
this.currentBusyFilename = filename;
window.localStorage.setItem(filename, contents);
return new Promise((resolve, reject) => {
setTimeout(() => {
this.currentBusyFilename = false;
resolve();
}, 0);
});
}
writeFileSyncIfSupported(filename, contents) {
window.localStorage.setItem(filename, contents);
return true;
}
readFileAsync(filename) {
if (this.currentBusyFilename === filename) {
logger.warn("Attempt to read", filename, "while write progress on it is ongoing!");
}
return new Promise((resolve, reject) => {
const contents = window.localStorage.getItem(filename);
if (!contents) {
// File not found
setTimeout(() => reject(FILE_NOT_FOUND), randomDelay());
return;
}
// File read, simulate delay
setTimeout(() => resolve(contents), 0);
});
}
deleteFileAsync(filename) {
if (this.currentBusyFilename === filename) {
logger.warn("Attempt to delete", filename, "while write progres on it is ongoing!");
}
this.currentBusyFilename = filename;
return new Promise((resolve, reject) => {
window.localStorage.removeItem(filename);
setTimeout(() => {
this.currentBusyFilename = false;
resolve();
}, 0);
});
}
}

View File

@@ -0,0 +1,242 @@
import { Math_min } from "../../core/builtins";
import { createLogger } from "../../core/logging";
import { queryParamOptions } from "../../core/query_parameters";
import { clamp } from "../../core/utils";
import { globalConfig, IS_MOBILE } from "../../core/config";
import { NoAdProvider } from "../ad_providers/no_ad_provider";
import { PlatformWrapperInterface } from "../wrapper";
import { ShapezioWebsiteEmbedProvider } from "./embed_providers/shapezio_website";
import { ArmorgamesEmbedProvider } from "./embed_providers/armorgames";
import { IogamesSpaceEmbedProvider } from "./embed_providers/iogames_space";
import { MiniclipEmbedProvider } from "./embed_providers/miniclip";
import { GamedistributionEmbedProvider } from "./embed_providers/gamedistribution";
import { KongregateEmbedProvider } from "./embed_providers/kongregate";
import { CrazygamesEmbedProvider } from "./embed_providers/crazygames";
import { EmbedProvider } from "./embed_provider";
const logger = createLogger("platform/browser");
export class PlatformWrapperImplBrowser extends PlatformWrapperInterface {
initialize() {
this.recaptchaTokenCallback = null;
this.embedProvider = new ShapezioWebsiteEmbedProvider();
if (!G_IS_STANDALONE && queryParamOptions.embedProvider) {
const providerId = queryParamOptions.embedProvider;
switch (providerId) {
case "armorgames": {
this.embedProvider = new ArmorgamesEmbedProvider();
break;
}
case "iogames.space": {
this.embedProvider = new IogamesSpaceEmbedProvider();
break;
}
case "miniclip": {
this.embedProvider = new MiniclipEmbedProvider();
break;
}
case "gamedistribution": {
this.embedProvider = new GamedistributionEmbedProvider();
break;
}
case "kongregate": {
this.embedProvider = new KongregateEmbedProvider();
break;
}
case "crazygames": {
this.embedProvider = new CrazygamesEmbedProvider();
break;
}
default: {
logger.error("Got unsupported embed provider:", providerId);
}
}
}
logger.log("Embed provider:", this.embedProvider.getId());
return super.initialize().then(() => {
// SENTRY
if (!G_IS_DEV && false) {
logger.log(this, "Loading sentry");
const sentryTag = document.createElement("script");
sentryTag.src = "https://browser.sentry-cdn.com/5.7.1/bundle.min.js";
sentryTag.setAttribute("integrity", "TODO_SENTRY");
sentryTag.setAttribute("crossorigin", "anonymous");
sentryTag.addEventListener("load", this.onSentryLoaded.bind(this));
document.head.appendChild(sentryTag);
}
});
}
/**
* @returns {EmbedProvider}
*/
getEmbedProvider() {
return this.embedProvider;
}
onSentryLoaded() {
logger.log("Initializing sentry");
window.Sentry.init({
dsn: "TODO SENTRY DSN",
release: G_APP_ENVIRONMENT + "-" + G_BUILD_VERSION + "@" + G_BUILD_COMMIT_HASH,
// Will cause a deprecation warning, but the demise of `ignoreErrors` is still under discussion.
// See: https://github.com/getsentry/raven-js/issues/73
ignoreErrors: [
// Random plugins/extensions
"top.GLOBALS",
// See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error.html
"originalCreateNotification",
"canvas.contentDocument",
"MyApp_RemoveAllHighlights",
"http://tt.epicplay.com",
"Can't find variable: ZiteReader",
"jigsaw is not defined",
"ComboSearch is not defined",
"http://loading.retry.widdit.com/",
"atomicFindClose",
// Facebook borked
"fb_xd_fragment",
// ISP "optimizing" proxy - `Cache-Control: no-transform` seems to reduce this. (thanks @acdha)
// See http://stackoverflow.com/questions/4113268/how-to-stop-javascript-injection-from-vodafone-proxy
"bmi_SafeAddOnload",
"EBCallBackMessageReceived",
// See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
"conduitPage",
// Generic error code from errors outside the security sandbox
// You can delete this if using raven.js > 1.0, which ignores these automatically.
"Script error.",
// Errors from ads
"Cannot read property 'postMessage' of null",
// Firefox only
"AbortError: The operation was aborted.",
"<unknown>",
],
ignoreUrls: [
// Facebook flakiness
/graph\.facebook\.com/i,
// Facebook blocked
/connect\.facebook\.net\/en_US\/all\.js/i,
// Woopra flakiness
/eatdifferent\.com\.woopra-ns\.com/i,
/static\.woopra\.com\/js\/woopra\.js/i,
// Chrome extensions
/extensions\//i,
/^chrome:\/\//i,
// Other plugins
/127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
/webappstoolbarba\.texthelp\.com\//i,
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i,
],
beforeSend(event, hint) {
if (window.anyModLoaded) {
return null;
}
return event;
},
});
}
getId() {
return "browser@" + this.embedProvider.getId();
}
getUiScale() {
if (IS_MOBILE) {
return 1;
}
const avgDims = Math_min(this.app.screenWidth, this.app.screenHeight);
return clamp((avgDims / 1000.0) * 1.9, 0.1, 10);
}
getSupportsRestart() {
return true;
}
getTouchPanStrength() {
return IS_MOBILE ? 1 : 0.5;
}
openExternalLink(url, force = false) {
logger.log("Opening external:", url);
// if (force || this.embedProvider.getSupportsExternalLinks()) {
window.open(url);
// } else {
// // Do nothing
// alert("This platform does not allow opening external links. You can play on the website directly to open them.");
// }
}
getSupportsAds() {
return this.embedProvider.getSupportsAds();
}
performRestart() {
logger.log("Performing restart");
window.location.reload(true);
}
/**
* Detects if there is an adblocker installed
* @returns {Promise<boolean>}
*/
detectAdblock() {
return Promise.race([
new Promise(resolve => {
// If the request wasn't blocked within a very short period of time, this means
// the adblocker is not active and the request was actually made -> ignore it then
setTimeout(() => resolve(false), 30);
}),
new Promise(resolve => {
fetch("https://googleads.g.doubleclick.net/pagead/id", {
method: "HEAD",
mode: "no-cors",
})
.then(res => {
resolve(false);
})
.catch(err => {
resolve(true);
});
}),
]);
}
initializeAdProvider() {
if (G_IS_DEV && !globalConfig.debug.testAds) {
return Promise.resolve();
}
// First, detect adblocker
return this.detectAdblock().then(hasAdblocker => {
if (hasAdblocker) {
return;
}
const adProvider = this.embedProvider.getAdProvider();
this.app.adProvider = new adProvider(this.app);
return this.app.adProvider.initialize().catch(err => {
logger.error("Failed to initialize ad provider, disabling ads:", err);
this.app.adProvider = new NoAdProvider(this.app);
});
});
}
exitApp() {
// Can not exit app
}
}