mirror of
https://github.com/a-sync/game-server-watcher.git
synced 2026-07-31 17:38:55 -04:00
275 lines
10 KiB
JavaScript
275 lines
10 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.MemorySessionStorage = exports.lazySession = exports.session = void 0;
|
|
const platform_node_js_1 = require("../platform.node.js");
|
|
const debug = (0, platform_node_js_1.debug)("grammy:session");
|
|
/**
|
|
* Session middleware provides a persistent data storage for your bot. You can
|
|
* use it to let your bot remember any data you want, for example the messages
|
|
* it sent or received in the past. This is done by attaching _session data_ to
|
|
* every chat. The stored data is then provided on the context object under
|
|
* `ctx.session`.
|
|
*
|
|
* > **What is a session?** Simply put, the session of a chat is a little
|
|
* > persistent storage that is attached to it. As an example, your bot can send
|
|
* > a message to a chat and store the ID of that message in the corresponding
|
|
* > session. The next time your bot receives an update from that chat, the
|
|
* > session will still contain that ID.
|
|
* >
|
|
* > Session data can be stored in a database, in a file, or simply in memory.
|
|
* > grammY only supports memory sessions out of the box, but you can use
|
|
* > third-party session middleware to connect to other storage solutions. Note
|
|
* > that memory sessions will be lost when you stop your bot and the process
|
|
* > exits, so they are usually not useful in production.
|
|
*
|
|
* Whenever your bot receives an update, the first thing the session middleware
|
|
* will do is to load the correct session from your storage solution. This
|
|
* object is then provided on `ctx.session` while your other middleware is
|
|
* running. As soon as your bot is done handling the update, the middleware
|
|
* takes over again and writes back the session object to your storage. This
|
|
* allows you to modify the session object arbitrarily in your middleware, and
|
|
* to stop worrying about the database.
|
|
*
|
|
* ```ts
|
|
* bot.use(session())
|
|
*
|
|
* bot.on('message', ctx => {
|
|
* // The session object is persisted across updates!
|
|
* const session = ctx.session
|
|
* })
|
|
* ```
|
|
*
|
|
* It is recommended to make use of the `inital` option in the configuration
|
|
* object, which correctly initializes session objects for new chats.
|
|
*
|
|
* You can delete the session data by setting `ctx.session` to `null` or
|
|
* `undefined`.
|
|
*
|
|
* Check out the [documentation](https://grammy.dev/plugins/session.html) on the
|
|
* website to know more about how sessions work in grammY.
|
|
*
|
|
* @param options Optional configuration to pass to the session middleware
|
|
*/
|
|
function session(options = {}) {
|
|
var _a, _b;
|
|
const getSessionKey = (_a = options.getSessionKey) !== null && _a !== void 0 ? _a : defaultGetSessionKey;
|
|
const storage = (_b = options.storage) !== null && _b !== void 0 ? _b : (debug("Storing session data in memory, all data will be lost when the bot restarts."),
|
|
new MemorySessionStorage());
|
|
return async (ctx, next) => {
|
|
var _a, _b;
|
|
const key = await getSessionKey(ctx);
|
|
let value = key === undefined
|
|
? undefined
|
|
: (_a = (await storage.read(key))) !== null && _a !== void 0 ? _a : (_b = options.initial) === null || _b === void 0 ? void 0 : _b.call(options);
|
|
Object.defineProperty(ctx, "session", {
|
|
enumerable: true,
|
|
get() {
|
|
if (key === undefined) {
|
|
const msg = undef("access", getSessionKey);
|
|
throw new Error(msg);
|
|
}
|
|
return value;
|
|
},
|
|
set(v) {
|
|
if (key === undefined) {
|
|
const msg = undef("assign", getSessionKey);
|
|
throw new Error(msg);
|
|
}
|
|
value = v;
|
|
},
|
|
});
|
|
await next(); // no catch: do not write back if middleware throws
|
|
if (key !== undefined) {
|
|
if (value == null)
|
|
await storage.delete(key);
|
|
else
|
|
await storage.write(key, value);
|
|
}
|
|
};
|
|
}
|
|
exports.session = session;
|
|
/**
|
|
* > This is an advanced function of grammY.
|
|
*
|
|
* Generally speaking, lazy sessions work just like normal sessions—just they
|
|
* are loaded on demand. Except for a few `async`s and `await`s here and there,
|
|
* their usage looks 100 % identical.
|
|
*
|
|
* Instead of directly querying the storage every time an update arrives, lazy
|
|
* sessions quickly do this _once you access_ `ctx.session`. This can
|
|
* significantly reduce the database traffic (especially when your bot is added
|
|
* to group chats), because it skips a read and a wrote operation for all
|
|
* updates that the bot does not react to.
|
|
*
|
|
* ```ts
|
|
* // The options are identical
|
|
* bot.use(lazySession({ storage: ... }))
|
|
*
|
|
* bot.on('message', async ctx => {
|
|
* // The session object is persisted across updates!
|
|
* const session = await ctx.session
|
|
* // ^
|
|
* // |
|
|
* // This plain property access (no function call) will trigger the database query!
|
|
* })
|
|
* ```
|
|
*
|
|
* Check out the
|
|
* [documentation](https://grammy.dev/plugins/session.html#lazy-sessions) on the
|
|
* website to know more about how lazy sessions work in grammY.
|
|
*
|
|
* @param options Optional configuration to pass to the session middleware
|
|
*/
|
|
function lazySession(options = {}) {
|
|
var _a, _b;
|
|
const getSessionKey = (_a = options.getSessionKey) !== null && _a !== void 0 ? _a : defaultGetSessionKey;
|
|
const storage = (_b = options.storage) !== null && _b !== void 0 ? _b : (debug("Storing session data in memory, all data will be lost when the bot restarts."),
|
|
new MemorySessionStorage());
|
|
return async (ctx, next) => {
|
|
const key = await getSessionKey(ctx);
|
|
let value = undefined;
|
|
let promise = undefined;
|
|
let fetching = false;
|
|
let read = false;
|
|
let wrote = false;
|
|
async function load() {
|
|
var _a;
|
|
if (key === undefined) {
|
|
const msg = undef("access", getSessionKey, { lazy: true });
|
|
throw new Error(msg);
|
|
}
|
|
let v = await storage.read(key);
|
|
if (!fetching)
|
|
return value;
|
|
if (v === undefined) {
|
|
v = (_a = options.initial) === null || _a === void 0 ? void 0 : _a.call(options);
|
|
if (v !== undefined) {
|
|
wrote = true;
|
|
value = v;
|
|
}
|
|
}
|
|
else {
|
|
value = v;
|
|
}
|
|
return value;
|
|
}
|
|
Object.defineProperty(ctx, "session", {
|
|
enumerable: true,
|
|
get() {
|
|
if (wrote)
|
|
return value;
|
|
read = true;
|
|
if (promise === undefined) {
|
|
fetching = true;
|
|
promise = load();
|
|
}
|
|
return promise;
|
|
},
|
|
set(v) {
|
|
if (key === undefined) {
|
|
const msg = undef("assign", getSessionKey, { lazy: true });
|
|
throw new Error(msg);
|
|
}
|
|
wrote = true;
|
|
fetching = false;
|
|
value = v;
|
|
},
|
|
});
|
|
await next(); // no catch: do not wrote back if middleware throws
|
|
if (key !== undefined) {
|
|
if (read)
|
|
await promise;
|
|
if (read || wrote) {
|
|
value = await value;
|
|
if (value == null)
|
|
await storage.delete(key);
|
|
else
|
|
await storage.write(key, value);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
exports.lazySession = lazySession;
|
|
/** Stores session data per chat by default */
|
|
function defaultGetSessionKey(ctx) {
|
|
var _a;
|
|
return (_a = ctx.chat) === null || _a === void 0 ? void 0 : _a.id.toString();
|
|
}
|
|
/** Returns a useful error message for when the session key is undefined */
|
|
function undef(op, getSessionKey, opts = {}) {
|
|
var _a;
|
|
const lazy = (_a = opts.lazy) !== null && _a !== void 0 ? _a : false;
|
|
const reason = getSessionKey === defaultGetSessionKey
|
|
? "this update does not belong to a chat, so the session key is undefined"
|
|
: "the custom `getSessionKey` function returned undefined for this update";
|
|
return `Cannot ${op} ${lazy ? "lazy " : ""}session data because ${reason}!`;
|
|
}
|
|
/**
|
|
* The memory session storage is a built-in storage adapter that saves your
|
|
* session data in RAM using a regular JavaScript `Map` object. If you use this
|
|
* storage adapter, all sessions will be lost when your process terminates or
|
|
* restarts. Hence, you should only use it for short-lived data that is not
|
|
* important to persist.
|
|
*
|
|
* This class is used as default if you do not provide a storage adapter, e.g.
|
|
* to your database.
|
|
*
|
|
* This storage adapter features expiring sessions. When instatiating this class
|
|
* yourself, you can pass a time to live in milliseconds that will be used for
|
|
* each session object. If a session for a user expired, the session data will
|
|
* be discarded on its first read, and a fresh session object as returned by the
|
|
* `inital` option (or undefined) will be put into place.
|
|
*/
|
|
class MemorySessionStorage {
|
|
/**
|
|
* Constructs a new memory session storage with the given time to live. Note
|
|
* that this storage adapter will not store your data permanently.
|
|
*
|
|
* @param timeToLive TTL in milliseconds, default is `Infinity`
|
|
*/
|
|
constructor(timeToLive = Infinity) {
|
|
this.timeToLive = timeToLive;
|
|
/**
|
|
* Internally used `Map` instance that stores the session data
|
|
*/
|
|
this.storage = new Map();
|
|
}
|
|
read(key) {
|
|
const value = this.storage.get(key);
|
|
if (value === undefined)
|
|
return undefined;
|
|
if (value.expires !== undefined && value.expires < Date.now()) {
|
|
this.delete(key);
|
|
return undefined;
|
|
}
|
|
return value.session;
|
|
}
|
|
/**
|
|
* Reads the values for all keys of the session storage, and returns them as
|
|
* an array.
|
|
*/
|
|
readAll() {
|
|
return Array
|
|
.from(this.storage.keys())
|
|
.map((key) => this.read(key))
|
|
.filter((value) => value !== undefined);
|
|
}
|
|
write(key, value) {
|
|
this.storage.set(key, this.addExpiryDate(value));
|
|
}
|
|
addExpiryDate(value) {
|
|
const ttl = this.timeToLive;
|
|
if (ttl !== undefined && ttl < Infinity) {
|
|
const now = Date.now();
|
|
return { session: value, expires: now + ttl };
|
|
}
|
|
else {
|
|
return { session: value };
|
|
}
|
|
}
|
|
delete(key) {
|
|
this.storage.delete(key);
|
|
}
|
|
}
|
|
exports.MemorySessionStorage = MemorySessionStorage;
|