mirror of
https://github.com/standardnotes/server
synced 2026-01-23 05:01:45 -05:00
Compare commits
30 Commits
@standardn
...
@standardn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1665779b5 | ||
|
|
a82192db42 | ||
|
|
589b740f49 | ||
|
|
3c10de3e5d | ||
|
|
41a04062c9 | ||
|
|
db9d10c302 | ||
|
|
5596d04040 | ||
|
|
341f69e301 | ||
|
|
ef0464690b | ||
|
|
199ebeb4ea | ||
|
|
c949670d4c | ||
|
|
9dcd583b58 | ||
|
|
097e320490 | ||
|
|
c9bfda91f4 | ||
|
|
2d6a3ebf45 | ||
|
|
d0d4bd23fb | ||
|
|
edb0a768d0 | ||
|
|
4cc647ac07 | ||
|
|
bcd1d830e6 | ||
|
|
2597324876 | ||
|
|
69b404f5d4 | ||
|
|
e94b0d0b02 | ||
|
|
ed1bf37287 | ||
|
|
3946f56261 | ||
|
|
fc53dab007 | ||
|
|
e836abdef7 | ||
|
|
826482b1f0 | ||
|
|
45bd00919c | ||
|
|
4e1bae6daf | ||
|
|
8f23c8ab3f |
7
.github/workflows/e2e-self-hosted.yml
vendored
7
.github/workflows/e2e-self-hosted.yml
vendored
@@ -46,6 +46,13 @@ jobs:
|
||||
CACHE_TYPE: redis
|
||||
SERVICE_PROXY_TYPE: ${{ matrix.service_proxy_type }}
|
||||
|
||||
- name: Output Server Logs to File
|
||||
run: docker compose -f docker-compose.ci.yml logs -f > logs/docker-compose.log 2>&1 &
|
||||
env:
|
||||
DB_TYPE: mysql
|
||||
CACHE_TYPE: redis
|
||||
SERVICE_PROXY_TYPE: ${{ matrix.service_proxy_type }}
|
||||
|
||||
- name: Wait for server to start
|
||||
run: docker/is-available.sh http://localhost:3123 $(pwd)/logs
|
||||
|
||||
|
||||
3
.github/workflows/publish.yml
vendored
3
.github/workflows/publish.yml
vendored
@@ -4,6 +4,9 @@ on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: contains(github.event.head_commit.message, 'chore(release)') == false
|
||||
|
||||
72
.pnp.cjs
generated
72
.pnp.cjs
generated
@@ -7045,13 +7045,13 @@ const RAW_RUNTIME_STATE =
|
||||
["@standardnotes/websockets-server", "workspace:packages/websockets"],\
|
||||
["@aws-sdk/client-apigatewaymanagementapi", "npm:3.427.0"],\
|
||||
["@aws-sdk/client-sqs", "npm:3.427.0"],\
|
||||
["@standardnotes/api", "npm:1.26.26"],\
|
||||
["@standardnotes/common", "workspace:packages/common"],\
|
||||
["@standardnotes/domain-core", "workspace:packages/domain-core"],\
|
||||
["@standardnotes/domain-events", "workspace:packages/domain-events"],\
|
||||
["@standardnotes/domain-events-infra", "workspace:packages/domain-events-infra"],\
|
||||
["@standardnotes/responses", "npm:1.13.27"],\
|
||||
["@standardnotes/security", "workspace:packages/security"],\
|
||||
["@standardnotes/time", "workspace:packages/time"],\
|
||||
["@types/cors", "npm:2.8.13"],\
|
||||
["@types/express", "npm:4.17.17"],\
|
||||
["@types/ioredis", "npm:5.0.0"],\
|
||||
@@ -18048,14 +18048,16 @@ const Filename = {
|
||||
const npath = Object.create(path__default.default);
|
||||
const ppath = Object.create(path__default.default.posix);
|
||||
npath.cwd = () => process.cwd();
|
||||
ppath.cwd = () => toPortablePath(process.cwd());
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path__default.default.posix.resolve(...segments);
|
||||
} else {
|
||||
return path__default.default.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
ppath.cwd = process.platform === `win32` ? () => toPortablePath(process.cwd()) : process.cwd;
|
||||
if (process.platform === `win32`) {
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path__default.default.posix.resolve(...segments);
|
||||
} else {
|
||||
return path__default.default.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
}
|
||||
const contains = function(pathUtils, from, to) {
|
||||
from = pathUtils.normalize(from);
|
||||
to = pathUtils.normalize(to);
|
||||
@@ -18069,17 +18071,13 @@ const contains = function(pathUtils, from, to) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
npath.contains = (from, to) => contains(npath, from, to);
|
||||
ppath.contains = (from, to) => contains(ppath, from, to);
|
||||
const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
|
||||
const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/;
|
||||
const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/;
|
||||
const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/;
|
||||
function fromPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
function fromPortablePathWin32(p) {
|
||||
let portablePathMatch, uncPortablePathMatch;
|
||||
if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP))
|
||||
p = portablePathMatch[1];
|
||||
@@ -18089,9 +18087,7 @@ function fromPortablePath(p) {
|
||||
return p;
|
||||
return p.replace(/\//g, `\\`);
|
||||
}
|
||||
function toPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
function toPortablePathWin32(p) {
|
||||
p = p.replace(/\\/g, `/`);
|
||||
let windowsPathMatch, uncWindowsPathMatch;
|
||||
if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP))
|
||||
@@ -18100,6 +18096,10 @@ function toPortablePath(p) {
|
||||
p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`;
|
||||
return p;
|
||||
}
|
||||
const toPortablePath = process.platform === `win32` ? toPortablePathWin32 : (p) => p;
|
||||
const fromPortablePath = process.platform === `win32` ? fromPortablePathWin32 : (p) => p;
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
function convertPath(targetPathUtils, sourcePath) {
|
||||
return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath);
|
||||
}
|
||||
@@ -19144,6 +19144,12 @@ class ProxiedFS extends FakeFS {
|
||||
}
|
||||
}
|
||||
|
||||
function direntToPortable(dirent) {
|
||||
const portableDirent = dirent;
|
||||
if (typeof dirent.path === `string`)
|
||||
portableDirent.path = npath.toPortablePath(dirent.path);
|
||||
return portableDirent;
|
||||
}
|
||||
class NodeFS extends BasePortableFakeFS {
|
||||
constructor(realFs = fs__default.default) {
|
||||
super();
|
||||
@@ -19470,15 +19476,31 @@ class NodeFS extends BasePortableFakeFS {
|
||||
async readdirPromise(p, opts) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
if (opts) {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
|
||||
if (opts.recursive && process.platform === `win32`) {
|
||||
if (opts.withFileTypes) {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(direntToPortable)), reject));
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(npath.toPortablePath)), reject));
|
||||
}
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
|
||||
}
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback((value) => resolve(value), reject));
|
||||
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
|
||||
}
|
||||
});
|
||||
}
|
||||
readdirSync(p, opts) {
|
||||
if (opts) {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
|
||||
if (opts.recursive && process.platform === `win32`) {
|
||||
if (opts.withFileTypes) {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(direntToPortable);
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(npath.toPortablePath);
|
||||
}
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
|
||||
}
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p));
|
||||
}
|
||||
@@ -23032,8 +23054,6 @@ function getPathForDisplay(p) {
|
||||
const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10));
|
||||
const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13;
|
||||
|
||||
const builtinModules = new Set(require$$0.Module.builtinModules || Object.keys(process.binding(`natives`)));
|
||||
const isBuiltinModule = (request) => request.startsWith(`node:`) || builtinModules.has(request);
|
||||
function readPackageScope(checkPath) {
|
||||
const rootSeparatorIndex = checkPath.indexOf(npath.sep);
|
||||
let separatorIndex;
|
||||
@@ -23142,7 +23162,7 @@ function applyPatch(pnpapi, opts) {
|
||||
const pathRegExp = /^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:@[^/]+\/)?[^/]+)\/*(.*|)$/;
|
||||
const originalModuleResolveFilename = require$$0.Module._resolveFilename;
|
||||
require$$0.Module._resolveFilename = function(request, parent, isMain, options) {
|
||||
if (isBuiltinModule(request))
|
||||
if (require$$0.isBuiltin(request))
|
||||
return request;
|
||||
if (!enableNativeHooks)
|
||||
return originalModuleResolveFilename.call(require$$0.Module, request, parent, isMain, options);
|
||||
@@ -24504,7 +24524,7 @@ function makeApi(runtimeState, opts) {
|
||||
throw new Error(`resolveToUnqualified can not handle private import mappings`);
|
||||
if (request === `pnpapi`)
|
||||
return npath.toPortablePath(opts.pnpapiResolution);
|
||||
if (considerBuiltins && isBuiltinModule(request))
|
||||
if (considerBuiltins && require$$0.isBuiltin(request))
|
||||
return null;
|
||||
const requestForDisplay = getPathForDisplay(request);
|
||||
const issuerForDisplay = issuer && getPathForDisplay(issuer);
|
||||
@@ -24642,7 +24662,7 @@ ${brokenAncestors.map((ancestorLocator) => `Ancestor breaking the chain: ${ances
|
||||
}
|
||||
}
|
||||
} else if (dependencyReference === void 0) {
|
||||
if (!considerBuiltins && isBuiltinModule(request)) {
|
||||
if (!considerBuiltins && require$$0.isBuiltin(request)) {
|
||||
if (isDependencyTreeRoot(issuerLocator)) {
|
||||
error = makeError(
|
||||
ErrorCode.UNDECLARED_DEPENDENCY,
|
||||
@@ -24809,7 +24829,7 @@ ${candidates.map((candidate) => `Not found: ${getPathForDisplay(candidate)}
|
||||
if (unqualifiedPath === null)
|
||||
return null;
|
||||
const isIssuerIgnored = () => issuer !== null ? isPathIgnored(issuer) : false;
|
||||
const remappedPath = (!considerBuiltins || !isBuiltinModule(request)) && !isIssuerIgnored() ? resolveUnqualifiedExport(request, unqualifiedPath, conditions, issuer) : unqualifiedPath;
|
||||
const remappedPath = (!considerBuiltins || !require$$0.isBuiltin(request)) && !isIssuerIgnored() ? resolveUnqualifiedExport(request, unqualifiedPath, conditions, issuer) : unqualifiedPath;
|
||||
return resolveUnqualified(remappedPath, { extensions });
|
||||
} catch (error) {
|
||||
if (Object.hasOwn(error, `pnpCode`))
|
||||
|
||||
127
.pnp.loader.mjs
generated
127
.pnp.loader.mjs
generated
@@ -1,9 +1,9 @@
|
||||
import fs from 'fs';
|
||||
import { URL as URL$1, fileURLToPath, pathToFileURL } from 'url';
|
||||
import path from 'path';
|
||||
import moduleExports, { Module } from 'module';
|
||||
import { createHash } from 'crypto';
|
||||
import { EOL } from 'os';
|
||||
import moduleExports, { isBuiltin } from 'module';
|
||||
import assert from 'assert';
|
||||
|
||||
const SAFE_TIME = 456789e3;
|
||||
@@ -16,14 +16,16 @@ const PortablePath = {
|
||||
const npath = Object.create(path);
|
||||
const ppath = Object.create(path.posix);
|
||||
npath.cwd = () => process.cwd();
|
||||
ppath.cwd = () => toPortablePath(process.cwd());
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path.posix.resolve(...segments);
|
||||
} else {
|
||||
return path.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
ppath.cwd = process.platform === `win32` ? () => toPortablePath(process.cwd()) : process.cwd;
|
||||
if (process.platform === `win32`) {
|
||||
ppath.resolve = (...segments) => {
|
||||
if (segments.length > 0 && ppath.isAbsolute(segments[0])) {
|
||||
return path.posix.resolve(...segments);
|
||||
} else {
|
||||
return path.posix.resolve(ppath.cwd(), ...segments);
|
||||
}
|
||||
};
|
||||
}
|
||||
const contains = function(pathUtils, from, to) {
|
||||
from = pathUtils.normalize(from);
|
||||
to = pathUtils.normalize(to);
|
||||
@@ -37,17 +39,13 @@ const contains = function(pathUtils, from, to) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
npath.contains = (from, to) => contains(npath, from, to);
|
||||
ppath.contains = (from, to) => contains(ppath, from, to);
|
||||
const WINDOWS_PATH_REGEXP = /^([a-zA-Z]:.*)$/;
|
||||
const UNC_WINDOWS_PATH_REGEXP = /^\/\/(\.\/)?(.*)$/;
|
||||
const PORTABLE_PATH_REGEXP = /^\/([a-zA-Z]:.*)$/;
|
||||
const UNC_PORTABLE_PATH_REGEXP = /^\/unc\/(\.dot\/)?(.*)$/;
|
||||
function fromPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
function fromPortablePathWin32(p) {
|
||||
let portablePathMatch, uncPortablePathMatch;
|
||||
if (portablePathMatch = p.match(PORTABLE_PATH_REGEXP))
|
||||
p = portablePathMatch[1];
|
||||
@@ -57,9 +55,7 @@ function fromPortablePath(p) {
|
||||
return p;
|
||||
return p.replace(/\//g, `\\`);
|
||||
}
|
||||
function toPortablePath(p) {
|
||||
if (process.platform !== `win32`)
|
||||
return p;
|
||||
function toPortablePathWin32(p) {
|
||||
p = p.replace(/\\/g, `/`);
|
||||
let windowsPathMatch, uncWindowsPathMatch;
|
||||
if (windowsPathMatch = p.match(WINDOWS_PATH_REGEXP))
|
||||
@@ -68,6 +64,10 @@ function toPortablePath(p) {
|
||||
p = `/unc/${uncWindowsPathMatch[1] ? `.dot/` : ``}${uncWindowsPathMatch[2]}`;
|
||||
return p;
|
||||
}
|
||||
const toPortablePath = process.platform === `win32` ? toPortablePathWin32 : (p) => p;
|
||||
const fromPortablePath = process.platform === `win32` ? fromPortablePathWin32 : (p) => p;
|
||||
npath.fromPortablePath = fromPortablePath;
|
||||
npath.toPortablePath = toPortablePath;
|
||||
function convertPath(targetPathUtils, sourcePath) {
|
||||
return targetPathUtils === npath ? fromPortablePath(sourcePath) : toPortablePath(sourcePath);
|
||||
}
|
||||
@@ -902,6 +902,12 @@ class ProxiedFS extends FakeFS {
|
||||
}
|
||||
}
|
||||
|
||||
function direntToPortable(dirent) {
|
||||
const portableDirent = dirent;
|
||||
if (typeof dirent.path === `string`)
|
||||
portableDirent.path = npath.toPortablePath(dirent.path);
|
||||
return portableDirent;
|
||||
}
|
||||
class NodeFS extends BasePortableFakeFS {
|
||||
constructor(realFs = fs) {
|
||||
super();
|
||||
@@ -1228,15 +1234,31 @@ class NodeFS extends BasePortableFakeFS {
|
||||
async readdirPromise(p, opts) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
if (opts) {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
|
||||
if (opts.recursive && process.platform === `win32`) {
|
||||
if (opts.withFileTypes) {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(direntToPortable)), reject));
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback((results) => resolve(results.map(npath.toPortablePath)), reject));
|
||||
}
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), opts, this.makeCallback(resolve, reject));
|
||||
}
|
||||
} else {
|
||||
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback((value) => resolve(value), reject));
|
||||
this.realFs.readdir(npath.fromPortablePath(p), this.makeCallback(resolve, reject));
|
||||
}
|
||||
});
|
||||
}
|
||||
readdirSync(p, opts) {
|
||||
if (opts) {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
|
||||
if (opts.recursive && process.platform === `win32`) {
|
||||
if (opts.withFileTypes) {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(direntToPortable);
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts).map(npath.toPortablePath);
|
||||
}
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p), opts);
|
||||
}
|
||||
} else {
|
||||
return this.realFs.readdirSync(npath.fromPortablePath(p));
|
||||
}
|
||||
@@ -1372,10 +1394,8 @@ class VirtualFS extends ProxiedFS {
|
||||
|
||||
const [major, minor] = process.versions.node.split(`.`).map((value) => parseInt(value, 10));
|
||||
const WATCH_MODE_MESSAGE_USES_ARRAYS = major > 19 || major === 19 && minor >= 2 || major === 18 && minor >= 13;
|
||||
const HAS_LAZY_LOADED_TRANSLATORS = major > 19 || major === 19 && minor >= 3;
|
||||
const HAS_LAZY_LOADED_TRANSLATORS = major === 20 && minor < 6 || major === 19 && minor >= 3;
|
||||
|
||||
const builtinModules = new Set(Module.builtinModules || Object.keys(process.binding(`natives`)));
|
||||
const isBuiltinModule = (request) => request.startsWith(`node:`) || builtinModules.has(request);
|
||||
function readPackageScope(checkPath) {
|
||||
const rootSeparatorIndex = checkPath.indexOf(npath.sep);
|
||||
let separatorIndex;
|
||||
@@ -1963,7 +1983,7 @@ async function resolvePrivateRequest(specifier, issuer, context, nextResolve) {
|
||||
}
|
||||
async function resolve$1(originalSpecifier, context, nextResolve) {
|
||||
const { findPnpApi } = moduleExports;
|
||||
if (!findPnpApi || isBuiltinModule(originalSpecifier))
|
||||
if (!findPnpApi || isBuiltin(originalSpecifier))
|
||||
return nextResolve(originalSpecifier, context, nextResolve);
|
||||
let specifier = originalSpecifier;
|
||||
const url = tryParseURL(specifier, isRelativeRegexp.test(specifier) ? context.parentURL : void 0);
|
||||
@@ -2022,31 +2042,46 @@ async function resolve$1(originalSpecifier, context, nextResolve) {
|
||||
|
||||
if (!HAS_LAZY_LOADED_TRANSLATORS) {
|
||||
const binding = process.binding(`fs`);
|
||||
const originalfstat = binding.fstat;
|
||||
const ZIP_MASK = 4278190080;
|
||||
const ZIP_MAGIC = 704643072;
|
||||
binding.fstat = function(...args) {
|
||||
const [fd, useBigint, req] = args;
|
||||
if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === void 0) {
|
||||
const originalReadFile = binding.readFileUtf8 || binding.readFileSync;
|
||||
if (originalReadFile) {
|
||||
binding[originalReadFile.name] = function(...args) {
|
||||
try {
|
||||
const stats = fs.fstatSync(fd);
|
||||
return new Float64Array([
|
||||
stats.dev,
|
||||
stats.mode,
|
||||
stats.nlink,
|
||||
stats.uid,
|
||||
stats.gid,
|
||||
stats.rdev,
|
||||
stats.blksize,
|
||||
stats.ino,
|
||||
stats.size,
|
||||
stats.blocks
|
||||
]);
|
||||
return fs.readFileSync(args[0], {
|
||||
encoding: `utf8`,
|
||||
flag: args[1]
|
||||
});
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return originalfstat.apply(this, args);
|
||||
};
|
||||
return originalReadFile.apply(this, args);
|
||||
};
|
||||
} else {
|
||||
const binding2 = process.binding(`fs`);
|
||||
const originalfstat = binding2.fstat;
|
||||
const ZIP_MASK = 4278190080;
|
||||
const ZIP_MAGIC = 704643072;
|
||||
binding2.fstat = function(...args) {
|
||||
const [fd, useBigint, req] = args;
|
||||
if ((fd & ZIP_MASK) === ZIP_MAGIC && useBigint === false && req === void 0) {
|
||||
try {
|
||||
const stats = fs.fstatSync(fd);
|
||||
return new Float64Array([
|
||||
stats.dev,
|
||||
stats.mode,
|
||||
stats.nlink,
|
||||
stats.uid,
|
||||
stats.gid,
|
||||
stats.rdev,
|
||||
stats.blksize,
|
||||
stats.ino,
|
||||
stats.size,
|
||||
stats.blocks
|
||||
]);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return originalfstat.apply(this, args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const resolve = resolve$1;
|
||||
|
||||
891
.yarn/releases/yarn-4.0.0-rc.51.cjs
vendored
891
.yarn/releases/yarn-4.0.0-rc.51.cjs
vendored
File diff suppressed because one or more lines are too long
893
.yarn/releases/yarn-4.0.2.cjs
vendored
Executable file
893
.yarn/releases/yarn-4.0.2.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -2,4 +2,4 @@ compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.0.0-rc.51.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.0.2.cjs
|
||||
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
- standardnotes_self_hosted
|
||||
|
||||
localstack:
|
||||
image: localstack/localstack:1.4
|
||||
image: localstack/localstack:3.0
|
||||
container_name: localstack-ci
|
||||
expose:
|
||||
- 4566
|
||||
|
||||
@@ -14,7 +14,7 @@ services:
|
||||
- standardnotes_self_hosted
|
||||
|
||||
localstack:
|
||||
image: localstack/localstack:1.3
|
||||
image: localstack/localstack:3.0
|
||||
container_name: localstack_self_hosted
|
||||
expose:
|
||||
- 4566
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"packageManager": "yarn@4.0.0-rc.51",
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"dependenciesMeta": {
|
||||
"grpc-tools@1.12.4": {
|
||||
"unplugged": true
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.34.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.2...@standardnotes/analytics@2.34.3) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.34.2](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.1...@standardnotes/analytics@2.34.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.34.1](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.34.0...@standardnotes/analytics@2.34.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [2.34.0](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.4...@standardnotes/analytics@2.34.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [2.33.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.3...@standardnotes/analytics@2.33.4) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
## [2.33.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.2...@standardnotes/analytics@2.33.3) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/analytics
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/analytics",
|
||||
"version": "2.33.3",
|
||||
"version": "2.34.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -10,7 +10,13 @@
|
||||
"author": "Standard Notes",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/analytics"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,44 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.87.4](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.3...@standardnotes/api-gateway@1.87.4) (2023-12-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** home server home page ([#949](https://github.com/standardnotes/server/issues/949)) ([a82192d](https://github.com/standardnotes/server/commit/a82192db42dfbb3eea4ac6af40ef2b3d6126e5a3))
|
||||
|
||||
## [1.87.3](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.2...@standardnotes/api-gateway@1.87.3) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api-gateway:** add session to response locals from web socket middleware ([4cc647a](https://github.com/standardnotes/server/commit/4cc647ac07b2471d6616a913bcdca431c506fd0e))
|
||||
|
||||
## [1.87.2](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.1...@standardnotes/api-gateway@1.87.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.87.1](https://github.com/standardnotes/server/compare/@standardnotes/api-gateway@1.87.0...@standardnotes/api-gateway@1.87.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.87.0](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.6...@standardnotes/api-gateway@1.87.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/api-gateway/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.86.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.5...@standardnotes/api-gateway@1.86.6) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
## [1.86.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.4...@standardnotes/api-gateway@1.86.5) (2023-11-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error handling on gRPC ([#937](https://github.com/standardnotes/api-gateway/issues/937)) ([8f23c8a](https://github.com/standardnotes/api-gateway/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
|
||||
|
||||
## [1.86.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.3...@standardnotes/api-gateway@1.86.4) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api-gateway
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api-gateway",
|
||||
"version": "1.86.4",
|
||||
"version": "1.87.4",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -11,11 +11,16 @@
|
||||
"dist/src/**/*.js",
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"repository": "git@github.com:standardnotes/api-gateway.git",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/api-gateway"
|
||||
},
|
||||
"author": "Karol Sójko <karol@standardnotes.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { Request, Response } from 'express'
|
||||
import { BaseHttpController, all, controller, results } from 'inversify-express-utils'
|
||||
|
||||
@controller('')
|
||||
export class FallbackController extends BaseHttpController {
|
||||
@all('*')
|
||||
public async fallback(): Promise<results.NotFoundResult> {
|
||||
public async fallback(request: Request, response: Response): Promise<void | results.NotFoundResult> {
|
||||
if (request.path === '/' && request.method === 'GET') {
|
||||
response.send(
|
||||
'<!DOCTYPE html><html lang="en"><head><meta name="robots" content="noindex"></head><body>Your home server is up and running! Enter the URL of this page into Standard Notes when registering or signing in to begin using your home server.</body></html>',
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return this.notFound()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ export class WebSocketAuthMiddleware extends BaseMiddleware {
|
||||
const decodedToken = <CrossServiceTokenData>verify(crossServiceToken, this.jwtSecret, { algorithms: ['HS256'] })
|
||||
|
||||
response.locals.user = decodedToken.user
|
||||
response.locals.session = decodedToken.session
|
||||
response.locals.roles = decodedToken.roles
|
||||
} catch (error) {
|
||||
const errorMessage = (error as AxiosError).isAxiosError
|
||||
|
||||
@@ -88,7 +88,10 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
|
||||
endpoint: string,
|
||||
payload?: Record<string, unknown> | string,
|
||||
): Promise<void> {
|
||||
if (endpoint === 'items/sync') {
|
||||
const requestIsUsingLatestApiVersions =
|
||||
payload !== undefined && typeof payload !== 'string' && 'api' in payload && payload.api === '20200115'
|
||||
|
||||
if (requestIsUsingLatestApiVersions && endpoint === 'items/sync') {
|
||||
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
|
||||
|
||||
response.status(result.status).send({
|
||||
|
||||
@@ -3,6 +3,39 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.174.3](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.2...@standardnotes/auth-server@1.174.3) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** pass session uuid to web sockets controller ([edb0a76](https://github.com/standardnotes/server/commit/edb0a768d0b8c31298b31372e6cec16d003fd28d))
|
||||
* pass session uuid to websockets token ([bcd1d83](https://github.com/standardnotes/server/commit/bcd1d830e6125fc5f8cc1312e581284221aaac8f))
|
||||
|
||||
## [1.174.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.1...@standardnotes/auth-server@1.174.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.174.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.174.0...@standardnotes/auth-server@1.174.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.174.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.2...@standardnotes/auth-server@1.174.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.173.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.1...@standardnotes/auth-server@1.173.2) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/auth-server
|
||||
|
||||
## [1.173.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.0...@standardnotes/auth-server@1.173.1) (2023-11-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error handling on gRPC ([#937](https://github.com/standardnotes/server/issues/937)) ([8f23c8a](https://github.com/standardnotes/server/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
|
||||
|
||||
# [1.173.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.172.2...@standardnotes/auth-server@1.173.0) (2023-11-22)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/auth-server",
|
||||
"version": "1.173.0",
|
||||
"version": "1.174.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -10,7 +10,13 @@
|
||||
"author": "Karol Sójko <karol@standardnotes.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/auth"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
|
||||
@@ -1195,6 +1195,7 @@ export class ContainerConfigLoader {
|
||||
container.get<GetRegularSubscriptionForUser>(TYPES.Auth_GetRegularSubscriptionForUser),
|
||||
container.get<GetSubscriptionSetting>(TYPES.Auth_GetSubscriptionSetting),
|
||||
container.get<SharedVaultUserRepositoryInterface>(TYPES.Auth_SharedVaultUserRepository),
|
||||
container.get<GetActiveSessionsForUser>(TYPES.Auth_GetActiveSessionsForUser),
|
||||
),
|
||||
)
|
||||
container.bind<ProcessUserRequest>(TYPES.Auth_ProcessUserRequest).to(ProcessUserRequest)
|
||||
|
||||
@@ -22,6 +22,7 @@ import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/
|
||||
import { UserSubscription } from '../../Subscription/UserSubscription'
|
||||
import { SubscriptionSetting } from '../../Setting/SubscriptionSetting'
|
||||
import { EncryptionVersion } from '../../Encryption/EncryptionVersion'
|
||||
import { GetActiveSessionsForUser } from '../GetActiveSessionsForUser'
|
||||
|
||||
describe('CreateCrossServiceToken', () => {
|
||||
let userProjector: ProjectorInterface<User>
|
||||
@@ -32,6 +33,7 @@ describe('CreateCrossServiceToken', () => {
|
||||
let getRegularSubscription: GetRegularSubscriptionForUser
|
||||
let getSubscriptionSetting: GetSubscriptionSetting
|
||||
let sharedVaultUserRepository: SharedVaultUserRepositoryInterface
|
||||
let getActiveSessionsForUser: GetActiveSessionsForUser
|
||||
const jwtTTL = 60
|
||||
|
||||
let session: Session
|
||||
@@ -49,11 +51,15 @@ describe('CreateCrossServiceToken', () => {
|
||||
getRegularSubscription,
|
||||
getSubscriptionSetting,
|
||||
sharedVaultUserRepository,
|
||||
getActiveSessionsForUser,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
session = {} as jest.Mocked<Session>
|
||||
|
||||
getActiveSessionsForUser = {} as jest.Mocked<GetActiveSessionsForUser>
|
||||
getActiveSessionsForUser.execute = jest.fn().mockReturnValue({ sessions: [session] })
|
||||
|
||||
user = {
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
email: 'test@test.te',
|
||||
@@ -195,6 +201,69 @@ describe('CreateCrossServiceToken', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should create a cross service token for a user and a specific session', async () => {
|
||||
await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
{
|
||||
name: 'role1',
|
||||
uuid: '1-3-4',
|
||||
},
|
||||
],
|
||||
belongs_to_shared_vaults: [
|
||||
{
|
||||
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: 'read',
|
||||
},
|
||||
],
|
||||
session: {
|
||||
test: 'test',
|
||||
},
|
||||
user: {
|
||||
email: 'test@test.te',
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
|
||||
it('should create a cross service token for a user and specific session if the session is missing', async () => {
|
||||
getActiveSessionsForUser.execute = jest.fn().mockReturnValue({ sessions: [] })
|
||||
|
||||
await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith(
|
||||
{
|
||||
roles: [
|
||||
{
|
||||
name: 'role1',
|
||||
uuid: '1-3-4',
|
||||
},
|
||||
],
|
||||
belongs_to_shared_vaults: [
|
||||
{
|
||||
shared_vault_uuid: '00000000-0000-0000-0000-000000000000',
|
||||
permission: 'read',
|
||||
},
|
||||
],
|
||||
user: {
|
||||
email: 'test@test.te',
|
||||
uuid: '00000000-0000-0000-0000-000000000000',
|
||||
},
|
||||
},
|
||||
60,
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an error if user does not exist', async () => {
|
||||
userRepository.findOneByUuid = jest.fn().mockReturnValue(null)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import { CreateCrossServiceTokenDTO } from './CreateCrossServiceTokenDTO'
|
||||
import { SharedVaultUserRepositoryInterface } from '../../SharedVault/SharedVaultUserRepositoryInterface'
|
||||
import { GetSubscriptionSetting } from '../GetSubscriptionSetting/GetSubscriptionSetting'
|
||||
import { GetRegularSubscriptionForUser } from '../GetRegularSubscriptionForUser/GetRegularSubscriptionForUser'
|
||||
import { GetActiveSessionsForUser } from '../GetActiveSessionsForUser'
|
||||
|
||||
export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
||||
constructor(
|
||||
@@ -23,6 +24,7 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
||||
private getRegularSubscription: GetRegularSubscriptionForUser,
|
||||
private getSubscriptionSettingUseCase: GetSubscriptionSetting,
|
||||
private sharedVaultUserRepository: SharedVaultUserRepositoryInterface,
|
||||
private getActiveSessions: GetActiveSessionsForUser,
|
||||
) {}
|
||||
|
||||
async execute(dto: CreateCrossServiceTokenDTO): Promise<Result<string>> {
|
||||
@@ -84,6 +86,14 @@ export class CreateCrossServiceToken implements UseCaseInterface<string> {
|
||||
|
||||
if (dto.session !== undefined) {
|
||||
authTokenData.session = this.projectSession(dto.session)
|
||||
} else if (dto.sessionUuid !== undefined) {
|
||||
const activeSessionsResponse = await this.getActiveSessions.execute({
|
||||
userUuid: user.uuid,
|
||||
sessionUuid: dto.sessionUuid,
|
||||
})
|
||||
if (activeSessionsResponse.sessions.length) {
|
||||
authTokenData.session = this.projectSession(activeSessionsResponse.sessions[0])
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok(this.tokenEncoder.encodeExpirableToken(authTokenData, this.jwtTTL))
|
||||
|
||||
@@ -10,5 +10,6 @@ export type CreateCrossServiceTokenDTO = Either<
|
||||
},
|
||||
{
|
||||
userUuid: string
|
||||
sessionUuid?: string
|
||||
}
|
||||
>
|
||||
|
||||
@@ -65,4 +65,10 @@ describe('GetActiveSessionsForUser', () => {
|
||||
|
||||
expect(sessionRepository.findAllByRefreshExpirationAndUserUuid).toHaveBeenCalledWith('1-2-3')
|
||||
})
|
||||
|
||||
it('should get a single session for a user', async () => {
|
||||
expect(await createUseCase().execute({ userUuid: '1-2-3', sessionUuid: '2-3-4' })).toEqual({
|
||||
sessions: [session2],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import { SessionRepositoryInterface } from '../Session/SessionRepositoryInterfac
|
||||
import { GetActiveSessionsForUserDTO } from './GetActiveSessionsForUserDTO'
|
||||
import { GetActiveSessionsForUserResponse } from './GetActiveSessionsForUserResponse'
|
||||
import { UseCaseInterface } from './UseCaseInterface'
|
||||
import { Session } from '../Session/Session'
|
||||
|
||||
@injectable()
|
||||
export class GetActiveSessionsForUser implements UseCaseInterface {
|
||||
@@ -18,13 +19,26 @@ export class GetActiveSessionsForUser implements UseCaseInterface {
|
||||
const ephemeralSessions = await this.ephemeralSessionRepository.findAllByUserUuid(dto.userUuid)
|
||||
const sessions = await this.sessionRepository.findAllByRefreshExpirationAndUserUuid(dto.userUuid)
|
||||
|
||||
return {
|
||||
sessions: sessions.concat(ephemeralSessions).sort((a, b) => {
|
||||
const dateA = a.refreshExpiration instanceof Date ? a.refreshExpiration : new Date(a.refreshExpiration)
|
||||
const dateB = b.refreshExpiration instanceof Date ? b.refreshExpiration : new Date(b.refreshExpiration)
|
||||
const activeSessions = sessions.concat(ephemeralSessions).sort((a, b) => {
|
||||
const dateA = a.refreshExpiration instanceof Date ? a.refreshExpiration : new Date(a.refreshExpiration)
|
||||
const dateB = b.refreshExpiration instanceof Date ? b.refreshExpiration : new Date(b.refreshExpiration)
|
||||
|
||||
return dateB.getTime() - dateA.getTime()
|
||||
}),
|
||||
return dateB.getTime() - dateA.getTime()
|
||||
})
|
||||
|
||||
if (dto.sessionUuid) {
|
||||
let sessions: Session[] = []
|
||||
const session = activeSessions.find((session) => session.uuid === dto.sessionUuid)
|
||||
if (session) {
|
||||
sessions = [session]
|
||||
}
|
||||
return {
|
||||
sessions,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sessions: activeSessions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export type GetActiveSessionsForUserDTO = {
|
||||
userUuid: string
|
||||
sessionUuid?: string
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export class BaseWebSocketsController extends BaseHttpController {
|
||||
|
||||
const resultOrError = await this.createCrossServiceToken.execute({
|
||||
userUuid: token.userUuid,
|
||||
sessionUuid: token.sessionUuid,
|
||||
})
|
||||
if (resultOrError.isFailed()) {
|
||||
return this.json(
|
||||
|
||||
@@ -19,62 +19,75 @@ export class SessionsServer implements ISessionsServer {
|
||||
call: grpc.ServerUnaryCall<AuthorizationHeader, SessionValidationResponse>,
|
||||
callback: grpc.sendUnaryData<SessionValidationResponse>,
|
||||
): Promise<void> {
|
||||
this.logger.debug('[SessionsServer] Validating session via gRPC')
|
||||
try {
|
||||
this.logger.debug('[SessionsServer] Validating session via gRPC')
|
||||
|
||||
const authenticateRequestResponse = await this.authenticateRequest.execute({
|
||||
authorizationHeader: call.request.getBearerToken(),
|
||||
})
|
||||
const authenticateRequestResponse = await this.authenticateRequest.execute({
|
||||
authorizationHeader: call.request.getBearerToken(),
|
||||
})
|
||||
|
||||
if (!authenticateRequestResponse.success) {
|
||||
const metadata = new grpc.Metadata()
|
||||
metadata.set('x-auth-error-message', authenticateRequestResponse.errorMessage as string)
|
||||
metadata.set('x-auth-error-tag', authenticateRequestResponse.errorTag as string)
|
||||
metadata.set('x-auth-error-response-code', authenticateRequestResponse.responseCode.toString())
|
||||
return callback(
|
||||
if (!authenticateRequestResponse.success) {
|
||||
const metadata = new grpc.Metadata()
|
||||
metadata.set('x-auth-error-message', authenticateRequestResponse.errorMessage as string)
|
||||
metadata.set('x-auth-error-tag', authenticateRequestResponse.errorTag as string)
|
||||
metadata.set('x-auth-error-response-code', authenticateRequestResponse.responseCode.toString())
|
||||
return callback(
|
||||
{
|
||||
code: Status.PERMISSION_DENIED,
|
||||
message: authenticateRequestResponse.errorMessage,
|
||||
name: authenticateRequestResponse.errorTag,
|
||||
metadata,
|
||||
},
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
const user = authenticateRequestResponse.user as User
|
||||
|
||||
const sharedVaultOwnerMetadata = call.metadata.get('x-shared-vault-owner-context')
|
||||
let sharedVaultOwnerContext = undefined
|
||||
if (sharedVaultOwnerMetadata.length > 0 && sharedVaultOwnerMetadata[0].length > 0) {
|
||||
sharedVaultOwnerContext = sharedVaultOwnerMetadata[0].toString()
|
||||
}
|
||||
|
||||
const resultOrError = await this.createCrossServiceToken.execute({
|
||||
user,
|
||||
session: authenticateRequestResponse.session,
|
||||
sharedVaultOwnerContext,
|
||||
})
|
||||
if (resultOrError.isFailed()) {
|
||||
const metadata = new grpc.Metadata()
|
||||
metadata.set('x-auth-error-message', resultOrError.getError())
|
||||
metadata.set('x-auth-error-response-code', '400')
|
||||
|
||||
return callback(
|
||||
{
|
||||
code: Status.INVALID_ARGUMENT,
|
||||
message: resultOrError.getError(),
|
||||
name: 'INVALID_ARGUMENT',
|
||||
metadata,
|
||||
},
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
const response = new SessionValidationResponse()
|
||||
response.setCrossServiceToken(resultOrError.getValue())
|
||||
|
||||
this.logger.debug('[SessionsServer] Session validated via gRPC')
|
||||
|
||||
callback(null, response)
|
||||
} catch (error) {
|
||||
this.logger.error(`[SessionsServer] Error validating session via gRPC: ${(error as Error).message}`)
|
||||
|
||||
callback(
|
||||
{
|
||||
code: Status.PERMISSION_DENIED,
|
||||
message: authenticateRequestResponse.errorMessage,
|
||||
name: authenticateRequestResponse.errorTag,
|
||||
metadata,
|
||||
code: Status.UNKNOWN,
|
||||
message: 'An error occurred while validating session',
|
||||
name: 'UNKNOWN',
|
||||
},
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
const user = authenticateRequestResponse.user as User
|
||||
|
||||
const sharedVaultOwnerMetadata = call.metadata.get('x-shared-vault-owner-context')
|
||||
let sharedVaultOwnerContext = undefined
|
||||
if (sharedVaultOwnerMetadata.length > 0 && sharedVaultOwnerMetadata[0].length > 0) {
|
||||
sharedVaultOwnerContext = sharedVaultOwnerMetadata[0].toString()
|
||||
}
|
||||
|
||||
const resultOrError = await this.createCrossServiceToken.execute({
|
||||
user,
|
||||
session: authenticateRequestResponse.session,
|
||||
sharedVaultOwnerContext,
|
||||
})
|
||||
if (resultOrError.isFailed()) {
|
||||
const metadata = new grpc.Metadata()
|
||||
metadata.set('x-auth-error-message', resultOrError.getError())
|
||||
metadata.set('x-auth-error-response-code', '400')
|
||||
|
||||
return callback(
|
||||
{
|
||||
code: Status.INVALID_ARGUMENT,
|
||||
message: resultOrError.getError(),
|
||||
name: 'INVALID_ARGUMENT',
|
||||
metadata,
|
||||
},
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
const response = new SessionValidationResponse()
|
||||
response.setCrossServiceToken(resultOrError.getValue())
|
||||
|
||||
this.logger.debug('[SessionsServer] Session validated via gRPC')
|
||||
|
||||
callback(null, response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.52.1](https://github.com/standardnotes/server/compare/@standardnotes/common@1.52.0...@standardnotes/common@1.52.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.52.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.51.0...@standardnotes/common@1.52.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
# [1.51.0](https://github.com/standardnotes/server/compare/@standardnotes/common@1.50.4...@standardnotes/common@1.51.0) (2023-09-26)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/common",
|
||||
"version": "1.51.0",
|
||||
"version": "1.52.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -13,7 +13,13 @@
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/common"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.41.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.41.0...@standardnotes/domain-core@1.41.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.41.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.40.0...@standardnotes/domain-core@1.41.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
# [1.40.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-core@1.39.0...@standardnotes/domain-core@1.40.0) (2023-10-26)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-core",
|
||||
"version": "1.40.0",
|
||||
"version": "1.41.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -12,7 +12,13 @@
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/domain-core"
|
||||
},
|
||||
"author": "Standard Notes",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.22.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.2...@standardnotes/domain-events-infra@1.22.3) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.22.2](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.1...@standardnotes/domain-events-infra@1.22.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.22.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.22.0...@standardnotes/domain-events-infra@1.22.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.4...@standardnotes/domain-events-infra@1.22.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.21.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.3...@standardnotes/domain-events-infra@1.21.4) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
## [1.21.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.2...@standardnotes/domain-events-infra@1.21.3) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events-infra
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events-infra",
|
||||
"version": "1.21.3",
|
||||
"version": "1.22.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -12,7 +12,13 @@
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/domain-events-infra"
|
||||
},
|
||||
"author": "Standard Notes",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.138.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.138.0...@standardnotes/domain-events@2.138.1) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/domain-events
|
||||
|
||||
# [2.138.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.137.1...@standardnotes/domain-events@2.138.0) (2023-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* send event to client upon items change on server ([#941](https://github.com/standardnotes/server/issues/941)) ([69b404f](https://github.com/standardnotes/server/commit/69b404f5d45f32530ebadbdbbec01d4e335dbbe9))
|
||||
|
||||
## [2.137.1](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.137.0...@standardnotes/domain-events@2.137.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [2.137.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.136.0...@standardnotes/domain-events@2.137.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
# [2.136.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.135.0...@standardnotes/domain-events@2.136.0) (2023-11-23)
|
||||
|
||||
### Features
|
||||
|
||||
* **domain-events:** add email campaign send out requested event ([45bd009](https://github.com/standardnotes/server/commit/45bd00919c0062ac4bddd3f858f3ab89166f4e69))
|
||||
|
||||
# [2.135.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.134.2...@standardnotes/domain-events@2.135.0) (2023-11-22)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/domain-events",
|
||||
"version": "2.135.0",
|
||||
"version": "2.138.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -12,7 +12,13 @@
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/domain-events"
|
||||
},
|
||||
"author": "Standard Notes",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
import { EmailCampaignSendOutRequestedEventPayload } from './EmailCampaignSendOutRequestedEventPayload'
|
||||
|
||||
export interface EmailCampaignSendOutRequestedEvent extends DomainEventInterface {
|
||||
type: 'EMAIL_CAMPAIGN_SEND_OUT_REQUESTED'
|
||||
payload: EmailCampaignSendOutRequestedEventPayload
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface EmailCampaignSendOutRequestedEventPayload {
|
||||
limit: number
|
||||
page: number
|
||||
campaignFileName: string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { DomainEventInterface } from './DomainEventInterface'
|
||||
import { ItemsChangedOnServerEventPayload } from './ItemsChangedOnServerEventPayload'
|
||||
|
||||
export interface ItemsChangedOnServerEvent extends DomainEventInterface {
|
||||
type: 'ITEMS_CHANGED_ON_SERVER'
|
||||
payload: ItemsChangedOnServerEventPayload
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface ItemsChangedOnServerEventPayload {
|
||||
userUuid: string
|
||||
sessionUuid: string
|
||||
timestamp: number
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface WebSocketMessageRequestedEventPayload {
|
||||
userUuid: string
|
||||
message: string
|
||||
originatingSessionUuid?: string
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ export * from './Event/EmailBackupRequestedEvent'
|
||||
export * from './Event/EmailBackupRequestedEventPayload'
|
||||
export * from './Event/EmailBouncedEvent'
|
||||
export * from './Event/EmailBouncedEventPayload'
|
||||
export * from './Event/EmailCampaignSendOutRequestedEvent'
|
||||
export * from './Event/EmailCampaignSendOutRequestedEventPayload'
|
||||
export * from './Event/EmailRequestedEvent'
|
||||
export * from './Event/EmailRequestedEventPayload'
|
||||
export * from './Event/EmailSubscriptionUnsubscribedEvent'
|
||||
@@ -38,6 +40,8 @@ export * from './Event/ItemRemovedFromSharedVaultEvent'
|
||||
export * from './Event/ItemRemovedFromSharedVaultEventPayload'
|
||||
export * from './Event/ItemRevisionCreationRequestedEvent'
|
||||
export * from './Event/ItemRevisionCreationRequestedEventPayload'
|
||||
export * from './Event/ItemsChangedOnServerEvent'
|
||||
export * from './Event/ItemsChangedOnServerEventPayload'
|
||||
export * from './Event/ListedAccountCreatedEvent'
|
||||
export * from './Event/ListedAccountCreatedEventPayload'
|
||||
export * from './Event/ListedAccountDeletedEvent'
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.36.3](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.36.2...@standardnotes/files-server@1.36.3) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.36.2](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.36.1...@standardnotes/files-server@1.36.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.36.1](https://github.com/standardnotes/server/compare/@standardnotes/files-server@1.36.0...@standardnotes/files-server@1.36.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.36.0](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.35.2...@standardnotes/files-server@1.36.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/files/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.35.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.35.1...@standardnotes/files-server@1.35.2) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
## [1.35.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.35.0...@standardnotes/files-server@1.35.1) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files-server
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
{
|
||||
"name": "@standardnotes/files-server",
|
||||
"version": "1.35.1",
|
||||
"version": "1.36.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
"description": "Standard Notes Files Server",
|
||||
"main": "dist/src/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
"repository": "git@github.com:standardnotes/files.git",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/files"
|
||||
},
|
||||
"authors": [
|
||||
"Karol Sójko <karol@standardnotes.com>"
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.3.1](https://github.com/standardnotes/server/compare/@standardnotes/grpc@1.3.0...@standardnotes/grpc@1.3.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.3.0](https://github.com/standardnotes/server/compare/@standardnotes/grpc@1.2.0...@standardnotes/grpc@1.3.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
# [1.2.0](https://github.com/standardnotes/server/compare/@standardnotes/grpc@1.1.0...@standardnotes/grpc@1.2.0) (2023-11-20)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/grpc",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -13,7 +13,13 @@
|
||||
"lib/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/grpc"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,50 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.22.7](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.6...@standardnotes/home-server@1.22.7) (2023-12-01)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.6](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.5...@standardnotes/home-server@1.22.6) (2023-11-30)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.5](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.4...@standardnotes/home-server@1.22.5) (2023-11-29)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.4](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.3...@standardnotes/home-server@1.22.4) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.3](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.2...@standardnotes/home-server@1.22.3) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.2](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.1...@standardnotes/home-server@1.22.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.22.1](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.0...@standardnotes/home-server@1.22.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.21.11...@standardnotes/home-server@1.22.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.21.11](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.21.10...@standardnotes/home-server@1.21.11) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.21.10](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.21.9...@standardnotes/home-server@1.21.10) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
## [1.21.9](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.21.8...@standardnotes/home-server@1.21.9) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/home-server
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
{
|
||||
"name": "@standardnotes/home-server",
|
||||
"version": "1.21.9",
|
||||
"version": "1.22.7",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
"description": "Standard Notes Home Server",
|
||||
"main": "dist/src/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
"repository": "git@github.com:standardnotes/server.git",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/home-server"
|
||||
},
|
||||
"author": "Karol Sójko <karol@standardnotes.com>",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.8.1](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.8.0...@standardnotes/predicates@1.8.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.8.0](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.7.0...@standardnotes/predicates@1.8.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
# [1.7.0](https://github.com/standardnotes/server/compare/@standardnotes/predicates@1.6.11...@standardnotes/predicates@1.7.0) (2023-09-26)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/predicates",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -13,7 +13,13 @@
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/predicates"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.51.3](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.2...@standardnotes/revisions-server@1.51.3) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.51.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.1...@standardnotes/revisions-server@1.51.2) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.51.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.51.0...@standardnotes/revisions-server@1.51.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.51.0](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.50.2...@standardnotes/revisions-server@1.51.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.50.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.50.1...@standardnotes/revisions-server@1.50.2) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
## [1.50.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.50.0...@standardnotes/revisions-server@1.50.1) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/revisions-server
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
{
|
||||
"name": "@standardnotes/revisions-server",
|
||||
"version": "1.50.1",
|
||||
"version": "1.51.3",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
"description": "Revisions Server",
|
||||
"main": "dist/src/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
"repository": "git@github.com:standardnotes/server.git",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/revisions"
|
||||
},
|
||||
"author": "Karol Sójko <karol@standardnotes.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
|
||||
@@ -3,6 +3,28 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.27.8](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.7...@standardnotes/scheduler-server@1.27.8) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.27.7](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.6...@standardnotes/scheduler-server@1.27.7) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.27.6](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.5...@standardnotes/scheduler-server@1.27.6) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
## [1.27.5](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.4...@standardnotes/scheduler-server@1.27.5) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.27.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.3...@standardnotes/scheduler-server@1.27.4) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
## [1.27.3](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.2...@standardnotes/scheduler-server@1.27.3) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/scheduler-server
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"name": "@standardnotes/scheduler-server",
|
||||
"version": "1.27.3",
|
||||
"version": "1.27.8",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/scheduler"
|
||||
},
|
||||
"description": "Scheduler Server",
|
||||
"main": "dist/src/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,24 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.17.2](https://github.com/standardnotes/server/compare/@standardnotes/security@1.17.1...@standardnotes/security@1.17.2) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass session uuid to websockets token ([bcd1d83](https://github.com/standardnotes/server/commit/bcd1d830e6125fc5f8cc1312e581284221aaac8f))
|
||||
|
||||
## [1.17.1](https://github.com/standardnotes/server/compare/@standardnotes/security@1.17.0...@standardnotes/security@1.17.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.17.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.16.0...@standardnotes/security@1.17.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/security@1.15.0...@standardnotes/security@1.16.0) (2023-10-19)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/security",
|
||||
"version": "1.16.0",
|
||||
"version": "1.17.2",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -13,7 +13,13 @@
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/security"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export type WebSocketConnectionTokenData = {
|
||||
userUuid: string
|
||||
sessionUuid: string
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.23.1](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.23.0...@standardnotes/settings@1.23.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.23.0](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.22.0...@standardnotes/settings@1.23.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
# [1.22.0](https://github.com/standardnotes/server/compare/@standardnotes/settings@1.21.47...@standardnotes/settings@1.22.0) (2023-10-26)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/settings",
|
||||
"version": "1.22.0",
|
||||
"version": "1.23.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -13,7 +13,13 @@
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/settings"
|
||||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.16.1](https://github.com/standardnotes/server/compare/@standardnotes/sncrypto-node@1.16.0...@standardnotes/sncrypto-node@1.16.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.16.0](https://github.com/standardnotes/server/compare/@standardnotes/sncrypto-node@1.15.6...@standardnotes/sncrypto-node@1.16.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.15.6](https://github.com/standardnotes/server/compare/@standardnotes/sncrypto-node@1.15.5...@standardnotes/sncrypto-node@1.15.6) (2023-09-04)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/sncrypto-node
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/sncrypto-node",
|
||||
"version": "1.15.6",
|
||||
"version": "1.16.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -14,7 +14,13 @@
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/sncrypto-node"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
|
||||
@@ -3,6 +3,67 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.127.5](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.127.4...@standardnotes/syncing-server@1.127.5) (2023-12-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** remove the dice roll on whether to inform the client on items changed ([#950](https://github.com/standardnotes/server/issues/950)) ([589b740](https://github.com/standardnotes/server/commit/589b740f4995d6f68d7fe9a366ce160d80344eac))
|
||||
|
||||
## [1.127.4](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.127.3...@standardnotes/syncing-server@1.127.4) (2023-11-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** increase chances for notifying of items change to 70% ([199ebeb](https://github.com/standardnotes/server/commit/199ebeb4ea644c1e5e72383e5c627b4dcc3268ca))
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "chore: upgrade deps" ([341f69e](https://github.com/standardnotes/server/commit/341f69e301036aa48588957fc930405d0344e6ce))
|
||||
|
||||
## [1.127.3](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.127.2...@standardnotes/syncing-server@1.127.3) (2023-11-29)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** dice roll specs ([9dcd583](https://github.com/standardnotes/server/commit/9dcd583b58c09b96635296996f22581018156485))
|
||||
* **syncing-server:** increase chances for notifying of items change ([097e320](https://github.com/standardnotes/server/commit/097e320490622849cdbc5ca58b03fe635b7dce0b))
|
||||
|
||||
## [1.127.2](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.127.1...@standardnotes/syncing-server@1.127.2) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **syncing-server:** add debug logs about sending items changed event ([2d6a3eb](https://github.com/standardnotes/server/commit/2d6a3ebf45a247b6a6ef2b7a32f685d9e746b0d3))
|
||||
|
||||
## [1.127.1](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.127.0...@standardnotes/syncing-server@1.127.1) (2023-11-28)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
# [1.127.0](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.126.1...@standardnotes/syncing-server@1.127.0) (2023-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* send event to client upon items change on server ([#941](https://github.com/standardnotes/server/issues/941)) ([69b404f](https://github.com/standardnotes/server/commit/69b404f5d45f32530ebadbdbbec01d4e335dbbe9))
|
||||
|
||||
## [1.126.1](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.126.0...@standardnotes/syncing-server@1.126.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.126.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.125.2...@standardnotes/syncing-server@1.126.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/syncing-server-js/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
## [1.125.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.125.1...@standardnotes/syncing-server@1.125.2) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/syncing-server
|
||||
|
||||
## [1.125.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.125.0...@standardnotes/syncing-server@1.125.1) (2023-11-22)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error handling on gRPC ([#937](https://github.com/standardnotes/syncing-server-js/issues/937)) ([8f23c8a](https://github.com/standardnotes/syncing-server-js/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
|
||||
|
||||
# [1.125.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.124.3...@standardnotes/syncing-server@1.125.0) (2023-11-22)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
{
|
||||
"name": "@standardnotes/syncing-server",
|
||||
"version": "1.125.0",
|
||||
"version": "1.127.5",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
"description": "Syncing Server",
|
||||
"main": "dist/src/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
"repository": "git@github.com:standardnotes/syncing-server-js.git",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/syncing-server"
|
||||
},
|
||||
"author": "Karol Sójko <karol@standardnotes.com>",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -fr dist",
|
||||
|
||||
@@ -601,12 +601,14 @@ export class ContainerConfigLoader {
|
||||
.bind<SaveItems>(TYPES.Sync_SaveItems)
|
||||
.toConstantValue(
|
||||
new SaveItems(
|
||||
container.get(TYPES.Sync_ItemSaveValidator),
|
||||
container.get(TYPES.Sync_SQLItemRepository),
|
||||
container.get(TYPES.Sync_Timer),
|
||||
container.get(TYPES.Sync_SaveNewItem),
|
||||
container.get(TYPES.Sync_UpdateExistingItem),
|
||||
container.get(TYPES.Sync_Logger),
|
||||
container.get<ItemSaveValidatorInterface>(TYPES.Sync_ItemSaveValidator),
|
||||
container.get<ItemRepositoryInterface>(TYPES.Sync_SQLItemRepository),
|
||||
container.get<TimerInterface>(TYPES.Sync_Timer),
|
||||
container.get<SaveNewItem>(TYPES.Sync_SaveNewItem),
|
||||
container.get<UpdateExistingItem>(TYPES.Sync_UpdateExistingItem),
|
||||
container.get<SendEventToClient>(TYPES.Sync_SendEventToClient),
|
||||
container.get<DomainEventFactoryInterface>(TYPES.Sync_DomainEventFactory),
|
||||
container.get<Logger>(TYPES.Sync_Logger),
|
||||
),
|
||||
)
|
||||
container
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ItemDumpedEvent,
|
||||
ItemRemovedFromSharedVaultEvent,
|
||||
ItemRevisionCreationRequestedEvent,
|
||||
ItemsChangedOnServerEvent,
|
||||
MessageSentToUserEvent,
|
||||
NotificationAddedForUserEvent,
|
||||
RevisionsCopyRequestedEvent,
|
||||
@@ -23,6 +24,25 @@ import { DomainEventFactoryInterface } from './DomainEventFactoryInterface'
|
||||
export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
constructor(private timer: TimerInterface) {}
|
||||
|
||||
createItemsChangedOnServerEvent(dto: {
|
||||
userUuid: string
|
||||
sessionUuid: string
|
||||
timestamp: number
|
||||
}): ItemsChangedOnServerEvent {
|
||||
return {
|
||||
type: 'ITEMS_CHANGED_ON_SERVER',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
meta: {
|
||||
correlation: {
|
||||
userIdentifier: dto.userUuid,
|
||||
userIdentifierType: 'uuid',
|
||||
},
|
||||
origin: DomainEventService.SyncingServer,
|
||||
},
|
||||
payload: dto,
|
||||
}
|
||||
}
|
||||
|
||||
createAccountDeletionVerificationPassedEvent(dto: {
|
||||
userUuid: string
|
||||
email: string
|
||||
@@ -207,7 +227,11 @@ export class DomainEventFactory implements DomainEventFactoryInterface {
|
||||
}
|
||||
}
|
||||
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent {
|
||||
createWebSocketMessageRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
message: string
|
||||
originatingSessionUuid?: string
|
||||
}): WebSocketMessageRequestedEvent {
|
||||
return {
|
||||
type: 'WEB_SOCKET_MESSAGE_REQUESTED',
|
||||
createdAt: this.timer.getUTCDate(),
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ItemDumpedEvent,
|
||||
ItemRemovedFromSharedVaultEvent,
|
||||
ItemRevisionCreationRequestedEvent,
|
||||
ItemsChangedOnServerEvent,
|
||||
MessageSentToUserEvent,
|
||||
NotificationAddedForUserEvent,
|
||||
RevisionsCopyRequestedEvent,
|
||||
@@ -17,7 +18,16 @@ import {
|
||||
} from '@standardnotes/domain-events'
|
||||
|
||||
export interface DomainEventFactoryInterface {
|
||||
createWebSocketMessageRequestedEvent(dto: { userUuid: string; message: string }): WebSocketMessageRequestedEvent
|
||||
createWebSocketMessageRequestedEvent(dto: {
|
||||
userUuid: string
|
||||
message: string
|
||||
originatingSessionUuid?: string
|
||||
}): WebSocketMessageRequestedEvent
|
||||
createItemsChangedOnServerEvent(dto: {
|
||||
userUuid: string
|
||||
sessionUuid: string
|
||||
timestamp: number
|
||||
}): ItemsChangedOnServerEvent
|
||||
createUserInvitedToSharedVaultEvent(dto: {
|
||||
invite: {
|
||||
uuid: string
|
||||
|
||||
@@ -8,6 +8,9 @@ import { Logger } from 'winston'
|
||||
import { ContentType, Dates, Result, Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
import { ItemHash } from '../../../Item/ItemHash'
|
||||
import { Item } from '../../../Item/Item'
|
||||
import { SendEventToClient } from '../SendEventToClient/SendEventToClient'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
import { ItemsChangedOnServerEvent } from '@standardnotes/domain-events'
|
||||
|
||||
describe('SaveItems', () => {
|
||||
let itemSaveValidator: ItemSaveValidatorInterface
|
||||
@@ -18,11 +21,30 @@ describe('SaveItems', () => {
|
||||
let logger: Logger
|
||||
let itemHash1: ItemHash
|
||||
let savedItem: Item
|
||||
let sendEventToClient: SendEventToClient
|
||||
let domainEventFactory: DomainEventFactoryInterface
|
||||
|
||||
const createUseCase = () =>
|
||||
new SaveItems(itemSaveValidator, itemRepository, timer, saveNewItem, updateExistingItem, logger)
|
||||
new SaveItems(
|
||||
itemSaveValidator,
|
||||
itemRepository,
|
||||
timer,
|
||||
saveNewItem,
|
||||
updateExistingItem,
|
||||
sendEventToClient,
|
||||
domainEventFactory,
|
||||
logger,
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
sendEventToClient = {} as jest.Mocked<SendEventToClient>
|
||||
sendEventToClient.execute = jest.fn().mockReturnValue(Result.ok())
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createItemsChangedOnServerEvent = jest
|
||||
.fn()
|
||||
.mockReturnValue({} as jest.Mocked<ItemsChangedOnServerEvent>)
|
||||
|
||||
itemSaveValidator = {} as jest.Mocked<ItemSaveValidatorInterface>
|
||||
itemSaveValidator.validate = jest.fn().mockResolvedValue({ passed: true })
|
||||
|
||||
@@ -53,6 +75,7 @@ describe('SaveItems', () => {
|
||||
updateExistingItem.execute = jest.fn().mockResolvedValue(Result.ok(savedItem))
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
|
||||
itemHash1 = ItemHash.create({
|
||||
@@ -92,6 +115,7 @@ describe('SaveItems', () => {
|
||||
userUuid: 'user-uuid',
|
||||
sessionUuid: 'session-uuid',
|
||||
})
|
||||
expect(sendEventToClient.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should mark items as conflicts if saving new item fails', async () => {
|
||||
@@ -115,6 +139,7 @@ describe('SaveItems', () => {
|
||||
type: 'uuid_conflict',
|
||||
},
|
||||
])
|
||||
expect(sendEventToClient.execute).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should mark items as conflicts if saving new item throws an error', async () => {
|
||||
@@ -217,6 +242,7 @@ describe('SaveItems', () => {
|
||||
sessionUuid: 'session-uuid',
|
||||
performingUserUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
expect(sendEventToClient.execute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should mark items as conflicts if updating existing item fails', async () => {
|
||||
|
||||
@@ -11,6 +11,8 @@ import { ItemSaveValidatorInterface } from '../../../Item/SaveValidator/ItemSave
|
||||
import { SaveNewItem } from '../SaveNewItem/SaveNewItem'
|
||||
import { UpdateExistingItem } from '../UpdateExistingItem/UpdateExistingItem'
|
||||
import { ItemRepositoryInterface } from '../../../Item/ItemRepositoryInterface'
|
||||
import { SendEventToClient } from '../SendEventToClient/SendEventToClient'
|
||||
import { DomainEventFactoryInterface } from '../../../Event/DomainEventFactoryInterface'
|
||||
|
||||
export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
||||
private readonly SYNC_TOKEN_VERSION = 2
|
||||
@@ -21,6 +23,8 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
||||
private timer: TimerInterface,
|
||||
private saveNewItem: SaveNewItem,
|
||||
private updateExistingItem: UpdateExistingItem,
|
||||
private sendEventToClient: SendEventToClient,
|
||||
private domainEventFactory: DomainEventFactoryInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
@@ -133,6 +137,8 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
||||
|
||||
const syncToken = this.calculateSyncToken(lastUpdatedTimestamp, savedItems)
|
||||
|
||||
await this.notifyOtherClientsOfTheUserThatItemsChanged(dto, savedItems, lastUpdatedTimestamp)
|
||||
|
||||
return Result.ok({
|
||||
savedItems,
|
||||
conflicts,
|
||||
@@ -140,6 +146,31 @@ export class SaveItems implements UseCaseInterface<SaveItemsResult> {
|
||||
})
|
||||
}
|
||||
|
||||
private async notifyOtherClientsOfTheUserThatItemsChanged(
|
||||
dto: SaveItemsDTO,
|
||||
savedItems: Item[],
|
||||
lastUpdatedTimestamp: number,
|
||||
): Promise<void> {
|
||||
if (savedItems.length === 0 || !dto.sessionUuid) {
|
||||
return
|
||||
}
|
||||
|
||||
const itemsChangedEvent = this.domainEventFactory.createItemsChangedOnServerEvent({
|
||||
userUuid: dto.userUuid,
|
||||
sessionUuid: dto.sessionUuid,
|
||||
timestamp: lastUpdatedTimestamp,
|
||||
})
|
||||
const result = await this.sendEventToClient.execute({
|
||||
userUuid: dto.userUuid,
|
||||
originatingSessionUuid: dto.sessionUuid,
|
||||
event: itemsChangedEvent,
|
||||
})
|
||||
/* istanbul ignore next */
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`[${dto.userUuid}] Sending items changed event to client failed. Error: ${result.getError()}`)
|
||||
}
|
||||
}
|
||||
|
||||
private calculateSyncToken(lastUpdatedTimestamp: number, savedItems: Array<Item>): string {
|
||||
if (savedItems.length) {
|
||||
const sortedItems = savedItems.sort((itemA: Item, itemB: Item) => {
|
||||
|
||||
@@ -17,6 +17,8 @@ describe('SendEventToClient', () => {
|
||||
beforeEach(() => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.info = jest.fn()
|
||||
logger.debug = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
|
||||
domainEventFactory = {} as jest.Mocked<DomainEventFactoryInterface>
|
||||
domainEventFactory.createWebSocketMessageRequestedEvent = jest
|
||||
@@ -58,4 +60,21 @@ describe('SendEventToClient', () => {
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('should return a failed result if error is thrown', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
domainEventFactory.createWebSocketMessageRequestedEvent = jest.fn().mockImplementation(() => {
|
||||
throw new Error('test')
|
||||
})
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
event: {
|
||||
type: 'test',
|
||||
} as jest.Mocked<DomainEventInterface>,
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,21 +13,26 @@ export class SendEventToClient implements UseCaseInterface<void> {
|
||||
) {}
|
||||
|
||||
async execute(dto: SendEventToClientDTO): Promise<Result<void>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
try {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
this.logger.debug(`[WebSockets] Requesting message ${dto.event.type} to user ${dto.userUuid}`)
|
||||
|
||||
const event = this.domainEventFactory.createWebSocketMessageRequestedEvent({
|
||||
userUuid: userUuid.value,
|
||||
message: JSON.stringify(dto.event),
|
||||
originatingSessionUuid: dto.originatingSessionUuid,
|
||||
})
|
||||
|
||||
await this.domainEventPublisher.publish(event)
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
return Result.fail(`Failed to send event to client: ${(error as Error).message}`)
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
this.logger.info(`[WebSockets] Requesting message ${dto.event.type} to user ${dto.userUuid}`)
|
||||
|
||||
const event = this.domainEventFactory.createWebSocketMessageRequestedEvent({
|
||||
userUuid: userUuid.value,
|
||||
message: JSON.stringify(dto.event),
|
||||
})
|
||||
|
||||
await this.domainEventPublisher.publish(event)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ import { DomainEventInterface } from '@standardnotes/domain-events'
|
||||
export interface SendEventToClientDTO {
|
||||
userUuid: string
|
||||
event: DomainEventInterface
|
||||
originatingSessionUuid?: string
|
||||
}
|
||||
|
||||
@@ -22,38 +22,82 @@ export class SyncingServer implements ISyncingServer {
|
||||
call: grpc.ServerUnaryCall<SyncRequest, SyncResponse>,
|
||||
callback: grpc.sendUnaryData<SyncResponse>,
|
||||
): Promise<void> {
|
||||
this.logger.debug('[SyncingServer] Syncing items via gRPC')
|
||||
try {
|
||||
this.logger.debug('[SyncingServer] Syncing items via gRPC')
|
||||
|
||||
const itemHashesRPC = call.request.getItemsList()
|
||||
const itemHashes: ItemHash[] = []
|
||||
for (const itemHash of itemHashesRPC) {
|
||||
const itemHashOrError = ItemHash.create({
|
||||
uuid: itemHash.getUuid(),
|
||||
content: itemHash.hasContent() ? itemHash.getContent() : undefined,
|
||||
content_type: itemHash.hasContentType() ? (itemHash.getContentType() as string) : null,
|
||||
deleted: itemHash.hasDeleted() ? itemHash.getDeleted() : undefined,
|
||||
duplicate_of: itemHash.hasDuplicateOf() ? itemHash.getDuplicateOf() : undefined,
|
||||
auth_hash: itemHash.hasAuthHash() ? itemHash.getAuthHash() : undefined,
|
||||
enc_item_key: itemHash.hasEncItemKey() ? itemHash.getEncItemKey() : undefined,
|
||||
items_key_id: itemHash.hasItemsKeyId() ? itemHash.getItemsKeyId() : undefined,
|
||||
created_at: itemHash.hasCreatedAt() ? itemHash.getCreatedAt() : undefined,
|
||||
created_at_timestamp: itemHash.hasCreatedAtTimestamp() ? itemHash.getCreatedAtTimestamp() : undefined,
|
||||
updated_at: itemHash.hasUpdatedAt() ? itemHash.getUpdatedAt() : undefined,
|
||||
updated_at_timestamp: itemHash.hasUpdatedAtTimestamp() ? itemHash.getUpdatedAtTimestamp() : undefined,
|
||||
user_uuid: call.metadata.get('userUuid').pop() as string,
|
||||
key_system_identifier: itemHash.hasKeySystemIdentifier() ? (itemHash.getKeySystemIdentifier() as string) : null,
|
||||
shared_vault_uuid: itemHash.hasSharedVaultUuid() ? (itemHash.getSharedVaultUuid() as string) : null,
|
||||
const itemHashesRPC = call.request.getItemsList()
|
||||
const itemHashes: ItemHash[] = []
|
||||
for (const itemHash of itemHashesRPC) {
|
||||
const itemHashOrError = ItemHash.create({
|
||||
uuid: itemHash.getUuid(),
|
||||
content: itemHash.hasContent() ? itemHash.getContent() : undefined,
|
||||
content_type: itemHash.hasContentType() ? (itemHash.getContentType() as string) : null,
|
||||
deleted: itemHash.hasDeleted() ? itemHash.getDeleted() : undefined,
|
||||
duplicate_of: itemHash.hasDuplicateOf() ? itemHash.getDuplicateOf() : undefined,
|
||||
auth_hash: itemHash.hasAuthHash() ? itemHash.getAuthHash() : undefined,
|
||||
enc_item_key: itemHash.hasEncItemKey() ? itemHash.getEncItemKey() : undefined,
|
||||
items_key_id: itemHash.hasItemsKeyId() ? itemHash.getItemsKeyId() : undefined,
|
||||
created_at: itemHash.hasCreatedAt() ? itemHash.getCreatedAt() : undefined,
|
||||
created_at_timestamp: itemHash.hasCreatedAtTimestamp() ? itemHash.getCreatedAtTimestamp() : undefined,
|
||||
updated_at: itemHash.hasUpdatedAt() ? itemHash.getUpdatedAt() : undefined,
|
||||
updated_at_timestamp: itemHash.hasUpdatedAtTimestamp() ? itemHash.getUpdatedAtTimestamp() : undefined,
|
||||
user_uuid: call.metadata.get('userUuid').pop() as string,
|
||||
key_system_identifier: itemHash.hasKeySystemIdentifier()
|
||||
? (itemHash.getKeySystemIdentifier() as string)
|
||||
: null,
|
||||
shared_vault_uuid: itemHash.hasSharedVaultUuid() ? (itemHash.getSharedVaultUuid() as string) : null,
|
||||
})
|
||||
|
||||
if (itemHashOrError.isFailed()) {
|
||||
const metadata = new grpc.Metadata()
|
||||
metadata.set('x-sync-error-message', itemHashOrError.getError())
|
||||
metadata.set('x-sync-error-response-code', '400')
|
||||
|
||||
return callback(
|
||||
{
|
||||
code: Status.INVALID_ARGUMENT,
|
||||
message: itemHashOrError.getError(),
|
||||
name: 'INVALID_ARGUMENT',
|
||||
metadata,
|
||||
},
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
itemHashes.push(itemHashOrError.getValue())
|
||||
}
|
||||
|
||||
let sharedVaultUuids: string[] | undefined = undefined
|
||||
const sharedVaultUuidsList = call.request.getSharedVaultUuidsList()
|
||||
if (sharedVaultUuidsList.length > 0) {
|
||||
sharedVaultUuids = sharedVaultUuidsList
|
||||
}
|
||||
|
||||
const apiVersion = call.request.hasApiVersion() ? (call.request.getApiVersion() as string) : ApiVersion.v20161215
|
||||
|
||||
const syncResult = await this.syncItemsUseCase.execute({
|
||||
userUuid: call.metadata.get('x-user-uuid').pop() as string,
|
||||
itemHashes,
|
||||
computeIntegrityHash: call.request.hasComputeIntegrity() ? call.request.getComputeIntegrity() === true : false,
|
||||
syncToken: call.request.hasSyncToken() ? call.request.getSyncToken() : undefined,
|
||||
cursorToken: call.request.getCursorToken() ? call.request.getCursorToken() : undefined,
|
||||
limit: call.request.hasLimit() ? call.request.getLimit() : undefined,
|
||||
contentType: call.request.hasContentType() ? call.request.getContentType() : undefined,
|
||||
apiVersion,
|
||||
snjsVersion: call.metadata.get('x-snjs-version').pop() as string,
|
||||
readOnlyAccess: call.metadata.get('x-read-only-access').pop() === 'true',
|
||||
sessionUuid: call.metadata.get('x-session-uuid').pop() as string,
|
||||
sharedVaultUuids,
|
||||
})
|
||||
|
||||
if (itemHashOrError.isFailed()) {
|
||||
if (syncResult.isFailed()) {
|
||||
const metadata = new grpc.Metadata()
|
||||
metadata.set('x-sync-error-message', itemHashOrError.getError())
|
||||
metadata.set('x-sync-error-message', syncResult.getError())
|
||||
metadata.set('x-sync-error-response-code', '400')
|
||||
|
||||
return callback(
|
||||
{
|
||||
code: Status.INVALID_ARGUMENT,
|
||||
message: itemHashOrError.getError(),
|
||||
message: syncResult.getError(),
|
||||
name: 'INVALID_ARGUMENT',
|
||||
metadata,
|
||||
},
|
||||
@@ -61,53 +105,24 @@ export class SyncingServer implements ISyncingServer {
|
||||
)
|
||||
}
|
||||
|
||||
itemHashes.push(itemHashOrError.getValue())
|
||||
}
|
||||
const syncResponse = await this.syncResponseFactoryResolver
|
||||
.resolveSyncResponseFactoryVersion(apiVersion)
|
||||
.createResponse(syncResult.getValue())
|
||||
|
||||
let sharedVaultUuids: string[] | undefined = undefined
|
||||
const sharedVaultUuidsList = call.request.getSharedVaultUuidsList()
|
||||
if (sharedVaultUuidsList.length > 0) {
|
||||
sharedVaultUuids = sharedVaultUuidsList
|
||||
}
|
||||
const projection = this.mapper.toProjection(syncResponse as SyncResponse20200115)
|
||||
|
||||
const apiVersion = call.request.hasApiVersion() ? (call.request.getApiVersion() as string) : ApiVersion.v20161215
|
||||
|
||||
const syncResult = await this.syncItemsUseCase.execute({
|
||||
userUuid: call.metadata.get('x-user-uuid').pop() as string,
|
||||
itemHashes,
|
||||
computeIntegrityHash: call.request.hasComputeIntegrity() ? call.request.getComputeIntegrity() === true : false,
|
||||
syncToken: call.request.hasSyncToken() ? call.request.getSyncToken() : undefined,
|
||||
cursorToken: call.request.getCursorToken() ? call.request.getCursorToken() : undefined,
|
||||
limit: call.request.hasLimit() ? call.request.getLimit() : undefined,
|
||||
contentType: call.request.hasContentType() ? call.request.getContentType() : undefined,
|
||||
apiVersion,
|
||||
snjsVersion: call.metadata.get('x-snjs-version').pop() as string,
|
||||
readOnlyAccess: call.metadata.get('x-read-only-access').pop() === 'true',
|
||||
sessionUuid: call.metadata.get('x-session-uuid').pop() as string,
|
||||
sharedVaultUuids,
|
||||
})
|
||||
if (syncResult.isFailed()) {
|
||||
const metadata = new grpc.Metadata()
|
||||
metadata.set('x-sync-error-message', syncResult.getError())
|
||||
metadata.set('x-sync-error-response-code', '400')
|
||||
callback(null, projection)
|
||||
} catch (error) {
|
||||
this.logger.error(`[SyncingServer] Error syncing items via gRPC: ${(error as Error).message}`)
|
||||
|
||||
return callback(
|
||||
{
|
||||
code: Status.INVALID_ARGUMENT,
|
||||
message: syncResult.getError(),
|
||||
name: 'INVALID_ARGUMENT',
|
||||
metadata,
|
||||
code: Status.UNKNOWN,
|
||||
message: 'An error occurred while syncing items',
|
||||
name: 'UNKNOWN',
|
||||
},
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
const syncResponse = await this.syncResponseFactoryResolver
|
||||
.resolveSyncResponseFactoryVersion(apiVersion)
|
||||
.createResponse(syncResult.getValue())
|
||||
|
||||
const projection = this.mapper.toProjection(syncResponse as SyncResponse20200115)
|
||||
|
||||
callback(null, projection)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.18.1](https://github.com/standardnotes/server/compare/@standardnotes/time@1.18.0...@standardnotes/time@1.18.1) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
# [1.18.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.17.0...@standardnotes/time@1.18.0) (2023-11-27)
|
||||
|
||||
### Features
|
||||
|
||||
* add npm provenance to published packages ([e836abd](https://github.com/standardnotes/server/commit/e836abdef73d246940d8fffd9e65e17c64cd35c8))
|
||||
|
||||
# [1.17.0](https://github.com/standardnotes/server/compare/@standardnotes/time@1.16.0...@standardnotes/time@1.17.0) (2023-09-28)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/time",
|
||||
"version": "1.17.0",
|
||||
"version": "1.18.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
@@ -12,7 +12,13 @@
|
||||
"dist/src/**/*.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"provenance": true
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/time"
|
||||
},
|
||||
"author": "Standard Notes",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -8,6 +8,15 @@ AUTH_JWT_SECRET=auth_jwt_secret
|
||||
|
||||
REDIS_URL=redis://cache
|
||||
|
||||
DB_HOST=127.0.0.1
|
||||
DB_REPLICA_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_USERNAME=websockets
|
||||
DB_PASSWORD=changeme123
|
||||
DB_DATABASE=websockets
|
||||
DB_DEBUG_LEVEL=all # "all" | "query" | "schema" | "error" | "warn" | "info" | "log" | "migration"
|
||||
DB_TYPE=mysql
|
||||
|
||||
SNS_TOPIC_ARN=
|
||||
SNS_AWS_REGION=
|
||||
SQS_QUEUE_URL=
|
||||
|
||||
@@ -3,6 +3,32 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.1](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.21.0...@standardnotes/websockets-server@1.21.1) (2023-11-28)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass session uuid to websockets token ([bcd1d83](https://github.com/standardnotes/server/commit/bcd1d830e6125fc5f8cc1312e581284221aaac8f))
|
||||
|
||||
# [1.21.0](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.20.4...@standardnotes/websockets-server@1.21.0) (2023-11-28)
|
||||
|
||||
### Features
|
||||
|
||||
* send event to client upon items change on server ([#941](https://github.com/standardnotes/server/issues/941)) ([69b404f](https://github.com/standardnotes/server/commit/69b404f5d45f32530ebadbdbbec01d4e335dbbe9))
|
||||
|
||||
## [1.20.4](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.20.3...@standardnotes/websockets-server@1.20.4) (2023-11-27)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* repository config in package.json files ([ed1bf37](https://github.com/standardnotes/server/commit/ed1bf37287af23a25b8388ada95f0acdec8f71ea))
|
||||
|
||||
## [1.20.3](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.20.2...@standardnotes/websockets-server@1.20.3) (2023-11-27)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.20.2](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.20.1...@standardnotes/websockets-server@1.20.2) (2023-11-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
## [1.20.1](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.20.0...@standardnotes/websockets-server@1.20.1) (2023-11-22)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/websockets-server
|
||||
|
||||
@@ -12,7 +12,7 @@ import { ContainerConfigLoader } from '../src/Bootstrap/Container'
|
||||
import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('server')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
@@ -7,7 +7,7 @@ import TYPES from '../src/Bootstrap/Types'
|
||||
import { Env } from '../src/Bootstrap/Env'
|
||||
import { DomainEventSubscriberInterface } from '@standardnotes/domain-events'
|
||||
|
||||
const container = new ContainerConfigLoader()
|
||||
const container = new ContainerConfigLoader('worker')
|
||||
void container.load().then((container) => {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class InitialDatabase1701087671322 implements MigrationInterface {
|
||||
name = 'InitialDatabase1701087671322'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'CREATE TABLE `connections` (`uuid` varchar(36) NOT NULL, `user_uuid` varchar(36) NOT NULL, `session_uuid` varchar(36) NOT NULL, `connection_id` varchar(255) NOT NULL, `created_at_timestamp` bigint NOT NULL, `updated_at_timestamp` bigint NOT NULL, INDEX `index_connections_on_user_uuid` (`user_uuid`), UNIQUE INDEX `index_connections_on_connection_id` (`connection_id`), PRIMARY KEY (`uuid`)) ENGINE=InnoDB',
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query('DROP INDEX `index_connections_on_connection_id` ON `connections`')
|
||||
await queryRunner.query('DROP INDEX `index_connections_on_user_uuid` ON `connections`')
|
||||
await queryRunner.query('DROP TABLE `connections`')
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"name": "@standardnotes/websockets-server",
|
||||
"version": "1.20.1",
|
||||
"version": "1.21.1",
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <21.0.0"
|
||||
},
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:standardnotes/server.git",
|
||||
"directory": "packages/websockets"
|
||||
},
|
||||
"description": "Websockets Server",
|
||||
"main": "dist/src/index.js",
|
||||
"typings": "dist/src/index.d.ts",
|
||||
@@ -24,13 +29,13 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-apigatewaymanagementapi": "^3.427.0",
|
||||
"@aws-sdk/client-sqs": "^3.427.0",
|
||||
"@standardnotes/api": "^1.26.26",
|
||||
"@standardnotes/common": "workspace:^",
|
||||
"@standardnotes/domain-core": "workspace:^",
|
||||
"@standardnotes/domain-events": "workspace:^",
|
||||
"@standardnotes/domain-events-infra": "workspace:^",
|
||||
"@standardnotes/responses": "^1.13.27",
|
||||
"@standardnotes/security": "workspace:^",
|
||||
"@standardnotes/time": "workspace:^",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "^16.0.1",
|
||||
"express": "^4.18.2",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as winston from 'winston'
|
||||
import Redis from 'ioredis'
|
||||
import { SQSClient, SQSClientConfig } from '@aws-sdk/client-sqs'
|
||||
import { ApiGatewayManagementApiClient } from '@aws-sdk/client-apigatewaymanagementapi'
|
||||
import { Container } from 'inversify'
|
||||
@@ -8,16 +7,14 @@ import {
|
||||
DomainEventMessageHandlerInterface,
|
||||
DomainEventSubscriberInterface,
|
||||
} from '@standardnotes/domain-events'
|
||||
import { TimerInterface, Timer } from '@standardnotes/time'
|
||||
import { Env } from './Env'
|
||||
import TYPES from './Types'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../Domain/WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { RedisWebSocketsConnectionRepository } from '../Infra/Redis/RedisWebSocketsConnectionRepository'
|
||||
import { AddWebSocketsConnection } from '../Domain/UseCase/AddWebSocketsConnection/AddWebSocketsConnection'
|
||||
import { RemoveWebSocketsConnection } from '../Domain/UseCase/RemoveWebSocketsConnection/RemoveWebSocketsConnection'
|
||||
import { WebSocketsClientMessenger } from '../Infra/WebSockets/WebSocketsClientMessenger'
|
||||
import { SQSDomainEventSubscriber, SQSEventMessageHandler } from '@standardnotes/domain-events-infra'
|
||||
import { ApiGatewayAuthMiddleware } from '../Controller/ApiGatewayAuthMiddleware'
|
||||
|
||||
import { ApiGatewayAuthMiddleware } from '../Infra/InversifyExpressUtils/Middleware/ApiGatewayAuthMiddleware'
|
||||
import {
|
||||
CrossServiceTokenData,
|
||||
TokenDecoder,
|
||||
@@ -27,29 +24,25 @@ import {
|
||||
WebSocketConnectionTokenData,
|
||||
} from '@standardnotes/security'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
import { WebSocketsController } from '../Controller/WebSocketsController'
|
||||
import { WebSocketServerInterface } from '@standardnotes/api'
|
||||
import { ClientMessengerInterface } from '../Client/ClientMessengerInterface'
|
||||
import { WebSocketMessageRequestedEventHandler } from '../Domain/Handler/WebSocketMessageRequestedEventHandler'
|
||||
import { SQLConnectionRepository } from '../Infra/TypeORM/SQLConnectionRepository'
|
||||
import { Connection } from '../Domain/Connection/Connection'
|
||||
import { SQLConnection } from '../Infra/TypeORM/SQLConnection'
|
||||
import { MapperInterface } from '@standardnotes/domain-core'
|
||||
import { Repository } from 'typeorm'
|
||||
import { ConnectionPersistenceMapper } from '../Mapping/SQL/ConnectionPersistenceMapper'
|
||||
import { AppDataSource } from './DataSource'
|
||||
import { SendMessageToClient } from '../Domain/UseCase/SendMessageToClient/SendMessageToClient'
|
||||
|
||||
export class ContainerConfigLoader {
|
||||
constructor(private mode: 'server' | 'worker' = 'server') {}
|
||||
|
||||
async load(): Promise<Container> {
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
const container = new Container()
|
||||
|
||||
const redisUrl = env.get('REDIS_URL')
|
||||
const isRedisInClusterMode = redisUrl.indexOf(',') > 0
|
||||
let redis
|
||||
if (isRedisInClusterMode) {
|
||||
redis = new Redis.Cluster(redisUrl.split(','))
|
||||
} else {
|
||||
redis = new Redis(redisUrl)
|
||||
}
|
||||
|
||||
container.bind(TYPES.Redis).toConstantValue(redis)
|
||||
|
||||
const winstonFormatters = [winston.format.splat(), winston.format.json()]
|
||||
|
||||
const logger = winston.createLogger({
|
||||
@@ -59,6 +52,13 @@ export class ContainerConfigLoader {
|
||||
})
|
||||
container.bind<winston.Logger>(TYPES.Logger).toConstantValue(logger)
|
||||
|
||||
const appDataSource = new AppDataSource({ env, runMigrations: this.mode === 'server' })
|
||||
await appDataSource.initialize()
|
||||
|
||||
logger.debug('Database initialized')
|
||||
|
||||
container.bind<TimerInterface>(TYPES.Timer).toConstantValue(new Timer())
|
||||
|
||||
if (env.get('SQS_QUEUE_URL', true)) {
|
||||
const sqsConfig: SQSClientConfig = {
|
||||
region: env.get('SQS_AWS_REGION', true),
|
||||
@@ -83,14 +83,26 @@ export class ContainerConfigLoader {
|
||||
region: env.get('API_GATEWAY_AWS_REGION', true) ?? 'us-east-1',
|
||||
}),
|
||||
)
|
||||
// Mappers
|
||||
container
|
||||
.bind<MapperInterface<Connection, SQLConnection>>(TYPES.ConnectionPersistenceMapper)
|
||||
.toConstantValue(new ConnectionPersistenceMapper())
|
||||
|
||||
// Controller
|
||||
container.bind<WebSocketServerInterface>(TYPES.WebSocketsController).to(WebSocketsController)
|
||||
// ORM
|
||||
container
|
||||
.bind<Repository<SQLConnection>>(TYPES.ORMConnectionRepository)
|
||||
.toConstantValue(appDataSource.getRepository(SQLConnection))
|
||||
|
||||
// Repositories
|
||||
container
|
||||
.bind<WebSocketsConnectionRepositoryInterface>(TYPES.WebSocketsConnectionRepository)
|
||||
.to(RedisWebSocketsConnectionRepository)
|
||||
.toConstantValue(
|
||||
new SQLConnectionRepository(
|
||||
container.get<Repository<SQLConnection>>(TYPES.ORMConnectionRepository),
|
||||
container.get<MapperInterface<Connection, SQLConnection>>(TYPES.ConnectionPersistenceMapper),
|
||||
container.get<winston.Logger>(TYPES.Logger),
|
||||
),
|
||||
)
|
||||
|
||||
// Middleware
|
||||
container.bind<ApiGatewayAuthMiddleware>(TYPES.ApiGatewayAuthMiddleware).to(ApiGatewayAuthMiddleware)
|
||||
@@ -103,21 +115,42 @@ export class ContainerConfigLoader {
|
||||
container
|
||||
.bind(TYPES.WEB_SOCKET_CONNECTION_TOKEN_TTL)
|
||||
.toConstantValue(+env.get('WEB_SOCKET_CONNECTION_TOKEN_TTL', true))
|
||||
container.bind(TYPES.REDIS_URL).toConstantValue(env.get('REDIS_URL'))
|
||||
container.bind(TYPES.SQS_QUEUE_URL).toConstantValue(env.get('SQS_QUEUE_URL'))
|
||||
container.bind(TYPES.VERSION).toConstantValue(env.get('VERSION'))
|
||||
|
||||
// use cases
|
||||
container.bind<AddWebSocketsConnection>(TYPES.AddWebSocketsConnection).to(AddWebSocketsConnection)
|
||||
container
|
||||
.bind<AddWebSocketsConnection>(TYPES.AddWebSocketsConnection)
|
||||
.toConstantValue(
|
||||
new AddWebSocketsConnection(
|
||||
container.get<WebSocketsConnectionRepositoryInterface>(TYPES.WebSocketsConnectionRepository),
|
||||
container.get<TimerInterface>(TYPES.Timer),
|
||||
container.get<winston.Logger>(TYPES.Logger),
|
||||
),
|
||||
)
|
||||
container.bind<RemoveWebSocketsConnection>(TYPES.RemoveWebSocketsConnection).to(RemoveWebSocketsConnection)
|
||||
container
|
||||
.bind<CreateWebSocketConnectionToken>(TYPES.CreateWebSocketConnectionToken)
|
||||
.to(CreateWebSocketConnectionToken)
|
||||
container
|
||||
.bind<SendMessageToClient>(TYPES.SendMessageToClient)
|
||||
.toConstantValue(
|
||||
new SendMessageToClient(
|
||||
container.get<WebSocketsConnectionRepositoryInterface>(TYPES.WebSocketsConnectionRepository),
|
||||
container.get<ApiGatewayManagementApiClient>(TYPES.WebSockets_ApiGatewayManagementApiClient),
|
||||
container.get<winston.Logger>(TYPES.Logger),
|
||||
),
|
||||
)
|
||||
|
||||
// Handlers
|
||||
container
|
||||
.bind<WebSocketMessageRequestedEventHandler>(TYPES.WebSocketMessageRequestedEventHandler)
|
||||
.to(WebSocketMessageRequestedEventHandler)
|
||||
.toConstantValue(
|
||||
new WebSocketMessageRequestedEventHandler(
|
||||
container.get<SendMessageToClient>(TYPES.SendMessageToClient),
|
||||
container.get<winston.Logger>(TYPES.Logger),
|
||||
),
|
||||
)
|
||||
|
||||
// Services
|
||||
container
|
||||
@@ -128,7 +161,6 @@ export class ContainerConfigLoader {
|
||||
.toConstantValue(
|
||||
new TokenEncoder<WebSocketConnectionTokenData>(container.get(TYPES.WEB_SOCKET_CONNECTION_TOKEN_SECRET)),
|
||||
)
|
||||
container.bind<ClientMessengerInterface>(TYPES.WebSocketsClientMessenger).to(WebSocketsClientMessenger)
|
||||
|
||||
const eventHandlers: Map<string, DomainEventHandlerInterface> = new Map([
|
||||
['WEB_SOCKET_MESSAGE_REQUESTED', container.get(TYPES.WebSocketMessageRequestedEventHandler)],
|
||||
|
||||
84
packages/websockets/src/Bootstrap/DataSource.ts
Normal file
84
packages/websockets/src/Bootstrap/DataSource.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { DataSource, EntityTarget, LoggerOptions, ObjectLiteral, Repository } from 'typeorm'
|
||||
import { MysqlConnectionOptions } from 'typeorm/driver/mysql/MysqlConnectionOptions'
|
||||
import { Env } from './Env'
|
||||
import { SQLConnection } from '../Infra/TypeORM/SQLConnection'
|
||||
|
||||
export class AppDataSource {
|
||||
private _dataSource: DataSource | undefined
|
||||
|
||||
constructor(
|
||||
private configuration: {
|
||||
env: Env
|
||||
runMigrations: boolean
|
||||
},
|
||||
) {}
|
||||
|
||||
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
|
||||
if (!this._dataSource) {
|
||||
throw new Error('DataSource not initialized')
|
||||
}
|
||||
|
||||
return this._dataSource.getRepository(target)
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
await this.dataSource.initialize()
|
||||
}
|
||||
|
||||
get dataSource(): DataSource {
|
||||
this.configuration.env.load()
|
||||
|
||||
const maxQueryExecutionTime = this.configuration.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
? +this.configuration.env.get('DB_MAX_QUERY_EXECUTION_TIME', true)
|
||||
: 45_000
|
||||
|
||||
const commonDataSourceOptions = {
|
||||
maxQueryExecutionTime,
|
||||
entities: [SQLConnection],
|
||||
migrations: [`${__dirname}/../../migrations/mysql/*.js`],
|
||||
migrationsRun: this.configuration.runMigrations,
|
||||
logging: <LoggerOptions>this.configuration.env.get('DB_DEBUG_LEVEL', true) ?? 'info',
|
||||
}
|
||||
|
||||
const inReplicaMode = this.configuration.env.get('DB_REPLICA_HOST', true) ? true : false
|
||||
|
||||
const replicationConfig = {
|
||||
master: {
|
||||
host: this.configuration.env.get('DB_HOST'),
|
||||
port: parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: this.configuration.env.get('DB_USERNAME'),
|
||||
password: this.configuration.env.get('DB_PASSWORD'),
|
||||
database: this.configuration.env.get('DB_DATABASE'),
|
||||
},
|
||||
slaves: [
|
||||
{
|
||||
host: this.configuration.env.get('DB_REPLICA_HOST', true),
|
||||
port: parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: this.configuration.env.get('DB_USERNAME'),
|
||||
password: this.configuration.env.get('DB_PASSWORD'),
|
||||
database: this.configuration.env.get('DB_DATABASE'),
|
||||
},
|
||||
],
|
||||
removeNodeErrorCount: 10,
|
||||
restoreNodeTimeout: 5,
|
||||
}
|
||||
|
||||
const mySQLDataSourceOptions: MysqlConnectionOptions = {
|
||||
...commonDataSourceOptions,
|
||||
type: 'mysql',
|
||||
charset: 'utf8mb4',
|
||||
supportBigNumbers: true,
|
||||
bigNumberStrings: false,
|
||||
replication: inReplicaMode ? replicationConfig : undefined,
|
||||
host: inReplicaMode ? undefined : this.configuration.env.get('DB_HOST'),
|
||||
port: inReplicaMode ? undefined : parseInt(this.configuration.env.get('DB_PORT')),
|
||||
username: inReplicaMode ? undefined : this.configuration.env.get('DB_USERNAME'),
|
||||
password: inReplicaMode ? undefined : this.configuration.env.get('DB_PASSWORD'),
|
||||
database: inReplicaMode ? undefined : this.configuration.env.get('DB_DATABASE'),
|
||||
}
|
||||
|
||||
this._dataSource = new DataSource(mySQLDataSourceOptions)
|
||||
|
||||
return this._dataSource
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { AppDataSource } from './DataSource'
|
||||
import { Env } from './Env'
|
||||
|
||||
const env: Env = new Env()
|
||||
env.load()
|
||||
|
||||
export const MigrationsDataSource = new AppDataSource({ env, runMigrations: true }).dataSource
|
||||
@@ -1,10 +1,12 @@
|
||||
const TYPES = {
|
||||
Logger: Symbol.for('Logger'),
|
||||
Redis: Symbol.for('Redis'),
|
||||
Timer: Symbol.for('Timer'),
|
||||
SQS: Symbol.for('SQS'),
|
||||
WebSockets_ApiGatewayManagementApiClient: Symbol.for('WebSockets_ApiGatewayManagementApiClient'),
|
||||
// Controller
|
||||
WebSocketsController: Symbol.for('WebSocketsController'),
|
||||
// Mappers
|
||||
ConnectionPersistenceMapper: Symbol.for('ConnectionPersistenceMapper'),
|
||||
// ORM
|
||||
ORMConnectionRepository: Symbol.for('ORMConnectionRepository'),
|
||||
// Repositories
|
||||
WebSocketsConnectionRepository: Symbol.for('WebSocketsConnectionRepository'),
|
||||
// Middleware
|
||||
@@ -22,6 +24,7 @@ const TYPES = {
|
||||
AddWebSocketsConnection: Symbol.for('AddWebSocketsConnection'),
|
||||
RemoveWebSocketsConnection: Symbol.for('RemoveWebSocketsConnection'),
|
||||
CreateWebSocketConnectionToken: Symbol.for('CreateWebSocketConnectionToken'),
|
||||
SendMessageToClient: Symbol.for('SendMessageToClient'),
|
||||
// Handlers
|
||||
WebSocketMessageRequestedEventHandler: Symbol.for('WebSocketMessageRequestedEventHandler'),
|
||||
// Services
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface ClientMessengerInterface {
|
||||
send(userUuid: string, message: string): Promise<void>
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { ApiGatewayAuthMiddleware } from './ApiGatewayAuthMiddleware'
|
||||
import { NextFunction, Request, Response } from 'express'
|
||||
import { Logger } from 'winston'
|
||||
import { CrossServiceTokenData, TokenDecoderInterface } from '@standardnotes/security'
|
||||
import { RoleName } from '@standardnotes/domain-core'
|
||||
|
||||
describe('ApiGatewayAuthMiddleware', () => {
|
||||
let tokenDecoder: TokenDecoderInterface<CrossServiceTokenData>
|
||||
let request: Request
|
||||
let response: Response
|
||||
let next: NextFunction
|
||||
|
||||
const logger = {
|
||||
debug: jest.fn(),
|
||||
} as unknown as jest.Mocked<Logger>
|
||||
|
||||
const createMiddleware = () => new ApiGatewayAuthMiddleware(tokenDecoder, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
tokenDecoder = {} as jest.Mocked<TokenDecoderInterface<CrossServiceTokenData>>
|
||||
tokenDecoder.decodeToken = jest.fn().mockReturnValue({
|
||||
user: {
|
||||
uuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
},
|
||||
roles: [
|
||||
{
|
||||
uuid: 'a-b-c',
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
request = {
|
||||
headers: {},
|
||||
} as jest.Mocked<Request>
|
||||
response = {
|
||||
locals: {},
|
||||
} as jest.Mocked<Response>
|
||||
response.status = jest.fn().mockReturnThis()
|
||||
response.send = jest.fn()
|
||||
next = jest.fn()
|
||||
})
|
||||
|
||||
it('should authorize user', async () => {
|
||||
request.headers['x-auth-token'] = 'auth-jwt-token'
|
||||
|
||||
await createMiddleware().handler(request, response, next)
|
||||
|
||||
expect(response.locals.user).toEqual({
|
||||
uuid: '1-2-3',
|
||||
email: 'test@test.te',
|
||||
})
|
||||
expect(response.locals.roles).toEqual([
|
||||
{
|
||||
uuid: 'a-b-c',
|
||||
name: RoleName.NAMES.CoreUser,
|
||||
},
|
||||
])
|
||||
|
||||
expect(next).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not authorize if request is missing auth jwt token in headers', async () => {
|
||||
await createMiddleware().handler(request, response, next)
|
||||
|
||||
expect(response.status).toHaveBeenCalledWith(401)
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should not authorize if auth jwt token is malformed', async () => {
|
||||
request.headers['x-auth-token'] = 'auth-jwt-token'
|
||||
|
||||
tokenDecoder.decodeToken = jest.fn().mockReturnValue(undefined)
|
||||
|
||||
await createMiddleware().handler(request, response, next)
|
||||
|
||||
expect(response.status).toHaveBeenCalledWith(401)
|
||||
expect(next).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should pass the error to next middleware if one occurres', async () => {
|
||||
request.headers['x-auth-token'] = 'auth-jwt-token'
|
||||
|
||||
const error = new Error('Ooops')
|
||||
|
||||
tokenDecoder.decodeToken = jest.fn().mockImplementation(() => {
|
||||
throw error
|
||||
})
|
||||
|
||||
await createMiddleware().handler(request, response, next)
|
||||
|
||||
expect(response.status).not.toHaveBeenCalled()
|
||||
|
||||
expect(next).toHaveBeenCalledWith(error)
|
||||
})
|
||||
})
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { WebSocketsController } from './WebSocketsController'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
|
||||
describe('WebSocketsController', () => {
|
||||
let createWebSocketConnectionToken: CreateWebSocketConnectionToken
|
||||
|
||||
const createController = () => new WebSocketsController(createWebSocketConnectionToken)
|
||||
|
||||
beforeEach(() => {
|
||||
createWebSocketConnectionToken = {} as jest.Mocked<CreateWebSocketConnectionToken>
|
||||
createWebSocketConnectionToken.execute = jest.fn().mockReturnValue({ token: 'foobar' })
|
||||
})
|
||||
|
||||
it('should create a web sockets connection token', async () => {
|
||||
const response = await createController().createConnectionToken({ userUuid: '1-2-3' })
|
||||
|
||||
expect(response).toEqual({
|
||||
status: 200,
|
||||
data: { token: 'foobar' },
|
||||
})
|
||||
|
||||
expect(createWebSocketConnectionToken.execute).toHaveBeenCalledWith({
|
||||
userUuid: '1-2-3',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,29 +0,0 @@
|
||||
import { HttpStatusCode, HttpResponse } from '@standardnotes/responses'
|
||||
import {
|
||||
WebSocketConnectionTokenRequestParams,
|
||||
WebSocketConnectionTokenResponseBody,
|
||||
WebSocketServerInterface,
|
||||
} from '@standardnotes/api'
|
||||
import { inject, injectable } from 'inversify'
|
||||
|
||||
import TYPES from '../Bootstrap/Types'
|
||||
import { CreateWebSocketConnectionToken } from '../Domain/UseCase/CreateWebSocketConnectionToken/CreateWebSocketConnectionToken'
|
||||
|
||||
@injectable()
|
||||
export class WebSocketsController implements WebSocketServerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.CreateWebSocketConnectionToken)
|
||||
private createWebSocketConnectionToken: CreateWebSocketConnectionToken,
|
||||
) {}
|
||||
|
||||
async createConnectionToken(
|
||||
params: WebSocketConnectionTokenRequestParams,
|
||||
): Promise<HttpResponse<WebSocketConnectionTokenResponseBody>> {
|
||||
const result = await this.createWebSocketConnectionToken.execute({ userUuid: params.userUuid as string })
|
||||
|
||||
return {
|
||||
status: HttpStatusCode.Success,
|
||||
data: result,
|
||||
}
|
||||
}
|
||||
}
|
||||
13
packages/websockets/src/Domain/Connection/Connection.ts
Normal file
13
packages/websockets/src/Domain/Connection/Connection.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Entity, Result, UniqueEntityId } from '@standardnotes/domain-core'
|
||||
|
||||
import { ConnectionProps } from './ConnectionProps'
|
||||
|
||||
export class Connection extends Entity<ConnectionProps> {
|
||||
private constructor(props: ConnectionProps, id?: UniqueEntityId) {
|
||||
super(props, id)
|
||||
}
|
||||
|
||||
static create(props: ConnectionProps, id?: UniqueEntityId): Result<Connection> {
|
||||
return Result.ok<Connection>(new Connection(props, id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
export interface ConnectionProps {
|
||||
userUuid: Uuid
|
||||
sessionUuid: Uuid
|
||||
connectionId: string
|
||||
timestamps: Timestamps
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
import { DomainEventHandlerInterface, WebSocketMessageRequestedEvent } from '@standardnotes/domain-events'
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { SendMessageToClient } from '../UseCase/SendMessageToClient/SendMessageToClient'
|
||||
|
||||
import TYPES from '../../Bootstrap/Types'
|
||||
import { ClientMessengerInterface } from '../../Client/ClientMessengerInterface'
|
||||
|
||||
@injectable()
|
||||
export class WebSocketMessageRequestedEventHandler implements DomainEventHandlerInterface {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketsClientMessenger) private webSocketsClientMessenger: ClientMessengerInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
private sendMessageToClient: SendMessageToClient,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async handle(event: WebSocketMessageRequestedEvent): Promise<void> {
|
||||
this.logger.debug(`Sending message to user ${event.payload.userUuid}`)
|
||||
const result = await this.sendMessageToClient.execute({
|
||||
userUuid: event.payload.userUuid,
|
||||
message: event.payload.message,
|
||||
originatingSessionUuid: event.payload.originatingSessionUuid,
|
||||
})
|
||||
|
||||
await this.webSocketsClientMessenger.send(event.payload.userUuid, event.payload.message)
|
||||
if (result.isFailed()) {
|
||||
this.logger.error(`Could not send message to user ${event.payload.userUuid}. Error: ${result.getError()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'reflect-metadata'
|
||||
import { Logger } from 'winston'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
|
||||
import { AddWebSocketsConnection } from './AddWebSocketsConnection'
|
||||
|
||||
describe('AddWebSocketsConnection', () => {
|
||||
let webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface
|
||||
let timer: TimerInterface
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () => new AddWebSocketsConnection(webSocketsConnectionRepository, logger)
|
||||
const createUseCase = () => new AddWebSocketsConnection(webSocketsConnectionRepository, timer, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
webSocketsConnectionRepository = {} as jest.Mocked<WebSocketsConnectionRepositoryInterface>
|
||||
@@ -17,12 +18,18 @@ describe('AddWebSocketsConnection', () => {
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
|
||||
timer = {} as jest.Mocked<TimerInterface>
|
||||
timer.getTimestampInMicroseconds = jest.fn().mockReturnValue(123)
|
||||
})
|
||||
|
||||
it('should save a web sockets connection for a user for further communication', async () => {
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3', connectionId: '2-3-4' })
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
connectionId: '2-3-4',
|
||||
})
|
||||
|
||||
expect(webSocketsConnectionRepository.saveConnection).toHaveBeenCalledWith('1-2-3', '2-3-4')
|
||||
expect(result.isFailed()).toBe(false)
|
||||
})
|
||||
|
||||
@@ -31,7 +38,31 @@ describe('AddWebSocketsConnection', () => {
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('Could not save connection'))
|
||||
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3', connectionId: '2-3-4' })
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
connectionId: '2-3-4',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('should return failure if the user uuid is invalid', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: 'invalid',
|
||||
sessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
connectionId: '2-3-4',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('should return error if the session uuid is invalid', async () => {
|
||||
const result = await createUseCase().execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
sessionUuid: 'invalid',
|
||||
connectionId: '2-3-4',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
import { inject, injectable } from 'inversify'
|
||||
import { Logger } from 'winston'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { Result, Timestamps, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { TimerInterface } from '@standardnotes/time'
|
||||
|
||||
import TYPES from '../../../Bootstrap/Types'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { AddWebSocketsConnectionDTO } from './AddWebSocketsConnectionDTO'
|
||||
import { Connection } from '../../Connection/Connection'
|
||||
|
||||
@injectable()
|
||||
export class AddWebSocketsConnection implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
@inject(TYPES.WebSocketsConnectionRepository)
|
||||
private webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface,
|
||||
@inject(TYPES.Logger) private logger: Logger,
|
||||
private timer: TimerInterface,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: AddWebSocketsConnectionDTO): Promise<Result<void>> {
|
||||
try {
|
||||
this.logger.debug(`Persisting connection ${dto.connectionId} for user ${dto.userUuid}`)
|
||||
|
||||
await this.webSocketsConnectionRepository.saveConnection(dto.userUuid, dto.connectionId)
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const sessionUuidOrError = Uuid.create(dto.sessionUuid)
|
||||
if (sessionUuidOrError.isFailed()) {
|
||||
return Result.fail(sessionUuidOrError.getError())
|
||||
}
|
||||
const sessionUuid = sessionUuidOrError.getValue()
|
||||
|
||||
const connectionOrError = Connection.create({
|
||||
userUuid,
|
||||
sessionUuid,
|
||||
connectionId: dto.connectionId,
|
||||
timestamps: Timestamps.create(
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
this.timer.getTimestampInMicroseconds(),
|
||||
).getValue(),
|
||||
})
|
||||
/* istanbul ignore next */
|
||||
if (connectionOrError.isFailed()) {
|
||||
return Result.fail(connectionOrError.getError())
|
||||
}
|
||||
const connection = connectionOrError.getValue()
|
||||
|
||||
await this.webSocketsConnectionRepository.saveConnection(connection)
|
||||
|
||||
return Result.ok()
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type AddWebSocketsConnectionDTO = {
|
||||
userUuid: string
|
||||
sessionUuid: string
|
||||
connectionId: string
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ describe('CreateWebSocketConnection', () => {
|
||||
})
|
||||
|
||||
it('should create a web socket connection token', async () => {
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3' })
|
||||
const result = await createUseCase().execute({ userUuid: '1-2-3', sessionUuid: '4-5-6' })
|
||||
|
||||
expect(result.token).toEqual('foobar')
|
||||
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith({ userUuid: '1-2-3' }, 30)
|
||||
expect(tokenEncoder.encodeExpirableToken).toHaveBeenCalledWith({ userUuid: '1-2-3', sessionUuid: '4-5-6' }, 30)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export type CreateWebSocketConnectionDTO = {
|
||||
userUuid: string
|
||||
sessionUuid: string
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export class CreateWebSocketConnectionToken implements UseCaseInterface {
|
||||
async execute(dto: CreateWebSocketConnectionDTO): Promise<CreateWebSocketConnectionResponse> {
|
||||
const data: WebSocketConnectionTokenData = {
|
||||
userUuid: dto.userUuid,
|
||||
sessionUuid: dto.sessionUuid,
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { ApiGatewayManagementApiClient } from '@aws-sdk/client-apigatewaymanagementapi'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
import { SendMessageToClient } from './SendMessageToClient'
|
||||
import { Logger } from 'winston'
|
||||
import { Connection } from '../../Connection/Connection'
|
||||
import { Timestamps, Uuid } from '@standardnotes/domain-core'
|
||||
|
||||
describe('SendMessageToClient', () => {
|
||||
let webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface
|
||||
let apiGatewayManagementClient: ApiGatewayManagementApiClient
|
||||
let logger: Logger
|
||||
|
||||
const createUseCase = () =>
|
||||
new SendMessageToClient(webSocketsConnectionRepository, apiGatewayManagementClient, logger)
|
||||
|
||||
beforeEach(() => {
|
||||
const connection = Connection.create({
|
||||
userUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
connectionId: 'connection-id',
|
||||
sessionUuid: Uuid.create('00000000-0000-0000-0000-000000000000').getValue(),
|
||||
timestamps: Timestamps.create(123, 123).getValue(),
|
||||
}).getValue()
|
||||
|
||||
webSocketsConnectionRepository = {} as jest.Mocked<WebSocketsConnectionRepositoryInterface>
|
||||
webSocketsConnectionRepository.findAllByUserUuid = jest.fn().mockResolvedValue([connection])
|
||||
|
||||
apiGatewayManagementClient = {} as jest.Mocked<ApiGatewayManagementApiClient>
|
||||
apiGatewayManagementClient.send = jest.fn().mockResolvedValue({ $metadata: { httpStatusCode: 200 } })
|
||||
|
||||
logger = {} as jest.Mocked<Logger>
|
||||
logger.debug = jest.fn()
|
||||
logger.error = jest.fn()
|
||||
})
|
||||
|
||||
it('sends message to all connections for a user', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
message: 'message',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(apiGatewayManagementClient.send).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('does not send message to originating session', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
message: 'message',
|
||||
originatingSessionUuid: '00000000-0000-0000-0000-000000000000',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(apiGatewayManagementClient.send).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('returns error if sending message fails', async () => {
|
||||
apiGatewayManagementClient.send = jest.fn().mockRejectedValue(new Error('error'))
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
message: 'message',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe(
|
||||
'Could not send message to connection connection-id for user 00000000-0000-0000-0000-000000000000. Error: error',
|
||||
)
|
||||
})
|
||||
|
||||
it('returns error if the user uuid is invalid', async () => {
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: 'invalid',
|
||||
message: 'message',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
|
||||
it('return error if sending the message does not return a 200 status code', async () => {
|
||||
apiGatewayManagementClient.send = jest.fn().mockResolvedValue({ $metadata: { httpStatusCode: 500 } })
|
||||
|
||||
const useCase = createUseCase()
|
||||
|
||||
const result = await useCase.execute({
|
||||
userUuid: '00000000-0000-0000-0000-000000000000',
|
||||
message: 'message',
|
||||
})
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core'
|
||||
import { ApiGatewayManagementApiClient, PostToConnectionCommand } from '@aws-sdk/client-apigatewaymanagementapi'
|
||||
import { Logger } from 'winston'
|
||||
|
||||
import { SendMessageToClientDTO } from './SendMessageToClientDTO'
|
||||
import { WebSocketsConnectionRepositoryInterface } from '../../WebSockets/WebSocketsConnectionRepositoryInterface'
|
||||
|
||||
export class SendMessageToClient implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private webSocketsConnectionRepository: WebSocketsConnectionRepositoryInterface,
|
||||
private apiGatewayManagementClient: ApiGatewayManagementApiClient,
|
||||
private logger: Logger,
|
||||
) {}
|
||||
|
||||
async execute(dto: SendMessageToClientDTO): Promise<Result<void>> {
|
||||
const userUuidOrError = Uuid.create(dto.userUuid)
|
||||
if (userUuidOrError.isFailed()) {
|
||||
return Result.fail(userUuidOrError.getError())
|
||||
}
|
||||
const userUuid = userUuidOrError.getValue()
|
||||
|
||||
const userConnections = await this.webSocketsConnectionRepository.findAllByUserUuid(userUuid)
|
||||
|
||||
for (const connection of userConnections) {
|
||||
if (dto.originatingSessionUuid && connection.props.sessionUuid.value === dto.originatingSessionUuid) {
|
||||
continue
|
||||
}
|
||||
|
||||
this.logger.debug(`Sending message to connection ${connection.props.connectionId} for user ${userUuid.value}`)
|
||||
|
||||
const requestParams = {
|
||||
ConnectionId: connection.props.connectionId,
|
||||
Data: dto.message,
|
||||
}
|
||||
|
||||
const command = new PostToConnectionCommand(requestParams)
|
||||
|
||||
try {
|
||||
const response = await this.apiGatewayManagementClient.send(command)
|
||||
|
||||
if (response.$metadata.httpStatusCode !== 200) {
|
||||
return Result.fail(
|
||||
`Could not send message to connection ${connection.props.connectionId} for user ${userUuid.value}. Response status code: ${response.$metadata.httpStatusCode}`,
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
return Result.fail(
|
||||
`Could not send message to connection ${connection.props.connectionId} for user ${userUuid.value}. Error: ${
|
||||
(error as Error).message
|
||||
}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface SendMessageToClientDTO {
|
||||
userUuid: string
|
||||
message: string
|
||||
originatingSessionUuid?: string
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user