@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
JavaScript
;
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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