UNPKG

@casual-simulation/aux-runtime

Version:
1,135 lines 506 kB
import { DEBUG_STRING, debugStringifyFunction } from './AuxGlobalContext'; import { hasValue, trimTag, isBot, BOT_SPACE_TAG, toast as toastMessage, getScriptIssues as scriptIssues, tip as tipMessage, hideTips as hideTipMessages, showJoinCode as calcShowJoinCode, requestFullscreen, exitFullscreen, html as htmlMessage, hideHtml as hideHtmlMessage, setClipboard as calcSetClipboard, tweenTo as calcTweenTo, showChat as calcShowChat, hideChat as calcHideChat, runScript, getMediaPermission as calcGetMediaPermission, getAverageFrameRate as calcGetAverageFrameRate, enableAR as calcEnableAR, disableAR as calcDisableAR, enableVR as calcEnableVR, disableVR as calcDisableVR, arSupported as calcARSupported, vrSupported as calcVRSupported, showUploadAuxFile as calcShowUploadAuxFile, openQRCodeScanner as calcOpenQRCodeScanner, showQRCode as calcShowQRCode, openBarcodeScanner as calcOpenBarcodeScanner, showBarcode as calcShowBarcode, importAUX as calcImportAUX, showInputForTag as calcShowInputForTag, showInput as calcShowInput, showConfirm as calcShowConfirm, replaceDragBot as calcReplaceDragBot, goToDimension as calcGoToDimension, goToURL as calcGoToURL, openURL as calcOpenURL, playSound as calcPlaySound, bufferSound as calcBufferSound, cancelSound as calcCancelSound, shell as calcShell, reject as calcReject, localFormAnimation as calcLocalFormAnimation, webhook as calcWebhook, superShout as calcSuperShout, share as calcShare, registerPrefix as calcRegisterPrefix, localPositionTween as calcLocalPositionTween, localRotationTween as calcLocalRotationTween, showUploadFiles as calcShowUploadFiles, download, loadSimulation, unloadSimulation, getUploadState, addState, getPortalTag, KNOWN_PORTALS, openConsole, tagsOnBot, getOriginalObject, getBotSpace, trimEvent, CREATE_ACTION_NAME, CREATE_ANY_ACTION_NAME, DESTROY_ACTION_NAME, ORIGINAL_OBJECT, getRemoteCount, getRemotes, listInstUpdates as calcListInstUpdates, getInstStateFromUpdates as calcGetInstStateFromUpdates, action, calculateAnchorPoint, calculateAnchorPointOffset, getBotPosition as calcGetBotPosition, getBotRotation as calcGetBotRotation, isRuntimeBot, SET_TAG_MASK_SYMBOL, CLEAR_TAG_MASKS_SYMBOL, getBotScale, EDIT_TAG_SYMBOL, EDIT_TAG_MASK_SYMBOL, circleWipe, addDropSnap as calcAddDropSnap, addDropGrid as calcAddDropGrid, animateToPosition, beginAudioRecording as calcBeginAudioRecording, endAudioRecording as calcEndAudioRecording, beginRecording as calcBeginRecording, endRecording as calcEndRecording, speakText as calcSpeakText, getVoices as calcGetVoices, getGeolocation as calcGetGeolocation, cancelAnimation, disablePOV, enablePOV, enableCustomDragging as calcEnableCustomDragging, MINI_PORTAL, registerCustomApp, setAppOutput, unregisterCustomApp, requestAuthData as calcRequestAuthData, createBot, defineGlobalBot as calcDefineGlobalBot, TEMPORARY_BOT_PARTITION_ID, convertToString, GET_TAG_MASKS_SYMBOL, isBotLink, parseBotLink, createBotLink, convertGeolocationToWhat3Words as calcConvertGeolocationToWhat3Words, meetCommand as calcMeetCommand, meetFunction as calcMeetFunction, openImageClassifier as calcOpenImageClassifier, classifyImages as calcOpenClassifyImages, isBotDate, DATE_TAG_PREFIX, parseBotDate, realNumberOrDefault, raycastFromCamera as calcRaycastFromCamera, raycastInPortal as calcRaycastInPortal, calculateRayFromCamera as calcCalculateRayFromCamera, bufferFormAddressGltf, startFormAnimation as calcStartFormAnimation, stopFormAnimation as calcStopFormAnimation, listFormAnimations as calcListFormAnimations, calculateStringTagValue, createInitializationUpdate as calcCreateInitalizationUpdate, applyUpdatesToInst as calcApplyUpdatesToInst, configureWakeLock, getWakeLockConfiguration as calcGetWakeLockConfiguration, analyticsRecordEvent as calcAnalyticsRecordEvent, KNOWN_TAGS, isStoredVersion2, getCurrentInstUpdate as calcGetCurrentInstUpdate, openPhotoCamera as calcOpenPhotoCamera, getEasing, enableCollaboration as calcEnableCollaboration, showAccountInfo as calcShowAccountInfo, reportInst as calcReportInst, getRecordsEndpoint as calcGetRecordsEndpoint, ldrawCountAddressBuildSteps as calcLdrawCountAddressBuildSteps, ldrawCountTextBuildSteps as calcLdrawCountTextBuildSteps, calculateViewportCoordinatesFromPosition as calcCalculateViewportCoordinatesFromPosition, calculateScreenCoordinatesFromViewportCoordinates as calcCalculateScreenCoordinatesFromViewportCoordinates, calculateViewportCoordinatesFromScreenCoordinates as calcCalculateViewportCoordinatesFromScreenCoordinates, capturePortalScreenshot as calcCapturePortalScreenshot, createStaticHtml as calcCreateStaticHtmlFromBots, recordLoom, watchLoom, getLoomMetadata, loadSharedDocument, installAuxFile as calcInstallAuxFile, calculateStringListTagValue, calculateScreenCoordinatesFromPosition as calcCalculateScreenCoordinatesFromPosition, addMapLayer as calcAddMapLayer, removeMapLayer as calcRemoveMapLayer, GET_DYNAMIC_LISTENERS_SYMBOL, ADD_BOT_LISTENER_SYMBOL, REMOVE_BOT_LISTENER_SYMBOL, } from '@casual-simulation/aux-common/bots'; import { aiChat, aiChatStream, aiGenerateSkybox, aiGenerateImage, grantRecordPermission as calcGrantRecordPermission, revokeRecordPermission as calcRevokeRecordPermission, grantInstAdminPermission as calcGrantInstAdminPermission, grantUserRole as calcGrantUserRole, revokeUserRole as calcRevokeUserRole, grantInstRole as calcGrantInstRole, revokeInstRole as calcRevokeInstRole, listUserStudios as calcListUserStudios, joinRoom as calcJoinRoom, leaveRoom as calcLeaveRoom, setRoomOptions as calcSetRoomOptions, getRoomOptions as calcGetRoomOptions, getRoomTrackOptions as calcGetRoomTrackOptions, setRoomTrackOptions as calcSetRoomTrackOptions, getRoomRemoteOptions as calcGetRoomRemoteOptions, listDataRecord, recordEvent as calcRecordEvent, getEventCount as calcGetEventCount, getFile as calcGetFile, eraseFile as calcEraseFile, getPublicRecordKey as calcGetPublicRecordKey, recordData as calcRecordData, getRecordData, eraseRecordData, recordFile as calcRecordFile, listDataRecordByMarker, aiHumeGetAccessToken, aiSloydGenerateModel, recordWebhook as calcRecordWebhook, getWebhook as calcGetWebhook, listWebhooks as calcListWebhooks, listWebhooksByMarker as calcListWebhooksByMarker, eraseWebhook as calcEraseWebhook, runWebhook as calcRunWebhook, recordNotification as calcRecordNotification, getNotification as calcGetNotification, listNotifications as calcListNotifications, listNotificationsByMarker as calcListNotificationsByMarker, eraseNotification as calcEraseNotification, subscribeToNotification as calcSubscribeToNotification, unsubscribeFromNotification as calcUnsubscribeFromNotification, sendNotification as calcSendNotification, listNotificationSubscriptions as calcListNotificationSubscriptions, listUserNotificationSubscriptions as calcListUserNotificationSubscriptions, aiOpenAICreateRealtimeSession, grantEntitlements as calcGrantEntitlements, recordPackageVersion as calcRecordPackageVersion, erasePackageVersion as calcErasePackageVersion, listPackageVersions as calcListPackageVersions, getPackageVersion as calcGetPackageVersion, recordPackageContainer as calcRecordPackageContainer, erasePackageContaienr as calcErasePackageContainer, listPackageContainers as calcListPackageContainers, listPackageContainersByMarker as calcListPackageContainersByMarker, getPackageContainer as calcGetPackageContainer, installPackage as calcInstallPackage, listInstalledPackages as calcListInstalledPackages, recordsCallProcedure, } from './RecordsEvents'; import { sortBy, cloneDeep, union, isEqual } from 'es-toolkit/compat'; import { remote as calcRemote, DEFAULT_BRANCH_NAME, formatVersionNumber, parseVersionNumber, PRIVATE_MARKER, } from '@casual-simulation/aux-common'; import { RanOutOfEnergyError } from './AuxResults'; import '@casual-simulation/aux-common/polyfill/Array.first.polyfill'; import '@casual-simulation/aux-common/polyfill/Array.last.polyfill'; import { embedBase64InPdf, getEmbeddedBase64FromPdf, toHexString as utilToHexString, fromHexString as utilFromHexString, } from './Utils'; import { convertToCopiableValue } from '@casual-simulation/aux-common/partitions/PartitionUtils'; import { sha256 as hashSha256, sha512 as hashSha512, hmac as calcHmac, sha1 as hashSha1, } from 'hash.js'; import stableStringify from '@casual-simulation/fast-json-stable-stringify'; import { encrypt as realEncrypt, decrypt as realDecrypt, keypair as realKeypair, sign as realSign, verify as realVerify, asymmetricKeypair as realAsymmetricKeypair, asymmetricEncrypt as realAsymmetricEncrypt, asymmetricDecrypt as realAsymmetricDecrypt, isAsymmetricKeypair, isAsymmetricEncrypted, isEncrypted, } from '@casual-simulation/crypto'; import { apply, del, insert, isTagEdit, preserve, } from '@casual-simulation/aux-common/bots'; import { Vector3 as ThreeVector3, Plane, Ray } from '@casual-simulation/three'; import mime from 'mime'; import TWEEN from '@tweenjs/tween.js'; import './PerformanceNowPolyfill'; import '@casual-simulation/aux-common/BlobPolyfill'; import { Vector3, Vector2, Quaternion, Rotation, } from '@casual-simulation/aux-common/math'; import { Fragment, h } from 'preact'; import htm from 'htm'; import { fromByteArray, toByteArray } from 'base64-js'; import expect, { iterableEquality } from '@casual-simulation/expect'; import { parseRecordKey, isRecordKey as calcIsRecordKey, } from '@casual-simulation/aux-common'; import SeedRandom from 'seedrandom'; import { DateTime } from 'luxon'; import * as hooks from 'preact/hooks'; import { render, createRef, createContext } from 'preact'; import * as compat from 'preact/compat'; import { isGenerator, UNCOPIABLE, unwind, } from '@casual-simulation/js-interpreter/InterpreterUtils'; import { INTERPRETABLE_FUNCTION } from './AuxCompiler'; import { constructInitializationUpdate, mergeInstUpdates as calcMergeInstUpdates, } from '@casual-simulation/aux-common/partitions/PartitionUtils'; import { CasualOSError } from './CasualOSError'; import { attachRuntime, detachRuntime } from './RuntimeEvents'; const _html = htm.bind(h); const html = ((...args) => { return _html(...args); }); html.h = h; html.f = Fragment; /** * Creates a new interpretable function based on the given function. * @param interpretableFunc */ export function createInterpretableFunction(interpretableFunc) { const normalFunc = ((...args) => unwind(interpretableFunc(...args))); normalFunc[INTERPRETABLE_FUNCTION] = interpretableFunc; return normalFunc; } /** * Sets the INTERPRETABLE_FUNCTION property on the given object (semantically a function) to the given interpretable version and returns the object. * @param interpretableFunc The version of the function that should be used as the interpretable version of the function. * @param normalFunc The function that should be tagged. */ export function tagAsInterpretableFunction(interpretableFunc, normalFunc) { normalFunc[INTERPRETABLE_FUNCTION] = interpretableFunc; return normalFunc; } /** * The status codes that should be used to retry web requests. */ const DEFUALT_RETRY_STATUS_CODES = [ 408, // Request Timeout 429, // Too Many Requests 500, // Internal Server Error 502, // Bad Gateway 503, // Service Unavailable 504, // Gateway Timeout 0, // Network Failure / CORS ]; /** * The time to wait until another web request retry unless specified by the webhook options. * Defaults to 3 seconds. */ const DEFAULT_RETRY_AFTER_MS = 3 * 1000; /** * The maximum amount of time to wait before giving up on a set of requests. * Defaults to 1 minute. */ const MAX_RETRY_AFTER_MS = 60 * 60 * 1000; /** * The maximum number of times that a web request should be retried for. */ const MAX_RETRY_COUNT = 10; export const GET_RUNTIME = Symbol('get_runtime'); const botsEquality = function (first, second) { if (isRuntimeBot(first) && isRuntimeBot(second)) { expect(getBotSnapshot(first)).toEqual(getBotSnapshot(second)); return true; } return undefined; }; expect.extend({ toEqual(received, expected) { // Copied from https://github.com/facebook/jest/blob/7bb400c373a6f90ba956dd25fe24ee4d4788f41e/packages/expect/src/matchers.ts#L580 // Added the testBots matcher to make testing against bots easier. const matcherName = 'toEqual'; const options = { comment: 'deep equality', isNot: this.isNot, promise: this.promise, }; const pass = this.equals(received, expected, [ botsEquality, iterableEquality, ]); const message = pass ? () => this.utils.matcherHint(matcherName, undefined, undefined, options) + '\n\n' + `Expected: not ${this.utils.printExpected(expected)}\n` + (this.utils.stringify(expected) !== this.utils.stringify(received) ? `Received: ${this.utils.printReceived(received)}` : '') : () => this.utils.matcherHint(matcherName, undefined, undefined, options) + '\n\n' + this.utils.printDiffOrStringify(expected, received, 'Expected', 'Received', this.expand !== false); // Passing the actual and expected objects so that a custom reporter // could access them, for example in order to display a custom visual diff, // or create a different error message return { actual: received, expected, message, name: matcherName, pass }; }, }); function getBotSnapshot(bot) { var _a; let b = { id: bot.id, space: bot.space, tags: typeof bot.tags.toJSON === 'function' ? bot.tags.toJSON() : bot.tags, }; let masks = isRuntimeBot(bot) ? bot[GET_TAG_MASKS_SYMBOL]() : cloneDeep((_a = bot.masks) !== null && _a !== void 0 ? _a : {}); if (Object.keys(masks).length > 0) { b.masks = masks; } return b; } const DEAD_RECKONING_OFFSET = 50; const DEG_TO_RAD = Math.PI / 180; const RAD_TO_DEG = 180 / Math.PI; /** * Creates a library that includes the default functions and APIs. * @param context The global context that should be used. */ export function createDefaultLibrary(context) { // TODO: Remove deprecated functions webhook.post = function (url, data, options) { return webhook({ ...options, method: 'POST', url: url, data: data, }); }; const webhookFunc = makeMockableFunction(webhook, 'webhook'); webhookFunc.post = makeMockableFunction(webhook.post, 'webhook.post'); const shoutImpl = ((name, arg) => unwind(shout(name, arg))); const shoutProxy = new Proxy(shoutImpl, { get(target, name, reciever) { if (typeof name === 'symbol' || (typeof target === 'function' && name in Function.prototype)) { return Reflect.get(target, name, reciever); } return (arg) => { return unwind(shout(name, arg)); }; }, }); const interpretableShoutImpl = shout; const interpretableShoutProxy = new Proxy(interpretableShoutImpl, { get(target, name, reciever) { if (typeof name === 'symbol' || (typeof target === 'function' && name in Function.prototype)) { return Reflect.get(target, name, reciever); } return (arg) => { return shout(name, arg); }; }, }); return { api: { _getBots, __getBots, getBots, _getBot, __getBot, getBot, getBotTagValues, getMod, getBotPosition, getBotRotation, getID, getJSON, getFormattedJSON, getSnapshot, diffSnapshots, applyDiffToSnapshot, getTag, setTag, setTagMask, clearTagMasks, insertTagText, insertTagMaskText, deleteTagText, deleteTagMaskText, removeTags, renameTag, applyMod, subtractMods, _create, _destroy, destroy: createInterpretableFunction(destroy), _changeState, changeState: createInterpretableFunction(changeState), getLink: createBotLinkApi, getBotLinks, _updateBotLinks, updateBotLinks, getDateTime, DateTime, /** * @hidden */ Vector2, /** * @hidden */ Vector3, /** * @hidden */ Quaternion, /** * @hidden */ Rotation, superShout, _priorityShout, priorityShout: createInterpretableFunction(priorityShout), _shout, shout: tagAsInterpretableFunction(interpretableShoutProxy, shoutProxy), _whisper, whisper: createInterpretableFunction(whisper), byTag, byID, byMod, inDimension, atPosition, inStack, neighboring, bySpace, byCreator, either, not, remote, sendRemoteData: remoteWhisper, remoteWhisper, remoteShout, uuid, _animateTag, __animateTag, animateTag, clearAnimations, // TODO: Remove deprecated functions webhook: webhookFunc, /** * @hidden */ sleep, /** * @hidden */ __energyCheck, clearTimeout, clearInterval, clearWatchBot, clearWatchPortal, assert, assertEqual, expect, html, /** * Gets the config bot (formerly known as the player bot). * This is the bot that represents the player's browser tab. * * It is `tempLocal` and is used to configure various portals. * * @example Get the config bot and set a username on it. * configBot.tags.username = "bob"; * * @example Open the sheetPortal to "testDimension". * configBot.tags.sheetPortal = "testDimension"; * * @dochash actions/os/system * @doctitle System Actions * @docsidebar System * @docdescription System actions are used to get information about the current session. * @docname configBot * @docgroup 01-os */ get _configBot() { return null; }, ai: { chat, generateSkybox, generateImage, hume: { getAccessToken: getHumeAccessToken, }, sloyd: { generateModel: generateSloydModel, }, stream: { chat: chatStream, }, openai: { createRealtimeSession: createOpenAIRealtimeSession, }, }, os: { [UNCOPIABLE]: true, addBotListener, removeBotListener, sleep, toast, getScriptIssues, tip, hideTips, showJoinCode, requestFullscreenMode, exitFullscreenMode, hideLoadingScreen, showHtml, hideHtml, setClipboard, tweenTo, moveTo, _focusOn_bot, _focusOn_position, focusOn, _showChat_placeholder, _showChat_options, showChat, hideChat, run, version, device, isCollaborative, enableCollaboration, showAccountInfo, getAB1BootstrapURL, enableAR, disableAR, enableVR, disableVR, arSupported, vrSupported, enablePointOfView, disablePointOfView, requestWakeLock, disableWakeLock, getWakeLockConfiguration, download: downloadData, downloadBots, downloadBotsAsInitialzationUpdate, getAuxFileForBots, installAuxFile, downloadServer, downloadInst: downloadServer, showUploadAuxFile, showUploadFiles, openQRCodeScanner, closeQRCodeScanner, showQRCode, hideQRCode, openBarcodeScanner, closeBarcodeScanner, showBarcode, hideBarcode, openImageClassifier, closeImageClassifier, classifyImages, openPhotoCamera, capturePhoto, closePhotoCamera, capturePortalScreenshot, /** * Gets the device-local time as the number of miliseconds since midnight January 1st, 1970 UTC-0 (i.e. the Unix Epoch). This is what your device's clock thinks the current time is. * * @example Toast the number of miliseconds since the Unix Epoch * os.toast(os.localTime); * * @dochash actions/os/time * @doctitle Time Actions * @docsidebar Time * @docdescription Time actions make working with time across devices easy. * @docgroup 01-time * @docname os.localTime */ get localTime() { return Date.now(); }, /** * Gets the shared time that has been agreed upon between devices in the inst as the number of miliseconds since midnight January 1st, 1970 UTC-0 (i.e. the Unix Epoch). * This is what your device's clock thinks the inst clock says. * * If an agreed upon time cannot be determined (for example, because collaboration is disabled in the inst), then this value will always be `NaN`. * * @example Toast the current shared time * os.toast(os.agreedUponTime); * * @dochash actions/os/time * @docgroup 01-time * @docname os.agreedUponTime */ get agreedUponTime() { return Date.now() + context.instTimeOffset; }, /** * Gets the average latency between this device's clock and the inst clock in miliseconds. Lower values tend to indicate a good connection while higher values tend to indicate a bad connection. * * If an agreed upon time cannot be determined (for example, because collaboration is disabled in the inst), then this value will always be `NaN`. * * @dochash actions/os/time * @docgroup 01-time * @docname os.instLatency */ get instLatency() { return context.instLatency; }, /** * Gets the calculated time offset between the inst clock and the local clock. This value is equivalent to `os.agreedUponTime - os.localTime`. * * If an agreed upon time cannot be determined (for example, because collaboration is disabled in the inst), then this value will always be `NaN`. * * @dochash actions/os/time * @docgroup 01-time * @docname os.instTimeOffset */ get instTimeOffset() { return context.instTimeOffset; }, /** * Gets the spread between calculated time offsets. Higher values indicate that {@link os.agreedUponTime} is less accurate. Lower values indicate that {@link os.agreedUponTime} is more accurate. * * If an agreed upon time cannot be determined (for example, because collaboration is disabled in the inst), then this value will always be `NaN`. * * @dochash actions/os/time * @docgroup 01-time * @docname os.instTimeOffsetSpread */ get instTimeOffsetSpread() { return context.instTimeOffsetSpread; }, /** * Gets the shared time that has been agreed upon between devices but with an additional 50ms offset added. * This offset attempts to ensure that changes/events will be recieved by all connected devices by the time it occurs, thereby making synchronized actions easier to perform. * * If an agreed upon time cannot be determined (for example, because collaboration is disabled in the inst), then this value will always be `NaN`. * * @dochash actions/os/time * @docgroup 01-time * @docname os.deadReckoningTime */ get deadReckoningTime() { return (Date.now() + context.instTimeOffset + DEAD_RECKONING_OFFSET); }, loadServer, unloadServer, loadInst: loadServer, unloadInst: unloadServer, importAUX, parseBotsFromData, replaceDragBot, isInDimension, getCurrentDimension, getCurrentServer, getCurrentInst: getCurrentServer, getCurrentInstRecord, getMenuDimension, getMiniPortalDimension, getPortalDimension, getDimensionalDepth, showInputForTag, _showInput: showInput, showInput: makeMockableFunction(showInput, 'os.showInput'), showConfirm, goToDimension, goToURL, openURL, openDevConsole, playSound, bufferSound, cancelSound, hasBotInMiniPortal, share, closeCircleWipe, openCircleWipe, addDropSnap, addBotDropSnap, addDropGrid, addBotDropGrid, enableCustomDragging, log, getGeolocation, inSheet, getCameraPosition, getCameraRotation, getFocusPoint, getPointerPosition, getPointerRotation, getPointerDirection, getInputState, getInputList, getMediaPermission, getAverageFrameRate, joinRoom, leaveRoom, setRoomOptions, getRoomOptions, getRoomTrackOptions, setRoomTrackOptions, getRoomRemoteOptions, registerTagPrefix: registerPrefix, registerApp: registerApp, unregisterApp, compileApp: setAppContent, appHooks: { ...hooks, render, createRef, createContext }, appCompat: { ...compat }, listBuiltinTags, reportInst, requestAuthBot, requestAuthBotInBackground, getPublicRecordKey, getSubjectlessPublicRecordKey, grantPermission, revokePermission, grantInstAdminPermission, grantUserRole, revokeUserRole, grantInstRole, revokeInstRole, isRecordKey, recordData, recordManualApprovalData, getData, getManualApprovalData, listData, listDataByMarker, eraseData, eraseManualApprovalData, recordWebhook, runWebhook, getWebhook, eraseWebhook, listWebhooks, listWebhooksByMarker, recordNotification, getNotification, eraseNotification, listNotifications, listNotificationsByMarker, subscribeToNotification, unsubscribeFromNotification, sendNotification, listNotificationSubscriptions, listUserNotificationSubscriptions, recordFile, getFile, getPublicFile, getPrivateFile, eraseFile, recordEvent, countEvents, parseVersionKey, formatVersionKey, grantEntitlements, recordPackageVersion, erasePackageVersion, listPackageVersions, getPackageVersion, recordPackageContainer, erasePackageContainer, listPackageContainers, listPackageContainersByMarker, getPackageContainer, installPackage, listInstalledPackages, recordSearchCollection, getSearchCollection, eraseSearchCollection, listSearchCollections, listSearchCollectionsByMarker, recordSearchDocument, eraseSearchDocument, listUserStudios, getRecordsEndpoint, convertGeolocationToWhat3Words, raycastFromCamera, raycast, calculateRayFromCamera, calculateViewportCoordinatesFromPosition, calculateScreenCoordinatesFromViewportCoordinates, calculateScreenCoordinatesFromPosition, calculateViewportCoordinatesFromScreenCoordinates, bufferFormAddressGLTF, startFormAnimation, stopFormAnimation, listFormAnimations, ldrawCountAddressBuildSteps, ldrawCountTextBuildSteps, attachDebugger, detachDebugger, addMapLayer, removeMapLayer, remotes, listInstUpdates, getInstStateFromUpdates, createInitializationUpdate, applyUpdatesToInst, getCurrentInstUpdate, mergeInstUpdates, remoteCount: serverRemoteCount, totalRemoteCount: totalRemoteCount, getSharedDocument, getLocalDocument, getMemoryDocument, beginAudioRecording, endAudioRecording, meetCommand, meetFunction, get vars() { return context.global; }, _createDebugger_normal, _createDebugger_pausable, _getExecutingDebugger, _attachDebugger, _detachDebugger, }, portal: { registerPrefix, }, server: { shell, serverRemoteCount, totalRemoteCount, remotes, players: remotes, serverPlayerCount: serverRemoteCount, totalPlayerCount: totalRemoteCount, }, action: { perform, reject, }, experiment: { localFormAnimation, localPositionTween, localRotationTween, getAnchorPointPosition, createStaticHtmlFromBots, beginAudioRecording, endAudioRecording, beginRecording, endRecording, speakText, getVoices, }, loom: { recordVideo: loomRecordVideo, watchVideo: loomWatchVideo, getVideoEmbedMetadata: loomGetVideoEmbedMetadata, }, math: { sum, avg, sqrt, abs, stdDev, getSeededRandomNumberGenerator, setRandomSeed, randomInt, random, degreesToRadians, radiansToDegrees, getForwardDirection, intersectPlane, getAnchorPointOffset, addVectors, subtractVectors, negateVector, normalizeVector, vectorLength, scaleVector, areClose, }, mod: { cameraPositionOffset, cameraRotationOffset, }, bytes: { toBase64String, fromBase64String, toHexString, fromHexString, toBase64Url, fromBase64Url, }, crypto: { _hash_raw, _hash_string, hash, sha256, sha512, _hmac_string, _hmac_raw, hmac, hmacSha256, hmacSha512, encrypt, decrypt, isEncrypted, asymmetric: { keypair: asymmetricKeypair, isKeypair: isAsymmetricKeypair, encrypt: asymmetricEncrypt, decrypt: asymmetricDecrypt, isEncrypted: isAsymmetricEncrypted, }, keypair, sign, verify, }, perf: { getStats, }, web: { _webGet, _webPost, _webHook, get: makeMockableFunction(webGet, 'web.get'), post: makeMockableFunction(webPost, 'web.post'), hook: makeMockableFunction(webhook, 'web.hook'), }, analytics: { recordEvent: analyticsRecordEvent, }, }, tagSpecificApi: { create: tagAsInterpretableFunction((options) => (...args) => { var _a; return create((_a = options.bot) === null || _a === void 0 ? void 0 : _a.id, ...args); }, (options) => (...args) => { var _a; return unwind(create((_a = options.bot) === null || _a === void 0 ? void 0 : _a.id, ...args)); }), setTimeout: botTimer('timeout', setTimeout, true), setInterval: botTimer('interval', setInterval, false), watchPortal: watchPortalBots(), watchBot: watchBot(), }, }; function botTimer(type, // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type func, clearAfterHandlerIsRun) { return (options) => function (handler, timeout, ...args) { if (!options.bot) { throw new Error(`Timers are not supported when there is no current bot.`); } if (typeof handler !== 'function') { throw new Error('A handler function must be provided.'); } let timer; if (clearAfterHandlerIsRun) { timer = func(function () { let result; try { // eslint-disable-next-line prefer-rest-params result = handler(...arguments); } finally { context.removeBotTimer(options.bot.id, type, timer); } context.processBotTimerResult(result); }, timeout, ...args); } else { timer = func(function () { context.processBotTimerResult( // eslint-disable-next-line prefer-rest-params handler(...arguments)); }, timeout, ...args); } context.recordBotTimer(options.bot.id, { timerId: timer, type: type, }); return timer; }; } function clearTimeout(id) { context.cancelAndRemoveTimers(id); } function clearInterval(id) { context.cancelAndRemoveTimers(id); } function watchPortalBots() { let timerId = 0; return (options) => function (portalId, handler) { let id = timerId++; const finalHandler = () => { try { let result = handler(); return wrapGenerator(result); } catch (err) { context.enqueueError(err); } }; context.recordBotTimer(options.bot.id, { type: 'watch_portal', timerId: id, portalId, tag: options.tag, handler: finalHandler, }); return id; }; } function watchBot() { let timerId = 0; return (options) => function (bot, handler) { let id = timerId++; let botIds = Array.isArray(bot) ? bot.map((b) => getID(b)) : [getID(bot)]; const finalHandler = () => { try { let result = handler(); return wrapGenerator(result); } catch (err) { context.enqueueError(err); } }; for (let botId of botIds) { context.recordBotTimer(options.bot.id, { type: 'watch_bot', timerId: id, botId: botId, tag: options.tag, handler: finalHandler, }); } return id; }; } function wrapGenerator(result) { if (isGenerator(result)) { const gen = result; let valid = true; const generatorWrapper = { [Symbol.iterator]: () => generatorWrapper, next(value) { if (!valid) { return { done: true, value: undefined, }; } try { return gen.next(value); } catch (err) { valid = false; context.enqueueError(err); return { done: true, value: undefined, }; } }, return(value) { if (!valid) { return { done: true, value: undefined, }; } try { return gen.return(value); } catch (err) { valid = false; context.enqueueError(err); return { done: true, value: undefined, }; } }, throw(e) { if (!valid) { return { done: true, value: undefined, }; } try { return gen.throw(e); } catch (err) { valid = false; context.enqueueError(err); return { done: true, value: undefined, }; } }, }; return generatorWrapper; } else { return result; } } function clearWatchBot(id) { context.cancelAndRemoveTimers(id, 'watch_bot'); } function clearWatchPortal(id) { context.cancelAndRemoveTimers(id, 'watch_portal'); } /** * Verifies that the given condition is true. * If it is not, then an error is thrown with the given message. * This function is useful for automated testing since tests should ideally throw an error if the test fails. * It can also be useful to make sure that some important code is only run if a precondition is met. * * @param condition the condition that should be verified. * @param message the message that should be included in the error. * * @example Assert that the tag color is "blue" * assert(tags.color === "blue", "The tag color is not blue!"); * * @dochash actions/debuggers * @docname assert */ function assert(condition, message) { if (!condition) { if (hasValue(message)) { throw new Error('Assertion failed. ' + message); } else { throw new Error('Assertion failed.'); } } } function getAssertionValue(value) { if (value instanceof Error) { return value.toString(); } return value; } /** * Verifies that the given values are equal to each other. * If they are not, then an error is thrown. * This function is useful for automated testing since tests should ideally throw an error if the test fails. * It can also be useful to make sure that some important code is only run if a precondition is met. * * @param first The first value to test. * @param second The second value to test. * * @example Assert that the tag color is "blue" * assertEqual(tags.color, "blue"); * * @example Assert that the bot contains some specific tag values * assertEqual(tags, { * color: "blue", * home: true, * homeX: 0, * homeY: 0 * }); * * @dochash actions/debuggers * @docname assertEqual */ function assertEqual(first, second) { expect(first).toEqual(second); } /** * Gets an array of bots that match the given tag and value. The returned array is sorted alphabetically by the {@tag id} tag. * * @param tag the name of the tag. Bots that have this tag will be included as long as they also match the second parameter. * @param value the value the tag should match. If not specified, then all bots with the tag will be included. If specified, then only bots that have the same tag and value will be included. If you specify a function as the value, then it will be used to match tag values. * * @example Find all the bots with #name set to "bob" * let bots = getBots("#name", "bob"); * * @example Find all bots with a #height larger than 2 * let bots = getBots("#height", height => height > 2); * * @example Find all bots with the #test tag * let bots = getBots("#test"); * * @dochash actions/data * @doctitle Data Actions * @docsidebar Data * @docdescription The Data Actions are used to get and set data on bots. * @docgroup 01-data-actions * @docgrouptitle Data Actions * @docname getBots * @docid getbots-tag */ function __getBots(tag, value) { return null; } /** * Gets an array of bots that match all of the given filter(s). The returned array is sorted alphabetically by the {@tag id} tag. * * @param filters If no filters are specified, then all bots in the inst are returned. If multiple filters are specified, then only the bots that match all of the filters are returned. * * @example Gets all the bots in the inst. * let bots = getBots(); * * @example Find all bots with the "test" tag * let bots = getBots(byTag("#test")); * * @example Find all bots with #name set to "bob" and in the #people dimension * let bots = getBots(byTag("#name", "bob"), inDimension("people")); * * @dochash actions/data * @docgroup 01-data-actions * @docname getBots * @docid getbots-filters */ function _getBots(...filters) { return null; } /** * @hidden */ function getBots(...filters) { if (filters.length > 0 && typeof filters[0] === 'function') { const filtered = context.bots.filter((b) => filters.every((f) => f(b))); const sortFuncs = filters .filter((f) => typeof f.sort === 'function') .map((f) => f.sort); const sorted = sortFuncs.length > 0 ? sortBy(filtered, ...sortFuncs) : filtered; return sorted; } let tag = filters[0]; if (typeof tag === 'undefined') { return context.bots.slice(); } else if (!tag) { return []; } tag = trimTag(tag); const filter = filters[1]; if (hasValue(filter)) { if (typeof filter === 'function') { return context.bots.filter((b) => filter(b.tags[tag])); } else if (tag === 'id' && typeof filter === 'string') { const bot = context.state[filter]; return bot ? [bot] : []; } else { return context.bots.filter((b) => b.tags[tag] === filter); } } else { return context.bots.filter((b) => hasValue(b.tags[tag])); } } /** * Get the first bot that matches all of the given filter(s). * If multiple bots match the given filter(s), then bots are sorted alphabetically by the [#id](tags:id) tag and the first one is returned. * If no bots match the given filter(s), then `undefined` is returned. * * @param filters If no filters are specified, then all bots in the inst are returned. If multiple filters are specified, then only the bots that match all of the filters are returned. * * @example Find a bot with the #test tag * let foundBot = getBot(byTag("#test")); * * @example Find a bot with #name set to "bob" and in the #people dimension * let foundBot = getBot(byTag("#name", "bob"), inDimension("people")); * * @dochash actions/data * @docgroup 01-data-actions * @docid getbot-filters * @docname getBot */ function _getBot(...filters) { return null; } /** * Gets the first bot that matches the given tag and value. * If multiple bots match the given tag and value, then bots are sorted alphabetically by the [#id](tags:id) tag and the first one is returned. * If no bots match the given tag and value, then `undefined` is returned. * @param tag the name of the tag to search for. * @param value the value the tag should match. If not specified, then the first bot with the tag will be returned. If specified, then the first bot that has the same tag and value will be returned. If you specify a function as the value, then it will be used to match tag values. * * @example Find the first bot with #name set to "bob" * let foundBot = getBot("#name", "bob"); * * @example Find the first bot with a #height larger than 2 * let foundBot = getBot("#height", height => height > 2); * * @example Find the first bot with the #test tag * let foundBot = getBot("#test"); * * @dochash actions/data * @docgroup 01-data-actions * @docid getbot-tag * @docname getBot */ function __getBot(tag, value) { return null; } function getBot(...args) { const bots = getBots(...args); return bots.first(); } /** * Gets a list of all the values in the inst for the given tag. Optionally accepts a filter for the tag values. * * @param tag the name of the tag to search for. * @param filter the filter that the tag values should match. If not specified, then all the tag values are included. If it is a function, then it will be used to