@@ -4,6 +4,7 @@ import DOMPurify from 'dompurify'
44import i18next from 'i18next'
55import { z } from 'zod'
66import { DOMCacheGetOrSet } from './Cache/DOM'
7+ import { dev , device } from './Config'
78import { calculateAmbrosiaGenerationSpeed , calculateOffline , calculateRedAmbrosiaGenerationSpeed } from './Calculate'
89import { updateGlobalsIsEvent } from './Event'
910import { addTimers , automaticTools } from './Helper'
@@ -285,7 +286,19 @@ export async function handleLogin () {
285286 document . getElementById ( 'accountSubTab' ) ?. appendChild ( logoutElement )
286287 }
287288
288- const response = await fetch ( 'https://synergism.cc/api/v1/users/me' , { credentials : 'same-origin' } ) . catch (
289+ // Build headers - include token for mobile auth
290+ const headers : HeadersInit = { }
291+ if ( device === 'mobile' ) {
292+ const token = localStorage . getItem ( 'synergism_token' )
293+ if ( token ) {
294+ headers . Authorization = `Bearer ${ token } `
295+ }
296+ }
297+
298+ const response = await fetch ( 'https://synergism.cc/api/v1/users/me' , {
299+ credentials : device === 'browser' ? 'same-origin' : undefined ,
300+ headers
301+ } ) . catch (
289302 ( ) =>
290303 new Response (
291304 JSON . stringify (
@@ -313,7 +326,7 @@ export async function handleLogin () {
313326
314327 // biome-ignore lint/suspicious/noConfusingLabels: it's not confusing or suspicious
315328 generateSubtab: {
316- if ( location . hostname !== 'synergism.cc' ) {
329+ if ( location . hostname !== 'synergism.cc' && device === 'browser' ) {
317330 subtabElement . innerHTML =
318331 'Login is not available here, go to <a href="https://synergism.cc">https://synergism.cc</a> instead!'
319332 } else if ( hasAccount ( account ) ) {
@@ -485,27 +498,45 @@ export async function handleLogin () {
485498 eventBonusesChevron . style . transform = isCollapsed ? 'rotate(0deg)' : 'rotate(-90deg)'
486499 } )
487500 } else if ( ! hasAccount ( account ) ) {
488- // User is not logged in
489- subtabElement . querySelector ( '#open-register' ) ?. addEventListener ( 'click' , ( ) => {
490- subtabElement . querySelector < HTMLElement > ( '#register' ) ?. style . setProperty ( 'display' , 'flex' )
491- subtabElement . querySelector < HTMLElement > ( '#login' ) ?. style . setProperty ( 'display' , 'none' )
492- subtabElement . querySelector < HTMLElement > ( '#forgotpassword' ) ?. style . setProperty ( 'display' , 'none' )
493- renderCaptcha ( )
494- } )
501+ if ( device !== 'mobile' ) {
502+ // User is not logged in
503+ subtabElement . querySelector ( '#open-register' ) ?. addEventListener ( 'click' , ( ) => {
504+ subtabElement . querySelector < HTMLElement > ( '#register' ) ?. style . setProperty ( 'display' , 'flex' )
505+ subtabElement . querySelector < HTMLElement > ( '#login' ) ?. style . setProperty ( 'display' , 'none' )
506+ subtabElement . querySelector < HTMLElement > ( '#forgotpassword' ) ?. style . setProperty ( 'display' , 'none' )
507+ renderCaptcha ( )
508+ } )
495509
496- subtabElement . querySelector ( '#open-signin' ) ?. addEventListener ( 'click' , ( ) => {
497- subtabElement . querySelector < HTMLElement > ( '#register' ) ?. style . setProperty ( 'display' , 'none' )
498- subtabElement . querySelector < HTMLElement > ( '#login' ) ?. style . setProperty ( 'display' , 'flex' )
499- subtabElement . querySelector < HTMLElement > ( '#forgotpassword' ) ?. style . setProperty ( 'display' , 'none' )
500- renderCaptcha ( )
501- } )
510+ subtabElement . querySelector ( '#open-signin' ) ?. addEventListener ( 'click' , ( ) => {
511+ subtabElement . querySelector < HTMLElement > ( '#register' ) ?. style . setProperty ( 'display' , 'none' )
512+ subtabElement . querySelector < HTMLElement > ( '#login' ) ?. style . setProperty ( 'display' , 'flex' )
513+ subtabElement . querySelector < HTMLElement > ( '#forgotpassword' ) ?. style . setProperty ( 'display' , 'none' )
514+ renderCaptcha ( )
515+ } )
502516
503- subtabElement . querySelector ( '#open-forgotpassword' ) ?. addEventListener ( 'click' , ( ) => {
504- subtabElement . querySelector < HTMLElement > ( '#register' ) ?. style . setProperty ( 'display' , 'none' )
505- subtabElement . querySelector < HTMLElement > ( '#login' ) ?. style . setProperty ( 'display' , 'none' )
506- subtabElement . querySelector < HTMLElement > ( '#forgotpassword' ) ?. style . setProperty ( 'display' , 'flex' )
507- renderCaptcha ( )
508- } )
517+ subtabElement . querySelector ( '#open-forgotpassword' ) ?. addEventListener ( 'click' , ( ) => {
518+ subtabElement . querySelector < HTMLElement > ( '#register' ) ?. style . setProperty ( 'display' , 'none' )
519+ subtabElement . querySelector < HTMLElement > ( '#login' ) ?. style . setProperty ( 'display' , 'none' )
520+ subtabElement . querySelector < HTMLElement > ( '#forgotpassword' ) ?. style . setProperty ( 'display' , 'flex' )
521+ renderCaptcha ( )
522+ } )
523+ } else {
524+ // Mobile: hide browser login forms and show Sign in with Apple
525+ subtabElement . querySelector ( '#button-holder' ) ?. classList . add ( 'none' )
526+ subtabElement . querySelector ( '#register' ) ?. classList . add ( 'none' )
527+ subtabElement . querySelector ( '#login' ) ?. classList . add ( 'none' )
528+ subtabElement . querySelector ( '#forgotpassword' ) ?. classList . add ( 'none' )
529+ // Hide Discord/Patreon login links
530+ subtabElement . querySelectorAll < HTMLElement > ( 'a[href*="login?with="]' ) . forEach ( ( el ) => {
531+ el . classList . add ( 'none' )
532+ } )
533+
534+ const appleButton = subtabElement . querySelector < HTMLButtonElement > ( '#apple-sign-in' )
535+ if ( appleButton ) {
536+ appleButton . style . display = 'inline-block'
537+ appleButton . addEventListener ( 'click' , signInWithApple )
538+ }
539+ }
509540 } else {
510541 assert ( false , `unknown account type ${ account . accountType } ` )
511542 }
@@ -718,6 +749,71 @@ export function renderCaptcha () {
718749 }
719750}
720751
752+ /**
753+ * Sign in with Apple
754+ */
755+ export async function signInWithApple ( ) : Promise < void > {
756+ if ( device !== 'mobile' ) {
757+ return
758+ }
759+
760+ try {
761+ const { SignInWithApple } = await import ( '@capacitor-community/apple-sign-in' )
762+
763+ const result = await SignInWithApple . authorize ( {
764+ clientId : 'com.pseudocorp.synergism' ,
765+ redirectURI : 'https://synergism.cc' ,
766+ scopes : 'email name' ,
767+ nonce : crypto . randomUUID ( )
768+ } )
769+
770+ // Send the authorization to the backend to register/sign in
771+ let response : Response
772+ try {
773+ response = await fetch ( 'https://synergism.cc/login/apple/register' , {
774+ method : 'POST' ,
775+ headers : {
776+ 'Content-Type' : 'application/json'
777+ } ,
778+ body : JSON . stringify ( {
779+ identityToken : result . response . identityToken ,
780+ authorizationCode : result . response . authorizationCode ,
781+ email : result . response . email ,
782+ givenName : result . response . givenName ,
783+ familyName : result . response . familyName
784+ } )
785+ } )
786+ } catch ( e ) {
787+ console . error ( 'Apple Sign-In fetch error:' , e )
788+ Notification ( `Network error: ${ ( e as Error ) . message } ` )
789+ return
790+ }
791+
792+ console . log ( 'got response' , response )
793+
794+ if ( response . ok ) {
795+ const data = await response . json ( ) as { success : boolean ; token : string }
796+ console . log ( 'data' , JSON . stringify ( data ) )
797+ // Store token for authenticated requests
798+ localStorage . setItem ( 'synergism_token' , data . token )
799+ // Reload to trigger handleLogin with the new session
800+ location . reload ( )
801+ } else {
802+ const text = await response . text ( )
803+ console . error ( 'Apple Sign-In error:' , response . status , text )
804+ try {
805+ const data = JSON . parse ( text ) as { error : string }
806+ Notification ( data . error || 'Apple Sign-In failed. Please try again.' )
807+ } catch {
808+ Notification ( `Apple Sign-In failed: ${ text || response . status } ` )
809+ }
810+ }
811+ } catch ( error ) {
812+ console . error ( 'Apple Sign-In error:' , error )
813+ Notification ( 'Apple Sign-In was cancelled or failed.' )
814+ }
815+ }
816+
721817const createFastForward = ( name : PseudoCoinTimeskipNames , minutes : number ) => {
722818 const seconds = minutes * 60
723819 // Only display relevant fast forward stats based on which one was purchased.
0 commit comments