Allow playing full version in browser via steam sso

pull/1450/head
tobspr 2 years ago
parent b446a4a915
commit 145f734907

@ -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) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -49,6 +49,45 @@
}
}
.steamSso {
position: fixed;
@include S(top, 0px);
@include S(left, 10px);
background: rgba(88, 92, 102, 0.4);
@include SuperSmallText;
color: #fff;
@include S(max-width, 150px);
@include S(border-radius, $globalBorderRadius);
border-top-left-radius: 0;
border-top-right-radius: 0;
@include S(padding, 5px);
box-shadow: 0 D(5px) D(15px) rgba(#000, 0.1);
display: flex;
color: #000;
flex-direction: column;
a.ssoSignIn {
background: #171a23 uiResource("steam_signin.png") center center / contain no-repeat;
@include S(width, 110px);
@include S(height, 19px);
display: inline-flex;
@include S(border-radius, $globalBorderRadius);
@include S(margin-top, 3px);
overflow: hidden;
text-indent: -999em;
&:hover {
opacity: 0.95;
}
}
@include DarkThemeOverride {
color: #333539;
a {
color: #111;
}
}
}
.fullscreenBackgroundVideo {
// display: none !important;
z-index: -1;

@ -24,6 +24,9 @@
justify-content: center;
height: 100%;
flex-direction: column;
text-align: center;
max-width: 80%;
align-self: center;
.steamLink {
@include S(height, 50px);

@ -181,10 +181,16 @@
pointer-events: all;
display: flex;
align-items: center;
z-index: 100;
justify-content: center;
background: rgba(#fff, 0.5);
text-transform: uppercase;
color: $colorRedBright;
@include S(border-radius, $globalBorderRadius);
@include DarkThemeOverride {
background: rgba(#55585f, 0.95);
}
}
}

@ -3,6 +3,7 @@ import { Application } from "../application";
/* typehints:end */
import { ExplainedResult } from "./explained_result";
import { ReadWriteProxy } from "./read_write_proxy";
import { WEB_STEAM_SSO_AUTHENTICATED } from "./steam_sso";
export class RestrictionManager extends ReadWriteProxy {
/**
@ -64,6 +65,10 @@ export class RestrictionManager extends ReadWriteProxy {
return false;
}
if (WEB_STEAM_SSO_AUTHENTICATED) {
return false;
}
if (G_IS_DEV) {
return typeof window !== "undefined" && window.location.search.indexOf("demo") >= 0;
}

@ -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);
}

@ -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([

@ -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();

@ -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;
}

@ -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", {

@ -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";
}

@ -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;

@ -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) {

@ -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 `
<div class="setting cardbox ${available ? "enabled" : "disabled"}">
${available ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
${
available
? ""
: `<span class="standaloneOnlyHint">${
WEB_STEAM_SSO_AUTHENTICATED ? "" : T.demo.settingNotAvailable
}</span>`
}
<div class="row">
<label>${T.settings.labels[this.id].title}</label>
<div class="value enum" data-setting="${this.id}"></div>
@ -229,7 +237,13 @@ export class BoolSetting extends BaseSetting {
const available = this.getIsAvailable(app);
return `
<div class="setting cardbox ${available ? "enabled" : "disabled"}">
${available ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
${
available
? ""
: `<span class="standaloneOnlyHint">${
WEB_STEAM_SSO_AUTHENTICATED ? "" : T.demo.settingNotAvailable
}</span>`
}
<div class="row">
<label>${T.settings.labels[this.id].title}</label>
@ -289,7 +303,13 @@ export class RangeSetting extends BaseSetting {
const available = this.getIsAvailable(app);
return `
<div class="setting cardbox ${available ? "enabled" : "disabled"}">
${available ? "" : `<span class="standaloneOnlyHint">${T.demo.settingNotAvailable}</span>`}
${
available
? ""
: `<span class="standaloneOnlyHint">${
WEB_STEAM_SSO_AUTHENTICATED ? "" : T.demo.settingNotAvailable
}</span>`
}
<div class="row">
<label>${T.settings.labels[this.id].title}</label>

@ -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 ? `<button class="exitAppButton" aria-label="Exit App"></button>` : ""}
</div>
${
G_IS_STANDALONE || WEB_STEAM_SSO_AUTHENTICATED
? ""
: `<div class="steamSso">
${T.mainMenu.playFullVersion}
<a class="ssoSignIn" href="${
this.app.clientApi.getEndpoint() + "/v1/noauth/steam-sso"
}">Sign in</a>
</div>`
}
${
WEB_STEAM_SSO_AUTHENTICATED
? `
<div class="steamSso">${T.mainMenu.playingFullVersion}
<a href="?sso_logout_silent">${T.mainMenu.logout}</a>
</div>
`
: ""
}
<video autoplay muted loop class="fullscreenBackgroundVideo">
<source src="${cachebust("res/bg_render.webm")}" type="video/webm">
</video>

@ -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 = `
<div class="headerBar">
@ -23,12 +28,12 @@ export class ModsState extends TextualGameState {
<div class="actions">
${
MODS_SUPPORTED && MODS.mods.length > 0
this.modsSupported && MODS.mods.length > 0
? `<button class="styledButton browseMods">${T.mods.browseMods}</button>`
: ""
}
${
MODS_SUPPORTED
this.modsSupported
? `<button class="styledButton openModsFolder">${T.mods.openFolder}</button>`
: ""
}
@ -45,11 +50,11 @@ export class ModsState extends TextualGameState {
}
getMainContentHTML() {
if (!MODS_SUPPORTED) {
if (!this.modsSupported) {
return `
<div class="noModSupport">
<p>${T.mods.noModSupport}</p>
<p>${WEB_STEAM_SSO_AUTHENTICATED ? T.mods.browserNoSupport : T.mods.noModSupport}</p>
<br>
<button class="styledButton browseMods">${T.mods.browseMods}</button>
<a href="#" class="steamLink steam_dlbtn_0" target="_blank">Get on Steam!</a>

@ -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))

@ -131,6 +131,10 @@ mainMenu:
helpTranslate: Help translate!
madeBy: Made by <author-link>
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:
<br><br>
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.<br><br>
Please make sure you don't have shapez open in any other browser tab or another computer with the same Steam account.<br><br>
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.<br><br>
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

Loading…
Cancel
Save