From 145f73490741ec4d249e5859150456ece65b8d22 Mon Sep 17 00:00:00 2001 From: tobspr Date: Mon, 20 Jun 2022 18:22:23 +0200 Subject: [PATCH] Allow playing full version in browser via steam sso --- gulp/preloader/preloader.js | 14 ++++ res/ui/steam_signin.png | Bin 0 -> 13101 bytes src/css/states/main_menu.scss | 39 +++++++++++ src/css/states/mods.scss | 3 + src/css/states/settings.scss | 6 ++ src/js/core/restriction_manager.js | 5 ++ src/js/core/steam_sso.js | 81 ++++++++++++++++++++++ src/js/core/utils.js | 4 +- src/js/game/modes/levels.js | 3 +- src/js/game/modes/regular.js | 5 +- src/js/platform/api.js | 32 ++++----- src/js/platform/browser/game_analytics.js | 5 ++ src/js/platform/browser/wrapper.js | 3 +- src/js/profile/application_settings.js | 6 ++ src/js/profile/setting_types.js | 26 ++++++- src/js/states/main_menu.js | 24 ++++++- src/js/states/mods.js | 21 +++--- src/js/states/preload.js | 15 ++-- translations/base-en.yaml | 18 +++++ 19 files changed, 267 insertions(+), 43 deletions(-) create mode 100644 res/ui/steam_signin.png create mode 100644 src/js/core/steam_sso.js diff --git a/gulp/preloader/preloader.js b/gulp/preloader/preloader.js index 553cd9c1..7e2fe518 100644 --- a/gulp/preloader/preloader.js +++ b/gulp/preloader/preloader.js @@ -2,6 +2,20 @@ var loadTimeout = null; var callbackDone = false; + var searchString = window.location.search; + if (searchString.includes("steam_sso_auth_token=")) { + var pos = searchString.indexOf("steam_sso_auth_token"); + const authToken = searchString.substring(pos + 21, pos + 57); + try { + window.localStorage.setItem("steam_sso_auth_token", authToken); + window.location.replace(window.location.protocol + "//" + window.location.host); + } catch (ex) { + alert("Failed to login via Steam SSO: " + ex); + window.location.replace("https://shapez.io"); + } + return; + } + // Catch load errors function errorHandler(event, source, lineno, colno, error) { diff --git a/res/ui/steam_signin.png b/res/ui/steam_signin.png new file mode 100644 index 0000000000000000000000000000000000000000..cd3120f81bdb4e3220fb76c209ea49aaa6f4884f GIT binary patch literal 13101 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 0; } diff --git a/src/js/core/steam_sso.js b/src/js/core/steam_sso.js new file mode 100644 index 00000000..f04fb3bc --- /dev/null +++ b/src/js/core/steam_sso.js @@ -0,0 +1,81 @@ +import { T } from "../translations"; +import { openStandaloneLink } from "./config"; + +export let WEB_STEAM_SSO_AUTHENTICATED = false; + +export async function authorizeViaSSOToken(app, dialogs) { + if (G_IS_STANDALONE) { + return; + } + + if (window.location.search.includes("sso_logout_silent")) { + window.localStorage.setItem("steam_sso_auth_token", ""); + window.location.replace("/"); + return new Promise(() => null); + } + + if (window.location.search.includes("sso_logout")) { + const { ok } = dialogs.showWarning(T.dialogs.steamSsoError.title, T.dialogs.steamSsoError.desc); + window.localStorage.setItem("steam_sso_auth_token", ""); + ok.add(() => window.location.replace("/")); + return new Promise(() => null); + } + + if (window.location.search.includes("steam_sso_no_ownership")) { + const { ok, getStandalone } = dialogs.showWarning( + T.dialogs.steamSsoNoOwnership.title, + T.dialogs.steamSsoNoOwnership.desc, + ["ok", "getStandalone:good"] + ); + window.localStorage.setItem("steam_sso_auth_token", ""); + getStandalone.add(() => { + openStandaloneLink(app, "sso_ownership"); + window.location.replace("/"); + }); + ok.add(() => window.location.replace("/")); + return new Promise(() => null); + } + + const token = window.localStorage.getItem("steam_sso_auth_token"); + if (!token) { + return Promise.resolve(); + } + + const apiUrl = app.clientApi.getEndpoint(); + console.warn("Authorizing via token:", token); + + const verify = async () => { + const token = window.localStorage.getItem("steam_sso_auth_token"); + if (!token) { + window.location.replace("?sso_logout"); + return; + } + + const response = await Promise.race([ + fetch(apiUrl + "/v1/sso/refresh", { + method: "POST", + body: token, + headers: { + "x-api-key": "d5c54aaa491f200709afff082c153ef2", + }, + }), + new Promise((resolve, reject) => { + setTimeout(() => reject("timeout exceeded"), 20000); + }), + ]); + const responseText = await response.json(); + if (!responseText.token) { + console.warn("Failed to register"); + window.localStorage.setItem("steam_sso_auth_token", ""); + window.location.replace("?sso_logout"); + return; + } + + window.localStorage.setItem("steam_sso_auth_token", responseText.token); + app.clientApi.token = responseText.token; + WEB_STEAM_SSO_AUTHENTICATED = true; + }; + + await verify(); + setInterval(verify, 120000); +} diff --git a/src/js/core/utils.js b/src/js/core/utils.js index e75789b9..9e2126b1 100644 --- a/src/js/core/utils.js +++ b/src/js/core/utils.js @@ -1,5 +1,6 @@ import { T } from "../translations"; import { rando } from "@nastyox/rando.js"; +import { WEB_STEAM_SSO_AUTHENTICATED } from "./steam_sso"; const bigNumberSuffixTranslationKeys = ["thousands", "millions", "billions", "trillions"]; @@ -764,7 +765,7 @@ export function getLogoSprite() { return "logo_cn.png"; } - if (G_IS_STANDALONE) { + if (G_IS_STANDALONE || WEB_STEAM_SSO_AUTHENTICATED) { return "logo.png"; } @@ -777,6 +778,7 @@ export function getLogoSprite() { /** * Rejects a promise after X ms + * @param {Promise} promise */ export function timeoutPromise(promise, timeout = 30000) { return Promise.race([ diff --git a/src/js/game/modes/levels.js b/src/js/game/modes/levels.js index bd404dc5..1976c8c7 100644 --- a/src/js/game/modes/levels.js +++ b/src/js/game/modes/levels.js @@ -1,3 +1,4 @@ +import { WEB_STEAM_SSO_AUTHENTICATED } from "../../core/steam_sso"; import { enumHubGoalRewards } from "../tutorial_goals"; export const finalGameShape = "RuCw--Cw:----Ru--"; @@ -356,7 +357,7 @@ const STANDALONE_LEVELS = () => [ export function generateLevelsForVariant() { if (G_IS_STEAM_DEMO) { return STEAM_DEMO_LEVELS(); - } else if (G_IS_STANDALONE) { + } else if (G_IS_STANDALONE || WEB_STEAM_SSO_AUTHENTICATED) { return STANDALONE_LEVELS(); } return WEB_DEMO_LEVELS(); diff --git a/src/js/game/modes/regular.js b/src/js/game/modes/regular.js index 7712582f..f0d62211 100644 --- a/src/js/game/modes/regular.js +++ b/src/js/game/modes/regular.js @@ -36,7 +36,8 @@ import { HUDInteractiveTutorial } from "../hud/parts/interactive_tutorial"; import { MetaBlockBuilding } from "../buildings/block"; import { MetaItemProducerBuilding } from "../buildings/item_producer"; import { MOD_SIGNALS } from "../../mods/mod_signals"; -import { finalGameShape, generateLevelsForVariant, LevelSetVariant } from "./levels"; +import { finalGameShape, generateLevelsForVariant } from "./levels"; +import { WEB_STEAM_SSO_AUTHENTICATED } from "../../core/steam_sso"; /** @typedef {{ * shape: string, @@ -377,7 +378,7 @@ export class RegularGameMode extends GameMode { } get difficultyMultiplicator() { - if (G_IS_STANDALONE) { + if (G_IS_STANDALONE || WEB_STEAM_SSO_AUTHENTICATED) { if (G_IS_STEAM_DEMO) { return 0.75; } diff --git a/src/js/platform/api.js b/src/js/platform/api.js index 95398ca8..5dbac1da 100644 --- a/src/js/platform/api.js +++ b/src/js/platform/api.js @@ -3,6 +3,7 @@ import { Application } from "../application"; /* typehints:end */ import { createLogger } from "../core/logging"; import { compressX64 } from "../core/lzstring"; +import { timeoutPromise } from "../core/utils"; import { T } from "../translations"; const logger = createLogger("puzzle-api"); @@ -53,23 +54,23 @@ export class ClientAPI { headers["x-token"] = this.token; } - return Promise.race([ + return timeoutPromise( fetch(this.getEndpoint() + endpoint, { cache: "no-cache", mode: "cors", headers, method: options.method || "GET", body: options.body ? JSON.stringify(options.body) : undefined, + }), + 15000 + ) + .then(res => { + if (res.status !== 200) { + throw "bad-status: " + res.status + " / " + res.statusText; + } + return res; }) - .then(res => { - if (res.status !== 200) { - throw "bad-status: " + res.status + " / " + res.statusText; - } - return res; - }) - .then(res => res.json()), - new Promise((resolve, reject) => setTimeout(() => reject("timeout"), 15000)), - ]) + .then(res => res.json()) .then(data => { if (data && data.error) { logger.warn("Got error from api:", data); @@ -100,22 +101,17 @@ export class ClientAPI { */ apiTryLogin() { if (!G_IS_STANDALONE) { - let token = window.localStorage.getItem("dev_api_auth_token"); - if (!token) { + let token = window.localStorage.getItem("steam_sso_auth_token"); + if (!token && G_IS_DEV) { token = window.prompt( "Please enter the auth token for the puzzle DLC (If you have none, you can't login):" ); - } - if (token) { window.localStorage.setItem("dev_api_auth_token", token); } return Promise.resolve({ token }); } - return Promise.race([ - ipcRenderer.invoke("steam:get-ticket"), - new Promise((resolve, reject) => setTimeout(() => reject("timeout"), 15000)), - ]).then( + return timeoutPromise(ipcRenderer.invoke("steam:get-ticket"), 15000).then( ticket => { logger.log("Got auth ticket:", ticket); return this._request("/v1/public/login", { diff --git a/src/js/platform/browser/game_analytics.js b/src/js/platform/browser/game_analytics.js index 747f25ef..7694ad4d 100644 --- a/src/js/platform/browser/game_analytics.js +++ b/src/js/platform/browser/game_analytics.js @@ -13,6 +13,7 @@ import { FILE_NOT_FOUND } from "../storage"; import OR from "@openreplay/tracker"; import OR_fetch from "@openreplay/tracker-fetch"; +import { WEB_STEAM_SSO_AUTHENTICATED } from "../../core/steam_sso"; let eventConnector; if (!G_IS_STANDALONE && !G_IS_DEV) { @@ -57,6 +58,10 @@ export class ShapezGameAnalytics extends GameAnalyticsInterface { return "steam"; } + if (WEB_STEAM_SSO_AUTHENTICATED) { + return "prod-full"; + } + if (G_IS_RELEASE) { return "prod"; } diff --git a/src/js/platform/browser/wrapper.js b/src/js/platform/browser/wrapper.js index 3610b533..267fce08 100644 --- a/src/js/platform/browser/wrapper.js +++ b/src/js/platform/browser/wrapper.js @@ -1,6 +1,7 @@ import { globalConfig, IS_MOBILE } from "../../core/config"; import { createLogger } from "../../core/logging"; import { queryParamOptions } from "../../core/query_parameters"; +import { WEB_STEAM_SSO_AUTHENTICATED } from "../../core/steam_sso"; import { clamp } from "../../core/utils"; import { GamedistributionAdProvider } from "../ad_providers/gamedistribution"; import { NoAdProvider } from "../ad_providers/no_ad_provider"; @@ -24,7 +25,7 @@ export class PlatformWrapperImplBrowser extends PlatformWrapperInterface { iogLink: true, }; - if (!G_IS_STANDALONE && queryParamOptions.embedProvider) { + if (!G_IS_STANDALONE && !WEB_STEAM_SSO_AUTHENTICATED && queryParamOptions.embedProvider) { const providerId = queryParamOptions.embedProvider; this.embedProvider.iframed = true; this.embedProvider.iogLink = false; diff --git a/src/js/profile/application_settings.js b/src/js/profile/application_settings.js index 281b532c..33dd9e84 100644 --- a/src/js/profile/application_settings.js +++ b/src/js/profile/application_settings.js @@ -511,6 +511,12 @@ export class ApplicationSettings extends ReadWriteProxy { return ExplainedResult.bad("Bad settings object"); } + // MODS + if (!THEMES[data.settings.theme] || !this.app.restrictionMgr.getHasExtendedSettings()) { + console.log("Resetting theme because its no longer available: " + data.settings.theme); + data.settings.theme = "light"; + } + const settings = data.settings; for (let i = 0; i < this.settingHandles.length; ++i) { diff --git a/src/js/profile/setting_types.js b/src/js/profile/setting_types.js index 943e8e53..ccc90d70 100644 --- a/src/js/profile/setting_types.js +++ b/src/js/profile/setting_types.js @@ -3,6 +3,7 @@ import { Application } from "../application"; /* typehints:end */ import { createLogger } from "../core/logging"; +import { WEB_STEAM_SSO_AUTHENTICATED } from "../core/steam_sso"; import { T } from "../translations"; const logger = createLogger("setting_types"); @@ -149,9 +150,16 @@ export class EnumSetting extends BaseSetting { */ getHtml(app) { const available = this.getIsAvailable(app); + return `
- ${available ? "" : `${T.demo.settingNotAvailable}`} + ${ + available + ? "" + : `${ + WEB_STEAM_SSO_AUTHENTICATED ? "" : T.demo.settingNotAvailable + }` + }
@@ -229,7 +237,13 @@ export class BoolSetting extends BaseSetting { const available = this.getIsAvailable(app); return `
- ${available ? "" : `${T.demo.settingNotAvailable}`} + ${ + available + ? "" + : `${ + WEB_STEAM_SSO_AUTHENTICATED ? "" : T.demo.settingNotAvailable + }` + }
@@ -289,7 +303,13 @@ export class RangeSetting extends BaseSetting { const available = this.getIsAvailable(app); return `
- ${available ? "" : `${T.demo.settingNotAvailable}`} + ${ + available + ? "" + : `${ + WEB_STEAM_SSO_AUTHENTICATED ? "" : T.demo.settingNotAvailable + }` + }
diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 7c26ab17..f4ebdfbe 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -4,6 +4,7 @@ import { GameState } from "../core/game_state"; import { DialogWithForm } from "../core/modal_dialog_elements"; import { FormElementInput } from "../core/modal_dialog_forms"; import { ReadWriteProxy } from "../core/read_write_proxy"; +import { WEB_STEAM_SSO_AUTHENTICATED } from "../core/steam_sso"; import { formatSecondsToTimeAgo, generateFileDownload, @@ -39,7 +40,8 @@ export class MainMenuState extends GameState { getInnerHTML() { const showLanguageIcon = !G_CHINA_VERSION && !G_WEGAME_VERSION; const showExitAppButton = G_IS_STANDALONE; - const showPuzzleDLC = !G_WEGAME_VERSION && G_IS_STANDALONE && !G_IS_STEAM_DEMO; + const showPuzzleDLC = + !G_WEGAME_VERSION && (G_IS_STANDALONE || WEB_STEAM_SSO_AUTHENTICATED) && !G_IS_STEAM_DEMO; const showWegameFooter = G_WEGAME_VERSION; const hasMods = MODS.anyModsActive(); @@ -117,6 +119,26 @@ export class MainMenuState extends GameState { ${showExitAppButton ? `` : ""}
+ ${ + G_IS_STANDALONE || WEB_STEAM_SSO_AUTHENTICATED + ? "" + : `
+ ${T.mainMenu.playFullVersion} + Sign in +
` + } + ${ + WEB_STEAM_SSO_AUTHENTICATED + ? ` +
${T.mainMenu.playingFullVersion} + ${T.mainMenu.logout} +
+ ` + : "" + } + diff --git a/src/js/states/mods.js b/src/js/states/mods.js index f4e6396c..07e47bb4 100644 --- a/src/js/states/mods.js +++ b/src/js/states/mods.js @@ -1,12 +1,9 @@ import { openStandaloneLink, THIRDPARTY_URLS } from "../core/config"; -import { queryParamOptions } from "../core/query_parameters"; +import { WEB_STEAM_SSO_AUTHENTICATED } from "../core/steam_sso"; import { TextualGameState } from "../core/textual_game_state"; import { MODS } from "../mods/modloader"; import { T } from "../translations"; -const MODS_SUPPORTED = - !G_IS_STEAM_DEMO && (G_IS_STANDALONE || (G_IS_DEV && !window.location.href.includes("demo"))); - export class ModsState extends TextualGameState { constructor() { super("ModsState"); @@ -16,6 +13,14 @@ export class ModsState extends TextualGameState { return T.mods.title; } + get modsSupported() { + return ( + !WEB_STEAM_SSO_AUTHENTICATED && + !G_IS_STEAM_DEMO && + (G_IS_STANDALONE || (G_IS_DEV && !window.location.href.includes("demo"))) + ); + } + internalGetFullHtml() { let headerHtml = `
@@ -23,12 +28,12 @@ export class ModsState extends TextualGameState {
${ - MODS_SUPPORTED && MODS.mods.length > 0 + this.modsSupported && MODS.mods.length > 0 ? `` : "" } ${ - MODS_SUPPORTED + this.modsSupported ? `` : "" } @@ -45,11 +50,11 @@ export class ModsState extends TextualGameState { } getMainContentHTML() { - if (!MODS_SUPPORTED) { + if (!this.modsSupported) { return `
-

${T.mods.noModSupport}

+

${WEB_STEAM_SSO_AUTHENTICATED ? T.mods.browserNoSupport : T.mods.noModSupport}


Get on Steam! diff --git a/src/js/states/preload.js b/src/js/states/preload.js index 3706555f..e4a0d11e 100644 --- a/src/js/states/preload.js +++ b/src/js/states/preload.js @@ -3,7 +3,8 @@ import { cachebust } from "../core/cachebust"; import { globalConfig } from "../core/config"; import { GameState } from "../core/game_state"; import { createLogger } from "../core/logging"; -import { getLogoSprite } from "../core/utils"; +import { authorizeViaSSOToken } from "../core/steam_sso"; +import { getLogoSprite, timeoutPromise } from "../core/utils"; import { getRandomHint } from "../game/hints"; import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs"; import { PlatformWrapperImplBrowser } from "../platform/browser/wrapper"; @@ -45,12 +46,7 @@ export class PreloadState extends GameState { } async fetchDiscounts() { - await Promise.race([ - new Promise((resolve, reject) => { - setTimeout(() => { - reject("Failed to resolve steam discounts within timeout"); - }, 2000); - }), + await timeoutPromise( fetch("https://analytics.shapez.io/v1/discounts") .then(res => res.json()) .then(data => { @@ -59,7 +55,8 @@ export class PreloadState extends GameState { ); logger.log("Fetched current discount:", globalConfig.currentDiscount); }), - ]).catch(err => { + 2000 + ).catch(err => { logger.warn("Failed to fetch current discount:", err); }); } @@ -72,6 +69,8 @@ export class PreloadState extends GameState { this.setStatus("Booting") .then(() => this.setStatus("Creating platform wrapper", 3)) + .then(() => authorizeViaSSOToken(this.app, this.dialogs)) + .then(() => this.app.platformWrapper.initialize()) .then(() => this.setStatus("Initializing local storage", 6)) diff --git a/translations/base-en.yaml b/translations/base-en.yaml index 27f00080..ae5b763d 100644 --- a/translations/base-en.yaml +++ b/translations/base-en.yaml @@ -131,6 +131,10 @@ mainMenu: helpTranslate: Help translate! madeBy: Made by + playFullVersion: Sign in to play the full version in your Browser! + playingFullVersion: You are now playing the full version! Not all features work yet, but I'm working on it! + logout: Logout + # This is shown when using firefox and other browsers which are not supported. browserWarning: >- Sorry, but the game is known to run slowly on your browser! Get the full version or download Google Chrome for the full experience. @@ -468,6 +472,19 @@ dialogs:

Error Message: + steamSsoError: + title: Full Version Logout + desc: >- + You have been logged out from the Full Browser Version since either your network connection is unstable or you are playing on another device.

+ Please make sure you don't have shapez open in any other browser tab or another computer with the same Steam account.

+ You can login again in the main menu. + + steamSsoNoOwnership: + title: Full Edition not owned + desc: >- + In order to play the Full Edition in your Browser, you need to own both the base game and the Puzzle DLC on your Steam account.

+ Please make sure you own both, signed in with the correct Steam account and then try again. + ingame: # This is shown in the top left corner and displays useful keybindings in # every situation @@ -1155,6 +1172,7 @@ mods: modsInfo: >- To install and manage mods, copy them to the mods folder (use the 'Open Mods Folder' button). Be sure to restart the game afterwards, otherwise the mods will not show up. noModSupport: Get the full version on Steam to install mods! + browserNoSupport: Due to browser restrictions it is currently only possible to install mods in the Steam version - Sorry! togglingComingSoon: title: Coming Soon