@asfweb/grpc-session
Version:
## Installation: ``` yarn add @asfweb/grpc-session ``` or ``` npm install @asfweb/grpc-session --save ```
331 lines (330 loc) • 13 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
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());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Session = exports.SessionError = void 0;
// Session
var grpc_js_1 = require("@grpc/grpc-js");
var crypto_js_1 = require("crypto-js");
var cookie_1 = __importDefault(require("cookie"));
var moment_1 = __importDefault(require("moment"));
var nanoid_1 = require("nanoid");
var constants_1 = require("./constants");
// import { SessionRedisStore } from ".";
/**
* Session Error Class
*/
var SessionError = /** @class */ (function (_super) {
__extends(SessionError, _super);
function SessionError(message) {
var _newTarget = this.constructor;
var _this =
// 'Error' breaks prototype chain here
_super.call(this, message) || this;
// Restore prototype chain
var proto = _newTarget.prototype;
if (Object.setPrototypeOf) {
Object.setPrototypeOf(_this, proto);
}
else {
_this.__proto__ = proto;
}
return _this;
}
return SessionError;
}(Error));
exports.SessionError = SessionError;
/**
* Session class
*/
var Session = /** @class */ (function () {
/**
* Session
*
* @param store Session Store
* @param options {sessionName:"_SID", expires: "Time in seconds: 60*60*20"}
*/
function Session(store, options) {
if (options === void 0) { options = {
sessionName: "_SID",
checkOrigin: true,
expires: 60 * 60 * 20,
cookie: { path: "/", httpOnly: true },
debug: false,
}; }
this.sessionData = null;
this.store = store;
this.sessionId = "";
this.sessionName = options.sessionName || "_SID";
this.options = options;
this.metadata = new grpc_js_1.Metadata();
}
/**
* Creates new session
*
* @param sessionData
* @returns Session
*/
Session.prototype.start = function (sessionData) {
this.sessionId = (0, nanoid_1.nanoid)();
this.sessionData = {};
if (sessionData) {
this.sessionData = sessionData;
}
return this;
};
/**
* Will try to restore session from id or will create a new one
*
* @param call ServerSurfaceCall
* @returns Promise<Session>
*/
Session.prototype.gRPC = function (call, sessionData) {
return __awaiter(this, void 0, void 0, function () {
var cookiesHeader, cookies, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
// Check cookies for session id
this.metadata = call.metadata;
cookiesHeader = this.metadata.get("cookie").toString();
cookies = cookie_1.default.parse(cookiesHeader);
if (!cookies[this.sessionName]) return [3 /*break*/, 6];
this.sessionId = cookies[this.sessionName];
_a = this;
return [4 /*yield*/, this.store.get(this.sessionId)];
case 1:
_a.sessionData = _b.sent();
if (!(this.sessionData === null)) return [3 /*break*/, 2];
this.start();
return [3 /*break*/, 5];
case 2:
if (!(this.sessionData.exp && this.sessionData.exp < (0, moment_1.default)().unix())) return [3 /*break*/, 4];
if (this.options.debug) {
console.log("Start new session: Previous session has been expired.");
}
return [4 /*yield*/, this.store.delete(this.sessionId)];
case 3:
_b.sent();
this.start();
_b.label = 4;
case 4:
// Check origin
if (this.options.checkOrigin && this.get("hash") !== this._MD5_hash()) {
if (this.options.debug) {
console.log("Start new session: Session has different origin.");
}
this.start();
}
_b.label = 5;
case 5: return [3 /*break*/, 7];
case 6:
if (this.options.debug) {
console.log("Start new session: Session cookie does not exist.");
}
// SessionId does not exist in cookies header start new session
this.start();
_b.label = 7;
case 7:
// Sends cookie header
call.sendMetadata(this.getMetadata());
// Initial session data
this.sessionData = __assign(__assign({}, this.sessionData), sessionData);
// Save session
return [4 /*yield*/, this.save()];
case 8:
// Save session
_b.sent();
return [2 /*return*/, this];
}
});
});
};
/**
* Get session key
*
* @param key string key
* @returns string
* @throws SessionError
*/
Session.prototype.get = function (key) {
if (this.sessionData === null) {
throw new SessionError(constants_1._ERROR_SESSION_DATA);
}
if (key) {
return this.sessionData[key];
}
return this.sessionData;
};
/**
* Sets new session key
*
* @param key string
* @param value string
* @returns Session
* @throws SessionError
*/
Session.prototype.set = function (key, value) {
if (this.sessionData === null) {
throw new SessionError(constants_1._ERROR_SESSION_DATA);
}
var newData = {};
newData[key] = value;
this.sessionData = __assign(__assign({}, this.sessionData), newData);
return this;
};
/**
* Removes key from session
*
* @param key string
* @returns Session
* @throws SessionError
*/
Session.prototype.remove = function (key) {
if (this.sessionData === null) {
throw new SessionError(constants_1._ERROR_SESSION_DATA);
}
delete this.sessionData[key];
return this;
};
/**
* Gets session id
*
* @returns string
* @throws SessionError
*/
Session.prototype.id = function () {
if (!this.sessionId) {
throw new SessionError(constants_1._ERROR_SESSION_ID);
}
return this.sessionId;
};
/**
* Get Grpc Metadata
*
* @returns Metadata
* @throws SessionError
*/
Session.prototype.getMetadata = function () {
if (this.sessionData === null) {
throw new SessionError(constants_1._ERROR_SESSION_DATA);
}
var defaultCookiesOptions = {
path: "/",
httpOnly: true,
secure: true,
SameSite: "Lax",
};
var options = __assign(__assign(__assign({}, defaultCookiesOptions), { maxAge: this.options.expires || 0 }), this.options.cookie);
var metadata = new grpc_js_1.Metadata();
metadata.set("Set-Cookie", cookie_1.default.serialize(this.sessionName, this.sessionId, options));
return metadata;
};
/**
* Saves Session
*
* @returns Promise<boolean>
* @throws SessionError
*/
Session.prototype.save = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (this.sessionData === null) {
throw new SessionError(constants_1._ERROR_SESSION_DATA);
}
// Sets expiration time inside session data
if (this.options.expires) {
this.set("exp", (0, moment_1.default)().unix() + this.options.expires);
}
if (this.options.checkOrigin) {
this.set("hash", this._MD5_hash());
}
return [2 /*return*/, this.store.set(this.sessionId, this.sessionData)];
});
});
};
/**
* Deletes Session
*
* @returns Promise<boolean>
*/
Session.prototype.destroy = function () {
return this.store.delete(this.sessionId);
};
/**
* A md5 hash of sessionId : user-agent header
*
* @returns string
*/
Session.prototype._MD5_hash = function () {
var userAgent = this.metadata.get("user-agent").toString();
var origin = this.metadata.get("origin").toString();
var hash = (0, crypto_js_1.MD5)("".concat(this.sessionId, ":").concat(userAgent, ":").concat(origin)).toString();
return hash;
};
return Session;
}());
exports.Session = Session;