connect-cosmosdb
Version:
Cosmos DB based session store for express-session
249 lines (248 loc) • 9.61 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_session_1 = require("express-session");
const noop = () => null;
class CosmosStore extends express_session_1.Store {
constructor() {
super();
if (CosmosStore._store)
throw new Error('Cosmos Store has already been initialized. Please use CosmosStore.initializeStore()');
if (!CosmosStore.options.cosmosClient) {
throw new Error('Cannot initialize Cosmos Store. Please use CosmosStore.initializeStore()');
}
}
// Update static options
static updateOptions(options) {
CosmosStore.options.cosmosClient = options.cosmosClient;
CosmosStore.options.containerName =
options.containerName || CosmosStore.DEFAULT_CONTAINER_NAME;
CosmosStore.options.databaseName = options.databaseName;
CosmosStore.options.disableTouch = options.disableTouch || false;
CosmosStore.options.ttl = options.ttl || CosmosStore.DEFAULT_TTL; //Default TTL is 1 day
}
/**
* Initialize a Cosmos Store instance for Session Storage.
* @param options Cosmos Store Options
* @returns A Cosmos Store instance
*/
static initializeStore(options) {
return __awaiter(this, void 0, void 0, function* () {
if (!options.cosmosClient)
throw new Error('Cosmos DB Client is required.');
if (!options.databaseName)
throw new Error('Database name is required');
if (CosmosStore._store) {
return CosmosStore._store;
}
CosmosStore.updateOptions(options);
CosmosStore._store = new CosmosStore();
yield CosmosStore._store.setup();
return CosmosStore._store;
});
}
/**
*
* Get the session dat for the session ID
* @param sid Session ID
* @param callback callback function to call after execution
*/
get(sid, callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { resource } = yield this.container
.item(sid, sid)
.read();
if (!resource) {
return callback(null, null);
}
return callback(null, resource);
}
catch (error) {
callback(error);
}
});
}
/**
* Set the session data for the session ID
* @param sid Session ID
* @param session Session data
* @param callback callback function to call after execution
*/
set(sid, session, callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
const currentTTL = this.getTTL(session);
try {
const sessionItem = Object.assign({ id: sid }, session);
if (currentTTL) {
//TTL is set or is computed from "Expires" value.
if (currentTTL <= 0) {
//TTL Expired
return this.destroy(sid);
}
// Upsert session with TTL
yield this.container.items.upsert(Object.assign(Object.assign({}, sessionItem), { ttl: currentTTL }));
}
else {
// Upsert session without TTL
yield this.container.items.create(Object.assign({}, sessionItem));
}
callback(null);
}
catch (error) {
callback(error);
}
});
}
/**
* Destroy a session.
*
* @param sid Session ID to destroy
* @param callback callback function to call after execution
*/
destroy(sid, callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.container.item(sid, sid).delete();
callback(null);
}
catch (error) {
callback(error);
}
});
}
/**
* Get all sessions in the store
* @param callback callback function to call after execution
*/
all(callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { resources } = yield this.container.items.readAll().fetchAll();
callback(null, resources);
}
catch (error) {
callback(error);
}
});
}
/** Returns the number of sessions in the store. */
length(callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
try {
const { resources } = yield this.container.items
.query({
query: `SELECT VALUE COUNT(s.id) FROM ${this.container.id} s`,
})
.fetchAll();
const [count] = resources; //Result is a JSON array
callback(null, count);
}
catch (error) {
callback(error);
}
});
}
/** Delete all sessions from the store. */
clear(callback = noop) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
try {
const { containerName } = CosmosStore.options;
yield ((_a = this.database) === null || _a === void 0 ? void 0 : _a.container(containerName).delete());
yield this.createContainer();
return callback();
}
catch (error) {
callback(error);
}
});
}
/** "Touches" a given session, resetting the idle timer. */
touch(sid, session, callback = noop) {
return __awaiter(this, void 0, void 0, function* () {
if (CosmosStore.options.disableTouch) {
return callback();
}
try {
// Note: Any write/update operation will update the TTL value in Cosmos.
yield this.container.item(sid, sid).patch({
operations: [
{
op: 'replace',
path: '/ttl',
value: this.getTTL(session),
},
],
});
callback();
}
catch (error) {
callback();
}
});
}
// Initial Cosmos DB set-up
setup() {
return __awaiter(this, void 0, void 0, function* () {
const { databaseName, containerName, cosmosClient } = CosmosStore.options;
const { database } = yield cosmosClient.databases.createIfNotExists({
id: databaseName,
});
this.database = database;
yield this.createContainer();
this.container = this.database.container(containerName);
});
}
createContainer() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const { containerName } = CosmosStore.options;
// The TTL value can be overridden on individual items, if default TTL is non-null or -1.
// TTL is disabled by default
// If TTL was already defined (e.g. via Azure Portal/CLI), the predefined value would be used here,
// as the container would have been created already.
yield ((_a = this.database) === null || _a === void 0 ? void 0 : _a.containers.createIfNotExists({
id: containerName,
defaultTtl: -1,
partitionKey: CosmosStore.PARTITION_KEY_PATH,
}));
});
}
/**
* Get Time to live value in seconds for the session.
* If the Expires value is set, TTL will be calculated based on Expiry.
* Else the default value of 1 day would be used.
* @param session
* @returns TTL value in seconds (Cosmos DB uses seconds as TTL)
*/
getTTL(session) {
// Custom expiry computation.
if (typeof CosmosStore.options.ttl === 'function')
return CosmosStore.options.ttl(session);
if (session.cookie && session.cookie.expires) {
const sessionExpiry = Number(new Date(session.cookie.expires));
const current = Date.now();
//Cosmos DB TTL in seconds
return Math.ceil((sessionExpiry - current) / 1000);
}
return CosmosStore.options.ttl;
}
static reset() {
CosmosStore._store = null;
CosmosStore.options = {};
}
}
exports.default = CosmosStore;
CosmosStore.options = {};
CosmosStore.PARTITION_KEY_PATH = '/id';
CosmosStore.DEFAULT_CONTAINER_NAME = 'sessions';
CosmosStore.DEFAULT_TTL = 86400;