-- Compiled with @isentinel/roblox-ts v4.0.11 local TS = _G[script] local catcher = TS.import(script, TS.getModule(script, "@lisachandra", "core").utils.main).catcher local typeAssertIs = TS.import(script, TS.getModule(script, "@lisachandra", "core").utils.type).typeAssertIs local Boba = TS.import(script, TS.getModule(script, "@rbxts", "boba").src).Boba local Log = TS.import(script, TS.getModule(script, "@rbxts", "log").out).default local HashLib = TS.import(script, TS.getModule(script, "@rbxts", "rbxts-hashlib").out) local TeleportService = TS.import(script, TS.getModule(script, "@rbxts", "services")).TeleportService -- ─── Runtime Configuration ────────────────────────────────────────────────── --* Configuration for teleport behavior including expiration, retry attempts, and flood delay. local defaultConfig = { attempts = 3, expiration = 300, flood_delay = 5, retry_delay = 1, } local _object = table.clone(defaultConfig) setmetatable(_object, nil) local teleportConfig = _object local teleportSecret = "" --* Configures teleport behavior (expiration, retry, flood delay). local function configureTeleport(config) local _object_1 = table.clone(defaultConfig) setmetatable(_object_1, nil) for _k, _v in config do _object_1[_k] = _v end teleportConfig = _object_1 end --* Configures the secret used for teleport hash verification. local function configureTeleportSecret(secret) teleportSecret = secret end local checkTeleportData = Boba.ExhaustiveStruct({ hash = Boba.String, stamp = Boba.Number, }) local teleportAsync = TS.Promise.promisify(function(placeId, players, options) return TeleportService:TeleportAsync(placeId, players, options) end) --* Reasons why a teleport may be considered invalid. local TeleportReason do local _inverse = {} TeleportReason = setmetatable({}, { __index = _inverse, }) TeleportReason.TeleportInvalidData = 0 _inverse[0] = "TeleportInvalidData" TeleportReason.TeleportInvalidHash = 1 _inverse[1] = "TeleportInvalidHash" TeleportReason.TeleportOldHash = 2 _inverse[2] = "TeleportOldHash" end --* Defines the structure for teleport data that can be serialized and sent with a teleport. local function generateTeleportHash(_param, secret) local stamp = _param.stamp if secret == nil then secret = teleportSecret end return HashLib.sha256(`{stamp}{secret}`) end --[[ * * Serializes teleport data into a TeleportOptions object, adding a hash and timestamp for security. * * @param data - The teleport data to serialize. It should not include `hash` or `stamp`. * @returns A TeleportOptions object with serialized data, hash, and timestamp. ]] local function serializeTeleportData(data) local options = Instance.new("TeleportOptions") local stamp = os.time(os.date("!*t")) local _object_1 = table.clone(data) setmetatable(_object_1, nil) _object_1.stamp = stamp local hash = generateTeleportHash(_object_1) local _object_2 = table.clone(data) setmetatable(_object_2, nil) _object_2.hash = hash _object_2.stamp = stamp local teleportData = _object_2 options:SetTeleportData(teleportData) return options end -- Logs invalid teleports. local function logInvalidTeleport(_param, userId, _teleportData) local format = _param.format local success = _param.success local unexpired = _param.unexpired local validData = _param.validData local validHash = _param.validHash if success or validHash == true or unexpired == true then return nil end Log.Warn("Invalid teleport detected for user {UserId}: validData={ValidData}, validHash={ValidHash}, unexpired={Unexpired} {Format}", userId, validData, validHash, unexpired, format) end --[[ * * Validates the teleport data received by a player upon joining a game instance. * * @param player - The player whose teleport data needs to be validated. * @param logInvalid - Whether it should log invalid teleports. * @returns An object indicating the success of the validation and the validity of the hash and * expiration status. ]] local function isValidTeleport(player, logInvalid) if logInvalid == nil then logInvalid = true end local joinData = player:GetJoinData() local teleportData = joinData.TeleportData local validData = checkTeleportData:match(teleportData) if not validData.ok then local returned = { format = validData:format(), success = false, validData = false, } if logInvalid then logInvalidTeleport(returned, player.UserId, teleportData) end return returned end typeAssertIs(teleportData) local stamp = os.time(os.date("!*t")) local hash = generateTeleportHash(teleportData) local validHash = teleportData.hash == hash local unexpired = stamp < teleportData.stamp + teleportConfig.expiration local success = validHash and unexpired local returned = { success = success, unexpired = unexpired, validData = true, validHash = validHash, } if not success then logInvalidTeleport(returned, player.UserId, teleportData) end return returned end --[[ * * Teleports a group of players to a specified place, retrying upon failure. * * @param placeId - The ID of the target place. * @param players - An array of players to teleport. * @param options - Optional TeleportOptions to include with the teleport request. * @returns A promise resolving to a tuple indicating success and the result of the teleport * attempt. ]] local teleport = TS.async(function(placeId, players, options) local attemptIndex = 0 local success = false local result while not success and attemptIndex < teleportConfig.attempts do success, result = teleportAsync(placeId, players, options):await() attemptIndex += 1 if not success then task.wait(teleportConfig.retry_delay) end end if not success then Log.Warn("Teleport unsuccessful: {$Result}", result) return { false, result } end return { true, result } end) TeleportService.TeleportInitFailed:Connect(function(player, teleportResult, errorMessage, targetPlaceId, teleportOptions) if teleportResult == Enum.TeleportResult.Flooded then task.wait(teleportConfig.flood_delay) elseif teleportResult == Enum.TeleportResult.Failure then task.wait(teleportConfig.retry_delay) else Log.Fatal(`Invalid teleport [{teleportResult.Name}]: {errorMessage}`) end teleport(targetPlaceId, { player }, teleportOptions):catch(catcher()) end) return { configureTeleport = configureTeleport, configureTeleportSecret = configureTeleportSecret, serializeTeleportData = serializeTeleportData, isValidTeleport = isValidTeleport, teleport = teleport, TeleportReason = TeleportReason, }