Skip to content

Commit 441834c

Browse files
authored
Prepare custom token to speed up embedded game frame login (#7423)
Don't show in changelog
1 parent d4352ba commit 441834c

File tree

1 file changed

+121
-55
lines changed

1 file changed

+121
-55
lines changed

newIDE/app/src/MainFrame/EditorContainers/HomePage/PlaySection/GamesPlatformFrameContext.js

+121-55
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import PublicProfileContext from '../../../../Profile/PublicProfileContext';
99
import RouterContext from '../../../RouterContext';
1010
import { retryIfFailed } from '../../../../Utils/RetryIfFailed';
1111
import optionalRequire from '../../../../Utils/OptionalRequire';
12+
import { isNativeMobileApp } from '../../../../Utils/Platform';
13+
import Window from '../../../../Utils/Window';
1214
const electron = optionalRequire('electron');
1315

1416
// If the iframe is displaying a game, it will continue playing its audio as long as the iframe
@@ -46,8 +48,8 @@ let gdevelopGamesMonetization: {|
4648
let gdevelopGamesMonetizationPromise: Promise<void> | null = null;
4749

4850
const ensureGDevelopGamesMonetizationReady = async () => {
49-
if (!!electron) {
50-
// Not supported on desktop.
51+
if (!!electron || isNativeMobileApp()) {
52+
// Not supported on desktop or mobile.
5153
return;
5254
}
5355
if (gdevelopGamesMonetization) {
@@ -84,6 +86,80 @@ const ensureGDevelopGamesMonetizationReady = async () => {
8486
return gdevelopGamesMonetizationPromise;
8587
};
8688

89+
/**
90+
* Generate a custom token for the user (or `null` if the user is not connected),
91+
* and keep it prepared to be sent to the Games Platform frame.
92+
* This avoids doing it at the last moment, and create useless wait on the frame side.
93+
*/
94+
const useUserCustomToken = (): {|
95+
userCustomToken: ?string,
96+
|} => {
97+
const { profile, getAuthorizationHeader } = React.useContext(
98+
AuthenticatedUserContext
99+
);
100+
const userId = profile ? profile.id : null;
101+
102+
const [customTokenUserId, setCustomTokenUserId] = React.useState(null);
103+
const [userCustomToken, setUserCustomToken] = React.useState(null);
104+
const [lastTokenGenerationTime, setLastTokenGenerationTime] = React.useState(
105+
0
106+
);
107+
108+
// Regenerate a token every 30 minutes (expiration is usually 60 minutes, but be safe)
109+
// or if the user changed.
110+
const hasUserChanged = customTokenUserId !== userId;
111+
const shouldRegenerateToken =
112+
Date.now() - lastTokenGenerationTime > 1000 * 60 * 30 || hasUserChanged;
113+
114+
const clearStoredToken = React.useCallback(() => {
115+
setUserCustomToken(null);
116+
setLastTokenGenerationTime(0);
117+
setCustomTokenUserId(null);
118+
}, []);
119+
120+
React.useEffect(
121+
() => {
122+
if (hasUserChanged) {
123+
clearStoredToken();
124+
console.info('User has changed, cleared stored user custom token.');
125+
}
126+
},
127+
[hasUserChanged, clearStoredToken]
128+
);
129+
130+
React.useEffect(
131+
() => {
132+
(async () => {
133+
if (!shouldRegenerateToken) return;
134+
if (!userId) {
135+
clearStoredToken();
136+
return;
137+
}
138+
139+
try {
140+
console.info(
141+
`Generating a custom token for user ${userId}, for usage in the Games Platform frame...`
142+
);
143+
const userCustomToken = await retryIfFailed({ times: 2 }, () =>
144+
generateCustomAuthToken(getAuthorizationHeader, userId)
145+
);
146+
setUserCustomToken(userCustomToken);
147+
setLastTokenGenerationTime(Date.now());
148+
setCustomTokenUserId(userId);
149+
} catch (error) {
150+
console.error(
151+
'Error while generating custom token. User will not be logged in the Games Platform frame.',
152+
error
153+
);
154+
}
155+
})();
156+
},
157+
[shouldRegenerateToken, userId, getAuthorizationHeader, clearStoredToken]
158+
);
159+
160+
return { userCustomToken };
161+
};
162+
87163
type GamesPlatformFrameStateProviderProps = {|
88164
children: React.Node,
89165
|};
@@ -102,7 +178,6 @@ const GamesPlatformFrameStateProvider = ({
102178
onOpenCreateAccountDialog,
103179
onOpenProfileDialog,
104180
profile,
105-
getAuthorizationHeader,
106181
} = React.useContext(AuthenticatedUserContext);
107182
const [
108183
newProjectActions,
@@ -310,59 +385,44 @@ const GamesPlatformFrameStateProvider = ({
310385
[handleIframeMessage]
311386
);
312387

313-
const sendTokenToIframeIfConnected = React.useCallback(
388+
const { userCustomToken } = useUserCustomToken();
389+
390+
const sendUserCustomTokenToFrame = React.useCallback(
314391
async () => {
315-
if (iframeLoaded && userId) {
316-
// The iframe is loaded and the user is authenticated, so we can
317-
// send that information to the iframe to automatically log the user in.
318-
// $FlowFixMe - we know it's an iframe.
319-
const iframe: ?HTMLIFrameElement = document.getElementById(
320-
GAMES_PLATFORM_IFRAME_ID
321-
);
322-
if (iframe && iframe.contentWindow) {
323-
try {
324-
const userCustomToken = await generateCustomAuthToken(
325-
getAuthorizationHeader,
326-
userId
327-
);
328-
iframe.contentWindow.postMessage(
329-
{
330-
id: 'connectUserWithCustomToken',
331-
token: userCustomToken,
332-
},
333-
// Specify the target origin to avoid leaking the customToken.
334-
// Replace with '*' to test locally.
335-
'https://gd.games'
336-
// '*'
337-
);
338-
} catch (error) {
339-
console.error(
340-
'Error while generating custom token. User will not be logged in in the frame.',
341-
error
342-
);
343-
return;
344-
}
345-
}
392+
if (!iframeLoaded) {
393+
return;
346394
}
347-
},
348-
[iframeLoaded, userId, getAuthorizationHeader]
349-
);
350395

351-
React.useEffect(
352-
() => {
353-
sendTokenToIframeIfConnected();
354-
},
355-
[sendTokenToIframeIfConnected]
356-
);
396+
// The iframe is loaded:
397+
// we can now sent the information that the user is connected,
398+
// to automatically log the user in the frame,
399+
// or notify it the user is not connected (or just disconnected).
357400

358-
const notifyIframeUserDisconnected = React.useCallback(
359-
() => {
360-
if (iframeLoaded && !userId) {
361-
// $FlowFixMe - we know it's an iframe.
362-
const iframe: ?HTMLIFrameElement = document.getElementById(
363-
GAMES_PLATFORM_IFRAME_ID
364-
);
365-
if (iframe && iframe.contentWindow) {
401+
// $FlowFixMe - we know it's an iframe.
402+
const iframe: ?HTMLIFrameElement = document.getElementById(
403+
GAMES_PLATFORM_IFRAME_ID
404+
);
405+
if (!iframe || !iframe.contentWindow) {
406+
console.error('Iframe not found or not accessible.');
407+
return;
408+
}
409+
410+
try {
411+
if (userCustomToken) {
412+
console.info('Sending user custom token to Games Platform frame...');
413+
iframe.contentWindow.postMessage(
414+
{
415+
id: 'connectUserWithCustomToken',
416+
token: userCustomToken,
417+
},
418+
// Specify the target origin to avoid leaking the customToken.
419+
// Use '*' to test locally.
420+
Window.isDev() ? '*' : 'https://gd.games'
421+
);
422+
} else {
423+
console.info(
424+
'Notifying the Games Platform frame that the user is not connected (or just disconnected).'
425+
);
366426
iframe.contentWindow.postMessage(
367427
{
368428
id: 'disconnectUser',
@@ -371,16 +431,22 @@ const GamesPlatformFrameStateProvider = ({
371431
'*'
372432
);
373433
}
434+
} catch (error) {
435+
console.error(
436+
'Error while sending user custom token. User will not be logged in the Games Platform frame.',
437+
error
438+
);
439+
return;
374440
}
375441
},
376-
[iframeLoaded, userId]
442+
[iframeLoaded, userCustomToken]
377443
);
378444

379445
React.useEffect(
380446
() => {
381-
notifyIframeUserDisconnected();
447+
sendUserCustomTokenToFrame();
382448
},
383-
[notifyIframeUserDisconnected]
449+
[sendUserCustomTokenToFrame]
384450
);
385451

386452
const configureNewProjectActions = React.useCallback(

0 commit comments

Comments
 (0)