Add an action to join a lobby in one-click (#7352)

This commit is contained in:
AlexandreS
2025-02-06 18:04:41 +01:00
committed by GitHub
parent 76c09908c8
commit b9a7786ab0
8 changed files with 503 additions and 27 deletions

View File

@@ -31,6 +31,128 @@ module.exports = {
.addInstructionOrExpressionGroupMetadata(_('Multiplayer'))
.setIcon('JsPlatform/Extensions/multiplayer.svg');
extension
.addAction(
'QuickJoinLobby',
_('Join the next available lobby'),
_(
'Join the next available lobby. The player will join the game instantly if this is possible.'
),
_('Join the next available lobby'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
'JsPlatform/Extensions/multiplayer.svg'
)
.addCodeOnlyParameter('currentScene', '')
.addParameter(
'yesorno',
_('Display loader while searching for a lobby.'),
'',
true
)
.setDefaultValue('yes')
.addParameter(
'yesorno',
_('Display game lobbies if no lobby can be joined directly.'),
'',
true
)
.setDefaultValue('yes')
.setHelpPath('/all-features/multiplayer')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.authenticateAndQuickJoinLobby');
extension
.addCondition(
'IsSearchingForLobbyToJoin',
_('Is searching for a lobby to join'),
_('Is searching for a lobby to join.'),
_('Is searching for a lobby to join'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
'JsPlatform/Extensions/multiplayer.svg'
)
.addCodeOnlyParameter('currentScene', '')
.setHelpPath('/all-features/multiplayer')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.isSearchingForLobbyToJoin');
extension
.addCondition(
'QuickJoinJustFailed',
_('Quick join failed to join a lobby'),
_('Quick join failed to join a lobby.'),
_('Quick join failed to join a lobby'),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg',
'JsPlatform/Extensions/multiplayer.svg'
)
.addCodeOnlyParameter('currentScene', '')
.setHelpPath('/all-features/multiplayer')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.hasQuickJoinJustFailed');
extension
.addStrExpression(
'QuickJoinFailureReason',
_('Quick join action failure reason'),
_(
"Returns the reason why the Quick join action failed. It can either be 'FULL' if all lobbies were occupied, 'NOT_ENOUGH_PLAYERS' if the lobby's configuration requires more than 1 player to start the game and no other players were available. It can also take the value 'UNKNOWN'."
),
_('Lobbies'),
'JsPlatform/Extensions/multiplayer.svg'
)
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
.addIncludeFile('Extensions/Multiplayer/peerJsHelper.js')
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationcomponents.js'
)
.addIncludeFile(
'Extensions/PlayerAuthentication/playerauthenticationtools.js'
)
.addIncludeFile('Extensions/Multiplayer/multiplayercomponents.js')
.addIncludeFile('Extensions/Multiplayer/messageManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayerVariablesManager.js')
.addIncludeFile('Extensions/Multiplayer/multiplayertools.js')
.setFunctionName('gdjs.multiplayer.getQuickJoinFailureReason');
extension
.addAction(
'OpenGameLobbies',

View File

@@ -1945,12 +1945,12 @@ namespace gdjs {
};
const heartbeatMessageNamePrefix = '#heartbeat';
const heartbeastMessageRegex = /#heartbeat#(.+)/;
const heartbeatMessageRegex = /#heartbeat#(.+)/;
const createHeartbeatMessage = (): {
messageName: string;
messageData: any;
} => {
// If we create the heartbeat meassage, we are the host,
// If we create the heartbeat message, we are the host.
// Ensure our player number is correctly set when the first heartbeat is sent.
_playersInfo[gdjs.multiplayer.getCurrentPlayerNumber()] = {
ping: 0, // we are the host, so we don't need to compute the ping.
@@ -2028,7 +2028,7 @@ namespace gdjs {
messages.forEach((message) => {
const messageData = message.getData();
const messageSender = message.getSender();
const matches = heartbeastMessageRegex.exec(messageName);
const matches = heartbeatMessageRegex.exec(messageName);
if (!matches) {
return;
}

View File

@@ -1,6 +1,7 @@
namespace gdjs {
const logger = new gdjs.Logger('Multiplayer');
export namespace multiplayerComponents {
const loaderContainerId = 'loader-container';
const lobbiesRootContainerId = 'lobbies-root-container';
const lobbiesFrameContainerId = 'lobbies-frame-container';
const lobbiesCloseContainerId = 'lobbies-close-container';
@@ -117,6 +118,66 @@ namespace gdjs {
return lobbiesIframe;
};
export const displayLoader = (
runtimeScene: gdjs.RuntimeScene,
yesOrNo: boolean
) => {
const domElementContainer = getDomElementContainer(runtimeScene);
if (!domElementContainer) {
return;
}
if (yesOrNo) {
const loaderContainer: HTMLDivElement = document.createElement('div');
loaderContainer.id = loaderContainerId;
loaderContainer.style.backgroundColor = '#000000';
loaderContainer.style.display = 'flex';
loaderContainer.style.height = '100%';
loaderContainer.style.width = '100%';
loaderContainer.style.justifyContent = 'center';
loaderContainer.style.alignItems = 'center';
loaderContainer.style.position = 'relative';
loaderContainer.style.zIndex = '2';
const loader = document.createElement('img');
loader.setAttribute('width', '50px');
loader.setAttribute(
'src',
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCI+CjxjaXJjbGUgb3BhY2l0eT0nMC4yNScgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iNCI+PC9jaXJjbGU+CjxwYXRoIG9wYWNpdHk9JzAuNzUnIGZpbGw9IiNGRkZGRkYiIGQ9Ik00IDEyYTggOCAwIDAxOC04VjBDNS4zNzMgMCAwIDUuMzczIDAgMTJoNHptMiA1LjI5MUE3Ljk2MiA3Ljk2MiAwIDAxNCAxMkgwYzAgMy4wNDIgMS4xMzUgNS44MjQgMyA3LjkzOGwzLTIuNjQ3eiI+PC9wYXRoPgo8L3N2Zz4='
);
try {
loader.animate(
[{ transform: 'rotate(0deg)' }, { transform: 'rotate(359deg)' }],
{
duration: 3000,
iterations: Infinity,
}
);
} catch {
logger.warn('Animation not supported, loader will be fixed.');
}
loaderContainer.appendChild(loader);
if (
domElementContainer.children &&
domElementContainer.children.length > 0
) {
domElementContainer.insertBefore(
loaderContainer,
domElementContainer.children[0]
);
} else {
domElementContainer.appendChild(loaderContainer);
}
} else {
const loaderContainer = domElementContainer.querySelector(
`#${loaderContainerId}`
);
if (!loaderContainer) return;
try {
domElementContainer.removeChild(loaderContainer);
} catch {}
}
};
/**
* Creates a DOM element that will contain the loader or a message if the game is not registered,
* and adds it to the dom container.

View File

@@ -17,6 +17,17 @@ namespace gdjs {
}[];
};
type Lobby = {
id: string;
status: 'waiting' | 'starting' | 'playing' | 'migrating' | 'migrated';
};
type QuickJoinLobbyResponse =
| { status: 'join-game'; lobby: Lobby }
| { status: 'join-lobby'; lobby: Lobby }
| { status: 'not-enough-players' }
| { status: 'full' };
const getTimeNow =
window.performance && typeof window.performance.now === 'function'
? window.performance.now.bind(window.performance)
@@ -90,6 +101,12 @@ namespace gdjs {
let _hasLobbyGameJustStarted = false;
export let _isLobbyGameRunning = false;
let _hasLobbyGameJustEnded = false;
let _quickJoinLobbyJustFailed = false;
let _quickJoinLobbyFailureReason:
| 'FULL'
| 'NOT_ENOUGH_PLAYERS'
| 'UNKNOWN'
| null = null;
let _lobbyId: string | null = null;
let _connectionId: string | null = null;
@@ -98,6 +115,13 @@ namespace gdjs {
let _lobbyChangeHostRequestInitiatedAt: number | null = null;
let _isChangingHost = false;
let _lobbyNewHostPickedAt: number | null = null;
let _actionAfterJoiningLobby:
| 'OPEN_LOBBY_PAGE'
| 'JOIN_GAME'
| 'START_GAME'
| null = null;
let _isQuickJoiningOrStartingAGame = false;
let _lastQuickJoinRequestDoneAt: number | null = null;
// Communication methods.
let _lobbiesMessageCallback: ((event: MessageEvent) => void) | null = null;
@@ -206,6 +230,7 @@ namespace gdjs {
_hasLobbyGameJustStarted = false;
_hasLobbyGameJustEnded = false;
_quickJoinLobbyJustFailed = false;
});
const getLobbiesWindowUrl = ({
@@ -253,6 +278,11 @@ namespace gdjs {
if (playerToken) {
url.searchParams.set('playerToken', playerToken);
}
const platformInfo = runtimeGame.getPlatformInfo();
url.searchParams.set(
'scm',
platformInfo.supportedCompressionMethods.join(',')
);
// Increment this value when a new feature is introduced so we can
// adapt the interface of the lobbies.
url.searchParams.set('multiplayerVersion', '2');
@@ -592,8 +622,16 @@ namespace gdjs {
logger.error('Malformed message received');
return;
}
handlePeerIdEvent({ peerId, compressionMethod });
const retryData = { times: 2, delayInMs: 500 };
try {
gdjs.evtTools.network.retryIfFailed(retryData, async () => {
handlePeerIdEvent({ peerId, compressionMethod });
});
} catch (error) {
logger.error(
`Handling peerId message from websocket failed (after {${retryData.times}} times with a delay of ${retryData.delayInMs}ms). Not trying anymore.`
);
}
break;
}
}
@@ -634,6 +672,17 @@ namespace gdjs {
};
};
const onPeerUnavailable = (runtimeScene: gdjs.RuntimeScene) => {
gdjs.multiplayerComponents.displayConnectionErrorNotification(
runtimeScene
);
handleLeaveLobbyEvent();
_actionAfterJoiningLobby = null;
_quickJoinLobbyFailureReason = null;
if (_isQuickJoiningOrStartingAGame)
onLobbyQuickJoinFinished(runtimeScene);
};
const handleConnectionIdReceived = function ({
runtimeScene,
connectionId,
@@ -679,10 +728,13 @@ namespace gdjs {
brokerServerConfig.port,
brokerServerConfig.path,
brokerServerConfig.key,
brokerServerConfig.secure
brokerServerConfig.secure,
{ onPeerUnavailable: () => onPeerUnavailable(runtimeScene) }
);
} else {
gdjs.multiplayerPeerJsHelper.useDefaultBrokerServer();
gdjs.multiplayerPeerJsHelper.useDefaultBrokerServer({
onPeerUnavailable: () => onPeerUnavailable(runtimeScene),
});
}
_connectionId = connectionId;
@@ -690,6 +742,28 @@ namespace gdjs {
// We save the lobbyId here as this is the moment when the player is really connected to the lobby.
_lobbyId = lobbyId;
if (_actionAfterJoiningLobby === 'OPEN_LOBBY_PAGE') {
openLobbiesWindow(runtimeScene);
onLobbyQuickJoinFinished(runtimeScene);
return;
} else if (_actionAfterJoiningLobby === 'JOIN_GAME') {
handleJoinGameMessage();
return;
} else if (_actionAfterJoiningLobby === 'START_GAME') {
const retryData = { times: 2, delayInMs: 500 };
try {
gdjs.evtTools.network.retryIfFailed(retryData, async () => {
sendPeerId();
handleStartGameMessage();
});
} catch (error) {
logger.error(
`Sending of peerId message from websocket failed (after {${retryData.times}} times with a delay of ${retryData.delayInMs}ms). Not trying anymore.`
);
}
return;
}
// Then we inform the lobbies window that the player has joined.
const lobbiesIframe = gdjs.multiplayerComponents.getLobbiesIframe(
runtimeScene
@@ -818,6 +892,8 @@ namespace gdjs {
}),
dev: isUsingGDevelopDevelopmentEnvironment,
});
// TODO: if 404, there's chance that it means the lobby is now closed. Display a message
// to the player?
} catch (error) {
logger.error('Error while sending heartbeat, retrying:', error);
try {
@@ -873,6 +949,8 @@ namespace gdjs {
logger.info('Lobby game has started.');
// In case we're joining an existing lobby, read the saved messages to catch-up with the game state.
gdjs.multiplayerMessageManager.handleSavedUpdateMessages(runtimeScene);
if (_isQuickJoiningOrStartingAGame)
onLobbyQuickJoinFinished(runtimeScene);
_isReadyToSendOrReceiveGameUpdateMessages = true;
_hasLobbyGameJustStarted = true;
_isLobbyGameRunning = true;
@@ -926,7 +1004,7 @@ namespace gdjs {
logger.error(
'No peerId found, the player does not seem connected to the broker server.'
);
return;
throw new Error('Missing player peerId.');
}
if (currentPeerId === peerId) {
@@ -990,10 +1068,15 @@ namespace gdjs {
const handleJoinGameMessage = function () {
if (!_websocket) {
logger.error(
'No connection to send the start countdown message. Are you connected to a lobby?'
'No connection to send the join game message. Are you connected to a lobby?'
);
return;
}
// TODO: When the message is sent, it is expected to then receive a "peerId" message
// from the websocket. This "peerId" message might not be sent for different reasons.
// Should there be a security that checks if the "peerId" message has been received
// in the next 10s or something more global that checks the lobby status after the player
// has committed to open a connection with it?
_websocket.send(
JSON.stringify({
@@ -1388,7 +1471,7 @@ namespace gdjs {
logger.error(
"No peerId found, the player doesn't seem connected to the broker server."
);
return;
throw new Error('Missing player peerId.');
}
_websocket.send(
@@ -1433,7 +1516,7 @@ namespace gdjs {
if (!event.data.lobbyId) {
throw new Error('Malformed message.');
}
_actionAfterJoiningLobby = null;
handleJoinLobbyEvent(runtimeScene, event.data.lobbyId);
break;
}
@@ -1519,6 +1602,162 @@ namespace gdjs {
);
};
const onLobbyQuickJoinFinished = (runtimeScene: gdjs.RuntimeScene) => {
_isQuickJoiningOrStartingAGame = false;
_actionAfterJoiningLobby = null;
gdjs.multiplayerComponents.displayLoader(runtimeScene, false);
};
const quickJoinLobby = async (
runtimeScene: gdjs.RuntimeScene,
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
if (_isQuickJoiningOrStartingAGame) return;
const _gameId = gdjs.projectData.properties.projectUuid;
if (!_gameId) {
handleLobbiesError(
runtimeScene,
'The game ID is missing, the quick join lobby action cannot continue.'
);
return;
}
_quickJoinLobbyFailureReason = null;
_isQuickJoiningOrStartingAGame = true;
if (displayLoader) {
gdjs.multiplayerComponents.displayLoader(runtimeScene, true);
}
const quickJoinLobbyRelativeUrl = `/play/game/${_gameId}/public-lobby/action/quick-join`;
const platformInfo = runtimeScene.getGame().getPlatformInfo();
try {
const quickJoinLobbyResponse: QuickJoinLobbyResponse = await gdjs.evtTools.network.retryIfFailed(
{ times: 2 },
() =>
fetchAsPlayer({
relativeUrl: quickJoinLobbyRelativeUrl,
method: 'POST',
dev: isUsingGDevelopDevelopmentEnvironment,
body: JSON.stringify({
isPreview: runtimeScene.getGame().isPreview(),
gameVersion: runtimeScene.getGame().getGameData().properties
.version,
supportedCompressionMethods:
platformInfo.supportedCompressionMethods,
}),
})
);
if (
quickJoinLobbyResponse.status === 'full' ||
quickJoinLobbyResponse.status === 'not-enough-players'
) {
_quickJoinLobbyJustFailed = true;
_quickJoinLobbyFailureReason =
quickJoinLobbyResponse.status === 'full'
? 'FULL'
: 'NOT_ENOUGH_PLAYERS';
onLobbyQuickJoinFinished(runtimeScene);
if (openLobbiesPageIfFailure) {
openLobbiesWindow(runtimeScene);
}
return;
}
if (quickJoinLobbyResponse.status === 'join-game') {
if (quickJoinLobbyResponse.lobby.status === 'waiting') {
_actionAfterJoiningLobby = 'START_GAME';
} else if (quickJoinLobbyResponse.lobby.status === 'playing') {
_actionAfterJoiningLobby = 'JOIN_GAME';
} else {
throw new Error(
`Lobby in wrong status: ${quickJoinLobbyResponse.status}`
);
}
} else {
if (_connectionId) {
// Already connected to a lobby.
onLobbyQuickJoinFinished(runtimeScene);
openLobbiesWindow(runtimeScene);
return;
} else {
_actionAfterJoiningLobby = 'OPEN_LOBBY_PAGE';
}
}
handleJoinLobbyEvent(runtimeScene, quickJoinLobbyResponse.lobby.id);
} catch (error) {
logger.error('An error occurred while joining a lobby:', error);
_quickJoinLobbyJustFailed = true;
_quickJoinLobbyFailureReason = 'UNKNOWN';
onLobbyQuickJoinFinished(runtimeScene);
if (openLobbiesPageIfFailure) {
openLobbiesWindow(runtimeScene);
}
}
};
export const authenticateAndQuickJoinLobby = async (
runtimeScene: gdjs.RuntimeScene,
displayLoader: boolean,
openLobbiesPageIfFailure: boolean
) => {
const requestDoneAt = Date.now();
if (_lastQuickJoinRequestDoneAt) {
if (requestDoneAt - _lastQuickJoinRequestDoneAt < 500) {
_lastQuickJoinRequestDoneAt = requestDoneAt;
logger.warn(
'Last request to quick join a lobby was sent too little time ago. Ignoring this one.'
);
return;
}
} else {
_lastQuickJoinRequestDoneAt = requestDoneAt;
}
const playerId = gdjs.playerAuthentication.getUserId();
const playerToken = gdjs.playerAuthentication.getUserToken();
if (!playerId || !playerToken) {
_isWaitingForLogin = true;
const {
status,
} = await gdjs.playerAuthentication.openAuthenticationWindow(
runtimeScene
).promise;
_isWaitingForLogin = false;
if (status === 'logged') {
await quickJoinLobby(
runtimeScene,
displayLoader,
openLobbiesPageIfFailure
);
}
return;
}
await quickJoinLobby(
runtimeScene,
displayLoader,
openLobbiesPageIfFailure
);
};
export const isSearchingForLobbyToJoin = (
runtimeScene: gdjs.RuntimeScene
) => {
return _isQuickJoiningOrStartingAGame;
};
export const hasQuickJoinJustFailed = (runtimeScene: gdjs.RuntimeScene) => {
return _quickJoinLobbyJustFailed;
};
export const getQuickJoinFailureReason = () => {
return _quickJoinLobbyFailureReason;
};
/**
* Action to display the lobbies window to the user.
*/

View File

@@ -11,6 +11,10 @@ namespace gdjs {
data: Uint8Array | string;
};
type PeerJSInitOptions = {
onPeerUnavailable?: () => void;
};
export type CompressionMethod = 'none' | 'cs:gzip' | 'cs:deflate';
/**
@@ -100,6 +104,8 @@ namespace gdjs {
*/
let ready = false;
let _peerIdToConnectToOnceReady: string | null = null;
/**
* List of IDs of peers that just disconnected.
*/
@@ -228,6 +234,14 @@ namespace gdjs {
return newMessagesList;
};
const _onReady = () => {
ready = true;
if (_peerIdToConnectToOnceReady) {
connect(_peerIdToConnectToOnceReady);
_peerIdToConnectToOnceReady = null;
}
};
/**
* Internal function called when a connection with a remote peer is initiated.
* @param connection The DataConnection of the peer
@@ -289,14 +303,28 @@ namespace gdjs {
* Internal function called to initialize PeerJS after it
* has been configured.
*/
const loadPeerJS = () => {
const initializePeerJS = (initOptions: PeerJSInitOptions = {}) => {
if (peer !== null) return;
peer = new Peer(peerConfig);
peer.on('open', () => {
ready = true;
_onReady();
});
peer.on('error', (errorMessage) => {
logger.error('PeerJS error:', errorMessage);
peer.on('error', (error) => {
// TODO: Support other error types listed in https://peerjs.com/docs/#peeron-error
if (
initOptions.onPeerUnavailable &&
// @ts-ignore - PeerJS adds `type` on errors, but it doesn't show in their TS types.
error.type === 'peer-unavailable'
) {
logger.error('Peer is unavailable.');
initOptions.onPeerUnavailable();
} else {
logger.error(
// @ts-ignore - PeerJS adds `type` on errors, but it doesn't show in their TS types.
`PeerJS error (${error.type || 'unknown'}):`,
error
);
}
});
peer.on('connection', (connection) => {
connection.on('open', () => {
@@ -306,17 +334,21 @@ namespace gdjs {
});
peer.on('close', () => {
peer = null;
loadPeerJS();
initializePeerJS(initOptions);
});
peer.on('disconnected', peer.reconnect);
};
export const useDefaultBrokerServer = initializePeerJS;
/**
* Connects to another p2p client.
* @param id - The other client's ID.
*/
export const connect = (id: string) => {
if (peer === null) return;
if (peer === null || !ready) {
_peerIdToConnectToOnceReady = id;
return;
}
const connection = peer.connect(id);
connection.on('open', () => {
_onConnect(connection);
@@ -365,13 +397,15 @@ namespace gdjs {
* @param path The path (part of the url after the host) to the broker server.
* @param key Optional password to connect to the broker server.
* @param ssl Use ssl?
* @param peerJSInitOptions @see PeerJSInitOptions
*/
export const useCustomBrokerServer = (
host: string,
port: number,
path: string,
key: string,
ssl: boolean
ssl: boolean,
peerJSInitOptions: PeerJSInitOptions = {}
) => {
Object.assign(peerConfig, {
host,
@@ -381,11 +415,9 @@ namespace gdjs {
// All servers have "peerjs" as default key
key: key.length === 0 ? 'peerjs' : key,
});
loadPeerJS();
initializePeerJS(peerJSInitOptions);
};
export const useDefaultBrokerServer = loadPeerJS;
/**
* Adds an ICE server candidate, and removes the default ones provided by PeerJs. Must be called before connecting to a broker.
* @param urls The URL of the STUN/TURN server.
@@ -422,8 +454,8 @@ namespace gdjs {
* @see Peer.id
*/
export const getCurrentId = (): string => {
if (peer == undefined) return '';
return peer.id || '';
if (peer === null) return '';
return peer.id;
};
/**

View File

@@ -82,7 +82,7 @@ namespace gdjs {
}
return;
}
if (this._ws.readyState === 1) this._ws.send(message);
if (this._ws.readyState === WebSocket.OPEN) this._ws.send(message);
}
}

View File

@@ -45,8 +45,7 @@ namespace gdjs {
request.ontimeout = onError;
request.onabort = onError;
request.onreadystatechange = () => {
/* "DONE" */
if (request.readyState === 4) {
if (request.readyState === XMLHttpRequest.DONE) {
if (request.status >= 400) {
onError('' + request.status);
}
@@ -97,6 +96,29 @@ namespace gdjs {
);
};
const delay = (ms: number): Promise<void> =>
new Promise((res) => setTimeout(res, ms));
export const retryIfFailed = async <T>(
{ times, delayInMs }: { times: number; delayInMs?: number },
functionCalled: () => Promise<T>
): Promise<T> => {
let tries = 0;
let latestError = null;
while (tries < times) {
tries++;
latestError = null;
try {
const latestReturnValue = await functionCalled();
return latestReturnValue;
} catch (error) {
if (delayInMs) await delay(delayInMs);
latestError = error;
}
}
throw latestError;
};
/**
* @deprecated
*/

View File

@@ -367,7 +367,7 @@ namespace gdjs {
/**
* Register a function to be called when a scene unload started. This is
* before the object deletion and renderer destruction. It is safe to
* manipulate these. It is **not** be safe to release resources as other
* manipulate these. It is **not** safe to release resources as other
* callbacks might do operations on objects or the scene.
*
* @param callback The function to be called.