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

Allow playing full version in browser via steam sso

This commit is contained in:
tobspr
2022-06-20 18:22:23 +02:00
parent b446a4a915
commit 145f734907
19 changed files with 267 additions and 43 deletions

View File

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

View File

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

View File

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

View File

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

81
src/js/core/steam_sso.js Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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