UNPKG

@microsoft/teams.apps

Version:

<p> <a href="https://www.npmjs.com/package/@microsoft/teams.apps" target="_blank"> <img src="https://img.shields.io/npm/v/@microsoft/teams.apps/latest" /> </a> <a href="https://www.npmjs.com/package/@microsoft/teams.apps?activeTab=code

189 lines • 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TurnStateLoader = void 0; exports.createStateLoader = createStateLoader; exports.createOAuthStateLoader = createOAuthStateLoader; const teams_common_1 = require("@microsoft/teams.common"); const constants_1 = require("../diagnostics/constants"); const helpers_1 = require("../diagnostics/helpers"); const container_1 = require("./container"); const turn_state_1 = require("./turn-state"); /** * Loads and saves per-turn state scopes using app storage. * * Each save replaces one complete scope. Concurrent turns therefore use * last-writer-wins semantics. */ class TurnStateLoader { storage; keyPrefix; logger; persistenceKeys = new WeakMap(); /** * Creates a state loader. * @param storage Storage for serialized state JSON. * @param options State storage and key options. * @param logger Optional logger for malformed persisted values. */ constructor(storage, options = {}, logger) { this.storage = storage; this.keyPrefix = options.keyPrefix ?? 'ts'; this.logger = logger; } /** * Returns the persisted conversation-scope key. * * The conversation ID is percent-encoded as one key segment. * @param conversationId Conversation ID. */ conversationKey(conversationId) { return `${this.keyPrefix}:conv:${encodeKeySegment(conversationId)}`; } /** * Returns the persisted user-within-conversation key. * * The conversation and user IDs are independently percent-encoded so * delimiter characters cannot make distinct ID pairs share a key. * @param conversationId Conversation ID. * @param userId User ID. */ userKey(conversationId, userId) { return `${this.keyPrefix}:user:${encodeKeySegment(conversationId)}:${encodeKeySegment(userId)}`; } /** * Loads conversation state and, when a user ID is present, user state. * @param conversationId Conversation ID for the activity. * @param userId Optional sender ID. */ async load(conversationId, userId) { if (!conversationId) { throw new Error('A conversation ID is required to load turn state.'); } const conversationKey = this.conversationKey(conversationId); const userKey = userId ? this.userKey(conversationId, userId) : undefined; return traceStateOperation(constants_1.APP_SPAN_NAMES.stateLoad, async () => { const conversation = await this.loadScope(conversationKey); const user = userKey ? await this.loadScope(userKey) : undefined; const container = new container_1.TurnStateContainer(conversation, user, () => this.delete(conversationId, userId)); this.persistenceKeys.set(container, { conversation: conversationKey, user: userKey, }); return container; }); } /** * Persists dirty scopes to the same storage keys from which they were loaded. * * Manually constructed containers are not bound to storage and cannot be * saved. Dirty empty scopes delete their backing keys; clean scopes perform * no I/O. Each successfully persisted scope is marked clean independently. * @param container Loaded state container to persist. */ async save(container) { const keys = this.persistenceKeys.get(container); if (!keys) { throw new Error('Turn state can only be saved by the loader that loaded it.'); } await traceStateOperation(constants_1.APP_SPAN_NAMES.stateSave, async () => { await this.saveScope(keys.conversation, container.conversation); if (container.user && keys.user) { await this.saveScope(keys.user, container.user); } }); } /** * Deletes persisted conversation and user scopes without loading them. * @param conversationId Conversation ID to delete. * @param userId Optional user ID to delete within the conversation. */ async delete(conversationId, userId) { await traceStateOperation(constants_1.APP_SPAN_NAMES.stateDelete, async () => { await this.storage.delete(this.conversationKey(conversationId)); if (userId) { await this.storage.delete(this.userKey(conversationId, userId)); } }); } async loadScope(key) { const value = await this.storage.get(key); if (value === undefined) { return new turn_state_1.TurnState(); } const data = deserializeState(value); if (!data) { this.logger?.warn('Ignoring malformed persisted turn state; expected a JSON object string.'); return new turn_state_1.TurnState(); } return new turn_state_1.TurnState(data); } async saveScope(key, state) { if (!state.isDirty) { return; } if (state.isEmpty) { await this.storage.delete(key); state.markClean(); return; } const value = JSON.stringify(state.toRecord()); await this.storage.set(key, value); state.markClean(); } } exports.TurnStateLoader = TurnStateLoader; /** * Creates the app state loader when state is enabled. * @param state `true` for defaults, options for custom behavior, or a falsy value to disable state. * @param logger Logger used to warn about process-local storage. */ function createStateLoader(state, logger) { return createStateLoaderWithWarning(state, logger, 'Per-turn state is using LocalStorage and will not be shared across processes. Configure state.storage for production.'); } /** @internal Creates the default state loader enabled by OAuth flow registration. */ function createOAuthStateLoader(logger) { return createStateLoaderWithWarning(true, logger, 'OAuth flow registration automatically enabled per-turn state using LocalStorage. ' + 'Pending sign-in attribution and token-exchange deduplication will not be shared across processes, ' + 'so OAuth sign-in may fail in multi-instance deployments. Configure state.storage with shared storage before running multiple instances.'); } function createStateLoaderWithWarning(state, logger, localStorageWarning) { if (!state) { return undefined; } const options = state === true ? {} : state; const storage = options.storage ?? new teams_common_1.LocalStorage(); if (storage instanceof teams_common_1.LocalStorage) { logger.warn(localStorageWarning); } return new TurnStateLoader(storage, options, logger); } function deserializeState(value) { try { const data = JSON.parse(value); return isStateRecord(data) ? data : undefined; } catch { return undefined; } } function isStateRecord(value) { return !!value && typeof value === 'object' && !Array.isArray(value); } function encodeKeySegment(value) { return encodeURIComponent(value).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`); } async function traceStateOperation(name, execute) { return (0, helpers_1.getTeamsBotApplicationTracer)().startActiveSpan(name, {}, async (span) => { try { return await execute(); } catch (error) { (0, helpers_1.recordTeamsBotApplicationException)(span, error); throw error; } finally { span.end(); } }); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9hZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3N0YXRlL2xvYWRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUErS0EsOENBU0M7QUFHRCx3REFRQztBQW5NRCwwREFBMEU7QUFFMUUsd0RBQTBEO0FBQzFELG9EQUdnQztBQUVoQywyQ0FBaUQ7QUFFakQsNkNBQXlDO0FBT3pDOzs7OztHQUtHO0FBQ0gsTUFBYSxlQUFlO0lBQ1QsT0FBTyxDQUEyQjtJQUNsQyxTQUFTLENBQVM7SUFDbEIsTUFBTSxDQUFXO0lBQ2pCLGVBQWUsR0FBRyxJQUFJLE9BQU8sRUFHM0MsQ0FBQztJQUVKOzs7OztPQUtHO0lBQ0gsWUFDRSxPQUFpQyxFQUNqQyxVQUF3QixFQUFFLEVBQzFCLE1BQWdCO1FBRWhCLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsSUFBSSxJQUFJLENBQUM7UUFDM0MsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7SUFDdkIsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsZUFBZSxDQUFDLGNBQXNCO1FBQ3BDLE9BQU8sR0FBRyxJQUFJLENBQUMsU0FBUyxTQUFTLGdCQUFnQixDQUFDLGNBQWMsQ0FBQyxFQUFFLENBQUM7SUFDdEUsQ0FBQztJQUVEOzs7Ozs7O09BT0c7SUFDSCxPQUFPLENBQUMsY0FBc0IsRUFBRSxNQUFjO1FBQzVDLE9BQU8sR0FBRyxJQUFJLENBQUMsU0FBUyxTQUFTLGdCQUFnQixDQUFDLGNBQWMsQ0FBQyxJQUFJLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7SUFDbEcsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsSUFBSSxDQUFDLGNBQXNCLEVBQUUsTUFBZTtRQUNoRCxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFDcEIsTUFBTSxJQUFJLEtBQUssQ0FBQyxtREFBbUQs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