tering-serieuze-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.prettierrc ADDED
@@ -0,0 +1,2 @@
1
+ tabWidth: 4
2
+ printWidth: 120
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # Tering serieuze SDK
package/dist/index.mjs ADDED
@@ -0,0 +1,284 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => {
4
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ return value;
6
+ };
7
+
8
+ // src/cache/abstractCache.ts
9
+ import { get } from "stack-trace";
10
+ var CacheLifetime = /* @__PURE__ */ ((CacheLifetime2) => {
11
+ CacheLifetime2[CacheLifetime2["Day"] = 86400] = "Day";
12
+ CacheLifetime2[CacheLifetime2["Week"] = 604800] = "Week";
13
+ return CacheLifetime2;
14
+ })(CacheLifetime || {});
15
+ var AbstractCache = class {
16
+ constructor(cacheName) {
17
+ this.cacheName = cacheName;
18
+ }
19
+ getCacheKey() {
20
+ const errorMessage = "Could not determine cache key. Please provide a key manually.";
21
+ const trace = get();
22
+ if (trace.length < 3) {
23
+ throw new Error(errorMessage);
24
+ }
25
+ const functionName = get()[2].getFunctionName();
26
+ if (!functionName) {
27
+ throw new Error(errorMessage);
28
+ }
29
+ return functionName;
30
+ }
31
+ };
32
+
33
+ // src/cache/cookieCache.ts
34
+ var CookieCache = class extends AbstractCache {
35
+ cacheOrGet(callbackWhenEmpty, key, lifetime) {
36
+ return callbackWhenEmpty();
37
+ }
38
+ async get(key) {
39
+ return void 0;
40
+ }
41
+ set(key, value) {
42
+ }
43
+ remove(key) {
44
+ }
45
+ clear() {
46
+ }
47
+ };
48
+
49
+ // src/cache/filesystemCache.ts
50
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
51
+ import path from "path";
52
+ import os from "os";
53
+ var FilesystemCache = class extends AbstractCache {
54
+ initialized = false;
55
+ state = {};
56
+ async cacheOrGet(callbackWhenEmpty, key, lifetime) {
57
+ key = key ?? this.getCacheKey();
58
+ const cachedValue = await this.get(key);
59
+ if (cachedValue) {
60
+ return cachedValue;
61
+ }
62
+ const result = await callbackWhenEmpty();
63
+ this.set(key, result, lifetime);
64
+ return result;
65
+ }
66
+ async get(key) {
67
+ if (!this.initialized) {
68
+ await this._initialize();
69
+ }
70
+ const expires = this.state[key]?.expiresAt;
71
+ if (Object.keys(this.state).includes(key) && (expires === void 0 || expires > Date.now())) {
72
+ return this.state[key].value;
73
+ }
74
+ return void 0;
75
+ }
76
+ set(key, value, lifetime) {
77
+ const expiresAt = lifetime ? Date.now() + lifetime : void 0;
78
+ const content = { value };
79
+ if (expiresAt) {
80
+ content.expiresAt = expiresAt;
81
+ }
82
+ this.state[key] = content;
83
+ this.writeState();
84
+ }
85
+ remove(key) {
86
+ delete this.state[key];
87
+ this.writeState();
88
+ }
89
+ clear() {
90
+ this.state = {};
91
+ unlinkSync(this.getFilePath());
92
+ }
93
+ getFilePath() {
94
+ return path.join(os.homedir(), ".tss", `${this.cacheName}.json`);
95
+ }
96
+ _initialize() {
97
+ this.initialized = true;
98
+ if (existsSync(this.getFilePath())) {
99
+ this.state = JSON.parse(readFileSync(this.getFilePath(), "utf8"));
100
+ }
101
+ }
102
+ writeState() {
103
+ if (!existsSync(path.dirname(this.getFilePath()))) {
104
+ mkdirSync(path.dirname(this.getFilePath()));
105
+ }
106
+ writeFileSync(this.getFilePath(), JSON.stringify(this.state));
107
+ }
108
+ };
109
+
110
+ // src/cache/nullCache.ts
111
+ var NullCache = class extends AbstractCache {
112
+ cacheOrGet(callbackWhenEmpty, key, lifetime) {
113
+ return callbackWhenEmpty();
114
+ }
115
+ async get(key) {
116
+ return void 0;
117
+ }
118
+ set(key, value) {
119
+ }
120
+ remove(key) {
121
+ }
122
+ clear() {
123
+ }
124
+ };
125
+
126
+ // src/cache/onePasswordCache.ts
127
+ import { promisify } from "util";
128
+ import { exec } from "child_process";
129
+ var asyncExec = promisify(exec);
130
+ var OnePasswordCache = class extends AbstractCache {
131
+ cacheOrGet(callbackWhenEmpty, key, lifetime) {
132
+ throw new Error("Not implemented, use get() and set()");
133
+ }
134
+ async get(key) {
135
+ const { stdout, stderr } = await asyncExec(`op item get "${key}" --fields label=credential --format json`);
136
+ if (stderr) {
137
+ console.log(stderr);
138
+ throw new Error("ERROR");
139
+ }
140
+ return JSON.parse(stdout).value;
141
+ }
142
+ async set(key, value) {
143
+ let action = `op item edit '${key}' 'credential=${value}'`;
144
+ try {
145
+ const t = await this.get(key);
146
+ } catch (e) {
147
+ action = `op item create --category="API Credential" --title ${key} 'credential=${value}'`;
148
+ }
149
+ const { stdout, stderr } = await asyncExec(action);
150
+ if (stderr) {
151
+ console.log(stderr);
152
+ throw new Error("ERROR");
153
+ }
154
+ }
155
+ async remove(key) {
156
+ await asyncExec(`op item delete '${key}'`);
157
+ }
158
+ clear() {
159
+ }
160
+ };
161
+
162
+ // src/api/base.ts
163
+ var BaseModule = class {
164
+ constructor(api, cache) {
165
+ this.api = api;
166
+ this.cache = cache;
167
+ }
168
+ getCache() {
169
+ return this.cache;
170
+ }
171
+ setCache(cache) {
172
+ this.cache = cache;
173
+ }
174
+ };
175
+
176
+ // src/api/auth.ts
177
+ var _AuthModule = class extends BaseModule {
178
+ async getToken() {
179
+ return this.cache.get(_AuthModule.cacheKey);
180
+ }
181
+ async setToken(token) {
182
+ return this.cache.set(_AuthModule.cacheKey, token);
183
+ }
184
+ async removeToken() {
185
+ return this.cache.remove(_AuthModule.cacheKey);
186
+ }
187
+ };
188
+ var AuthModule = _AuthModule;
189
+ __publicField(AuthModule, "cacheKey", "TSS-TOKEN");
190
+
191
+ // src/api/jingle.ts
192
+ var JingleModule = class extends BaseModule {
193
+ async getGrouped() {
194
+ return this.cache.cacheOrGet(() => {
195
+ return this.api.fetch("jingle/grouped");
196
+ });
197
+ }
198
+ async getAll() {
199
+ return this.cache.cacheOrGet(() => {
200
+ return this.api.fetch("jingle");
201
+ });
202
+ }
203
+ async play(folder, file) {
204
+ console.log(`Playing jingle ${folder}/${file}`);
205
+ return this.api.post("jingle/play", { folder, file });
206
+ }
207
+ };
208
+
209
+ // src/api/user.ts
210
+ var UserModule = class extends BaseModule {
211
+ async get(userId) {
212
+ return this.cache.cacheOrGet(() => {
213
+ const userIdParam = userId ? `/${userId}` : "";
214
+ return this.api.fetch(`user${userIdParam}`);
215
+ });
216
+ }
217
+ };
218
+
219
+ // src/index.ts
220
+ import {
221
+ BadRequestException,
222
+ ForbiddenException,
223
+ NotFoundException,
224
+ UnauthorizedException,
225
+ UnprocessableEntityException
226
+ } from "@nestjs/common";
227
+ var CacheType = /* @__PURE__ */ ((CacheType2) => {
228
+ CacheType2["jingle"] = "jingle";
229
+ CacheType2["auth"] = "auth";
230
+ CacheType2["user"] = "user";
231
+ return CacheType2;
232
+ })(CacheType || {});
233
+ var TssApi = class {
234
+ auth;
235
+ jingle;
236
+ user;
237
+ constructor(cacheConfiguration) {
238
+ this.jingle = new JingleModule(this, cacheConfiguration?.jingle ?? new NullCache("jingle"));
239
+ this.auth = new AuthModule(this, cacheConfiguration?.auth ?? new NullCache("auth"));
240
+ this.user = new UserModule(this, cacheConfiguration?.user ?? new NullCache("user"));
241
+ }
242
+ async fetch(url, config = {}) {
243
+ config.method = config.method ?? "GET";
244
+ config.headers = {
245
+ ...config.headers,
246
+ "Content-Type": "application/json",
247
+ Authorization: `Bearer ${await this.auth.getToken()}`
248
+ };
249
+ const apiPrefix = process.env.API_URL ?? "https://localhost:8081";
250
+ const response = await fetch(apiPrefix + "/api/" + url, config);
251
+ switch (response.status) {
252
+ case 400:
253
+ throw new BadRequestException(response.statusText);
254
+ case 401:
255
+ throw new UnauthorizedException(response.statusText);
256
+ case 403:
257
+ throw new ForbiddenException(response.statusText);
258
+ case 404:
259
+ throw new NotFoundException(response.statusText);
260
+ case 422:
261
+ throw new UnprocessableEntityException(response.statusText);
262
+ }
263
+ return response.json();
264
+ }
265
+ async post(url, body) {
266
+ return this.fetch(url, {
267
+ method: "POST",
268
+ body: JSON.stringify(body)
269
+ });
270
+ }
271
+ };
272
+ export {
273
+ AbstractCache,
274
+ AuthModule,
275
+ CacheLifetime,
276
+ CacheType,
277
+ CookieCache,
278
+ FilesystemCache,
279
+ JingleModule,
280
+ NullCache,
281
+ OnePasswordCache,
282
+ TssApi,
283
+ UserModule
284
+ };
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cache/abstractCache.ts", "../src/cache/cookieCache.ts", "../src/cache/filesystemCache.ts", "../src/cache/nullCache.ts", "../src/cache/onePasswordCache.ts", "../src/api/base.ts", "../src/api/auth.ts", "../src/api/jingle.ts", "../src/api/user.ts", "../src/index.ts"],
4
+ "sourcesContent": ["import { get } from \"stack-trace\";\n\nexport enum CacheLifetime {\n Day = 60 * 60 * 24,\n Week = 60 * 60 * 24 * 7,\n}\n\nexport abstract class AbstractCache {\n constructor(protected cacheName: string) {}\n\n public abstract cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T>;\n\n public abstract get<T>(key: string): Promise<T | undefined>;\n\n public abstract set<T>(key: string, value: T, lifetime?: number): void;\n\n public abstract remove(key: string): void;\n\n public abstract clear(): void;\n\n public getCacheKey(): string {\n const errorMessage = \"Could not determine cache key. Please provide a key manually.\";\n const trace = get();\n if (trace.length < 3) {\n throw new Error(errorMessage);\n }\n\n const functionName = get()[2].getFunctionName();\n if (!functionName) {\n throw new Error(errorMessage);\n }\n\n return functionName;\n }\n}\n", "import { AbstractCache } from \"./abstractCache\";\n\nexport class CookieCache extends AbstractCache {\n public cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T> {\n // TODO implement\n return callbackWhenEmpty();\n }\n\n public async get<T>(key: string): Promise<T | undefined> {\n return undefined;\n }\n\n public set<T>(key: string, value: T): void {}\n\n public remove(key: string): void {}\n\n public clear(): void {}\n}\n", "import { AbstractCache } from \"./abstractCache\";\nimport { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\";\nimport path from \"path\";\nimport os from \"os\";\n\ntype CacheItem = {\n value: any;\n expiresAt?: number;\n};\n\nexport class FilesystemCache extends AbstractCache {\n protected initialized = false;\n\n protected state: { [key: string]: CacheItem } = {};\n\n public async cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T> {\n key = key ?? this.getCacheKey();\n\n const cachedValue = await this.get<T>(key);\n if (cachedValue) {\n return cachedValue;\n }\n\n const result = await callbackWhenEmpty();\n this.set(key, result, lifetime);\n return result;\n }\n\n public async get<T>(key: string): Promise<T | undefined> {\n if (!this.initialized) {\n await this._initialize();\n }\n\n const expires = this.state[key]?.expiresAt;\n if (Object.keys(this.state).includes(key) && (expires === undefined || expires > Date.now())) {\n return this.state[key].value;\n }\n\n return undefined;\n }\n\n public set<T>(key: string, value: T, lifetime?: number): void {\n const expiresAt = lifetime ? Date.now() + lifetime : undefined;\n const content: CacheItem = { value };\n if (expiresAt) {\n content.expiresAt = expiresAt;\n }\n this.state[key] = content;\n this.writeState();\n }\n\n public remove(key: string): void {\n delete this.state[key];\n this.writeState();\n }\n\n public clear(): void {\n this.state = {};\n unlinkSync(this.getFilePath());\n }\n\n public getFilePath() {\n return path.join(os.homedir(), \".tss\", `${this.cacheName}.json`);\n }\n\n protected _initialize() {\n this.initialized = true;\n if (existsSync(this.getFilePath())) {\n this.state = JSON.parse(readFileSync(this.getFilePath(), \"utf8\"));\n }\n }\n\n protected writeState() {\n if (!existsSync(path.dirname(this.getFilePath()))) {\n mkdirSync(path.dirname(this.getFilePath()));\n }\n\n writeFileSync(this.getFilePath(), JSON.stringify(this.state));\n }\n}\n", "import { AbstractCache } from \"./abstractCache\";\n\nexport class NullCache extends AbstractCache {\n public cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T> {\n return callbackWhenEmpty();\n }\n\n public async get<T>(key: string): Promise<T | undefined> {\n return undefined;\n }\n\n public set<T>(key: string, value: T): void {}\n\n public remove(key: string): void {}\n\n public clear(): void {}\n}\n", "import { AbstractCache } from \"./abstractCache\";\nimport { promisify } from \"util\";\nimport { exec } from \"child_process\";\n\nconst asyncExec = promisify(exec);\n\n// TODO error handling\nexport class OnePasswordCache extends AbstractCache {\n public cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T> {\n throw new Error(\"Not implemented, use get() and set()\");\n }\n\n public async get<T>(key: string): Promise<T | undefined> {\n const { stdout, stderr } = await asyncExec(`op item get \"${key}\" --fields label=credential --format json`);\n\n if (stderr) {\n console.log(stderr);\n throw new Error(\"ERROR\");\n }\n\n return JSON.parse(stdout).value;\n }\n\n public async set<T>(key: string, value: T): Promise<void> {\n let action = `op item edit '${key}' 'credential=${value}'`;\n\n try {\n const t = await this.get(key);\n } catch (e) {\n action = `op item create --category=\"API Credential\" --title ${key} 'credential=${value}'`;\n }\n\n const { stdout, stderr } = await asyncExec(action);\n\n if (stderr) {\n console.log(stderr);\n throw new Error(\"ERROR\");\n }\n }\n\n public async remove(key: string): Promise<void> {\n await asyncExec(`op item delete '${key}'`);\n }\n\n public clear(): void {\n // Loopje maken en heel 1password leeg maken?\n }\n}\n", "import { AbstractCache } from \"../cache\";\nimport { TssApi } from \"../index\";\n\nexport class BaseModule {\n constructor(protected api: TssApi, protected cache: AbstractCache) {}\n\n public getCache() {\n return this.cache;\n }\n\n public setCache(cache: AbstractCache) {\n this.cache = cache;\n }\n}\n", "import { BaseModule } from \"./base\";\n\nexport class AuthModule extends BaseModule {\n protected static cacheKey = \"TSS-TOKEN\";\n\n async getToken(): Promise<string | undefined> {\n return this.cache.get(AuthModule.cacheKey);\n }\n\n async setToken(token: string) {\n return this.cache.set(AuthModule.cacheKey, token);\n }\n\n async removeToken() {\n return this.cache.remove(AuthModule.cacheKey);\n }\n}\n", "import { JingleFolder, Jingle } from \"tering-serieuze-types\";\nimport { BaseModule } from \"./base\";\n\nexport class JingleModule extends BaseModule {\n async getGrouped() {\n return this.cache.cacheOrGet(() => {\n return this.api.fetch<JingleFolder[]>(\"jingle/grouped\");\n });\n }\n\n async getAll() {\n return this.cache.cacheOrGet(() => {\n return this.api.fetch<Jingle[]>(\"jingle\");\n });\n }\n\n async play(folder: string, file: string) {\n console.log(`Playing jingle ${folder}/${file}`);\n return this.api.post(\"jingle/play\", { folder, file });\n }\n}\n", "import { BaseModule } from \"./base\";\nimport { User } from \"tering-serieuze-types\";\n\nexport class UserModule extends BaseModule {\n async get(userId?: string) {\n return this.cache.cacheOrGet(() => {\n const userIdParam = userId ? `/${userId}` : \"\";\n return this.api.fetch<User>(`user${userIdParam}`);\n });\n }\n}\n", "import { AbstractCache, NullCache } from \"./cache\";\nimport { AuthModule, JingleModule, UserModule } from \"./api\";\nimport {\n BadRequestException,\n ForbiddenException,\n NotFoundException,\n UnauthorizedException,\n UnprocessableEntityException,\n} from \"@nestjs/common\";\n\nexport * from \"./api\";\nexport * from \"./cache\";\n\nexport interface SDKRequestInit extends RequestInit {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n}\n\nexport enum CacheType {\n jingle = \"jingle\",\n auth = \"auth\",\n user = \"user\",\n}\n\nexport type CacheConfiguration = {\n [key in keyof typeof CacheType]?: AbstractCache;\n};\n\nexport class TssApi {\n auth: AuthModule;\n\n jingle: JingleModule;\n\n user: UserModule;\n\n constructor(cacheConfiguration?: CacheConfiguration) {\n this.jingle = new JingleModule(this, cacheConfiguration?.jingle ?? new NullCache(\"jingle\"));\n this.auth = new AuthModule(this, cacheConfiguration?.auth ?? new NullCache(\"auth\"));\n this.user = new UserModule(this, cacheConfiguration?.user ?? new NullCache(\"user\"));\n }\n\n async fetch<T>(url: string, config: SDKRequestInit = {}) {\n config.method = config.method ?? \"GET\";\n config.headers = {\n ...config.headers,\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${await this.auth.getToken()}`,\n };\n\n const apiPrefix = process.env.API_URL ?? \"https://localhost:8081\";\n const response = await fetch(apiPrefix + \"/api/\" + url, config);\n switch (response.status) {\n case 400:\n throw new BadRequestException(response.statusText);\n case 401:\n throw new UnauthorizedException(response.statusText);\n case 403:\n throw new ForbiddenException(response.statusText);\n case 404:\n throw new NotFoundException(response.statusText);\n case 422:\n throw new UnprocessableEntityException(response.statusText);\n }\n\n return response.json() as Promise<T>;\n }\n\n async post<T>(url: string, body: any) {\n return this.fetch<T>(url, {\n method: \"POST\",\n body: JSON.stringify(body),\n });\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["CacheLifetime", "CacheType"]
7
+ }
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./src";
2
+ export * from "./src/api";
3
+ export * from "./src/cache";
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "tering-serieuze-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Teringserieuze sdk",
5
+ "author": "Frank",
6
+ "main": "dist/index.mjs",
7
+ "types": "index.d.ts",
8
+ "type": "module",
9
+ "scripts": {
10
+ "test": "vitest",
11
+ "coverage": "vitest run --coverage",
12
+ "dev": "esbuild src/index.ts --bundle --platform=node --outfile=dist/index.mjs --packages=external --sourcemap=external --format=esm --watch",
13
+ "build": "esbuild src/index.ts --bundle --platform=node --outfile=dist/index.mjs --packages=external --sourcemap=external --format=esm",
14
+ "webstorm-integration": "vitest --watch --reporter=dot --reporter=json --outputFile=.vitest-result.json"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+ssh://git@gitlab.com/tering-serieuze-shit/tering-serieuze-sdk.git"
19
+ },
20
+ "license": "ISC",
21
+ "bugs": {
22
+ "url": "https://gitlab.com/tering-serieuze-shit/tering-serieuze-sdk/issues"
23
+ },
24
+ "homepage": "https://gitlab.com/tering-serieuze-shit/tering-serieuze-sdk#readme",
25
+ "dependencies": {
26
+ "@nestjs/common": "^9.3.9",
27
+ "@types/node": "^18.11.18",
28
+ "stack-trace": "^1.0.0-pre2",
29
+ "tering-serieuze-types": "^1.8.4"
30
+ },
31
+ "devDependencies": {
32
+ "@types/stack-trace": "^0.0.30",
33
+ "@vitest/coverage-c8": "^0.28.5",
34
+ "esbuild": "^0.16.12",
35
+ "prettier": "^2.8.1",
36
+ "vitest": "^0.28.5"
37
+ }
38
+ }
@@ -0,0 +1,17 @@
1
+ import { BaseModule } from "./base";
2
+
3
+ export class AuthModule extends BaseModule {
4
+ protected static cacheKey = "TSS-TOKEN";
5
+
6
+ async getToken(): Promise<string | undefined> {
7
+ return this.cache.get(AuthModule.cacheKey);
8
+ }
9
+
10
+ async setToken(token: string) {
11
+ return this.cache.set(AuthModule.cacheKey, token);
12
+ }
13
+
14
+ async removeToken() {
15
+ return this.cache.remove(AuthModule.cacheKey);
16
+ }
17
+ }
@@ -0,0 +1,14 @@
1
+ import { AbstractCache } from "../cache";
2
+ import { TssApi } from "../index";
3
+
4
+ export class BaseModule {
5
+ constructor(protected api: TssApi, protected cache: AbstractCache) {}
6
+
7
+ public getCache() {
8
+ return this.cache;
9
+ }
10
+
11
+ public setCache(cache: AbstractCache) {
12
+ this.cache = cache;
13
+ }
14
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./auth";
2
+ export * from "./jingle";
3
+ export * from "./user";
@@ -0,0 +1,21 @@
1
+ import { JingleFolder, Jingle } from "tering-serieuze-types";
2
+ import { BaseModule } from "./base";
3
+
4
+ export class JingleModule extends BaseModule {
5
+ async getGrouped() {
6
+ return this.cache.cacheOrGet(() => {
7
+ return this.api.fetch<JingleFolder[]>("jingle/grouped");
8
+ });
9
+ }
10
+
11
+ async getAll() {
12
+ return this.cache.cacheOrGet(() => {
13
+ return this.api.fetch<Jingle[]>("jingle");
14
+ });
15
+ }
16
+
17
+ async play(folder: string, file: string) {
18
+ console.log(`Playing jingle ${folder}/${file}`);
19
+ return this.api.post("jingle/play", { folder, file });
20
+ }
21
+ }
@@ -0,0 +1,11 @@
1
+ import { BaseModule } from "./base";
2
+ import { User } from "tering-serieuze-types";
3
+
4
+ export class UserModule extends BaseModule {
5
+ async get(userId?: string) {
6
+ return this.cache.cacheOrGet(() => {
7
+ const userIdParam = userId ? `/${userId}` : "";
8
+ return this.api.fetch<User>(`user${userIdParam}`);
9
+ });
10
+ }
11
+ }
@@ -0,0 +1,35 @@
1
+ import { get } from "stack-trace";
2
+
3
+ export enum CacheLifetime {
4
+ Day = 60 * 60 * 24,
5
+ Week = 60 * 60 * 24 * 7,
6
+ }
7
+
8
+ export abstract class AbstractCache {
9
+ constructor(protected cacheName: string) {}
10
+
11
+ public abstract cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T>;
12
+
13
+ public abstract get<T>(key: string): Promise<T | undefined>;
14
+
15
+ public abstract set<T>(key: string, value: T, lifetime?: number): void;
16
+
17
+ public abstract remove(key: string): void;
18
+
19
+ public abstract clear(): void;
20
+
21
+ public getCacheKey(): string {
22
+ const errorMessage = "Could not determine cache key. Please provide a key manually.";
23
+ const trace = get();
24
+ if (trace.length < 3) {
25
+ throw new Error(errorMessage);
26
+ }
27
+
28
+ const functionName = get()[2].getFunctionName();
29
+ if (!functionName) {
30
+ throw new Error(errorMessage);
31
+ }
32
+
33
+ return functionName;
34
+ }
35
+ }
@@ -0,0 +1,18 @@
1
+ import { AbstractCache } from "./abstractCache";
2
+
3
+ export class CookieCache extends AbstractCache {
4
+ public cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T> {
5
+ // TODO implement
6
+ return callbackWhenEmpty();
7
+ }
8
+
9
+ public async get<T>(key: string): Promise<T | undefined> {
10
+ return undefined;
11
+ }
12
+
13
+ public set<T>(key: string, value: T): void {}
14
+
15
+ public remove(key: string): void {}
16
+
17
+ public clear(): void {}
18
+ }
@@ -0,0 +1,80 @@
1
+ import { AbstractCache } from "./abstractCache";
2
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+
6
+ type CacheItem = {
7
+ value: any;
8
+ expiresAt?: number;
9
+ };
10
+
11
+ export class FilesystemCache extends AbstractCache {
12
+ protected initialized = false;
13
+
14
+ protected state: { [key: string]: CacheItem } = {};
15
+
16
+ public async cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T> {
17
+ key = key ?? this.getCacheKey();
18
+
19
+ const cachedValue = await this.get<T>(key);
20
+ if (cachedValue) {
21
+ return cachedValue;
22
+ }
23
+
24
+ const result = await callbackWhenEmpty();
25
+ this.set(key, result, lifetime);
26
+ return result;
27
+ }
28
+
29
+ public async get<T>(key: string): Promise<T | undefined> {
30
+ if (!this.initialized) {
31
+ await this._initialize();
32
+ }
33
+
34
+ const expires = this.state[key]?.expiresAt;
35
+ if (Object.keys(this.state).includes(key) && (expires === undefined || expires > Date.now())) {
36
+ return this.state[key].value;
37
+ }
38
+
39
+ return undefined;
40
+ }
41
+
42
+ public set<T>(key: string, value: T, lifetime?: number): void {
43
+ const expiresAt = lifetime ? Date.now() + lifetime : undefined;
44
+ const content: CacheItem = { value };
45
+ if (expiresAt) {
46
+ content.expiresAt = expiresAt;
47
+ }
48
+ this.state[key] = content;
49
+ this.writeState();
50
+ }
51
+
52
+ public remove(key: string): void {
53
+ delete this.state[key];
54
+ this.writeState();
55
+ }
56
+
57
+ public clear(): void {
58
+ this.state = {};
59
+ unlinkSync(this.getFilePath());
60
+ }
61
+
62
+ public getFilePath() {
63
+ return path.join(os.homedir(), ".tss", `${this.cacheName}.json`);
64
+ }
65
+
66
+ protected _initialize() {
67
+ this.initialized = true;
68
+ if (existsSync(this.getFilePath())) {
69
+ this.state = JSON.parse(readFileSync(this.getFilePath(), "utf8"));
70
+ }
71
+ }
72
+
73
+ protected writeState() {
74
+ if (!existsSync(path.dirname(this.getFilePath()))) {
75
+ mkdirSync(path.dirname(this.getFilePath()));
76
+ }
77
+
78
+ writeFileSync(this.getFilePath(), JSON.stringify(this.state));
79
+ }
80
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./abstractCache";
2
+ export * from "./cookieCache";
3
+ export * from "./filesystemCache";
4
+ export * from "./nullCache";
5
+ export * from "./onePasswordCache";
@@ -0,0 +1,17 @@
1
+ import { AbstractCache } from "./abstractCache";
2
+
3
+ export class NullCache extends AbstractCache {
4
+ public cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T> {
5
+ return callbackWhenEmpty();
6
+ }
7
+
8
+ public async get<T>(key: string): Promise<T | undefined> {
9
+ return undefined;
10
+ }
11
+
12
+ public set<T>(key: string, value: T): void {}
13
+
14
+ public remove(key: string): void {}
15
+
16
+ public clear(): void {}
17
+ }
@@ -0,0 +1,48 @@
1
+ import { AbstractCache } from "./abstractCache";
2
+ import { promisify } from "util";
3
+ import { exec } from "child_process";
4
+
5
+ const asyncExec = promisify(exec);
6
+
7
+ // TODO error handling
8
+ export class OnePasswordCache extends AbstractCache {
9
+ public cacheOrGet<T>(callbackWhenEmpty: () => Promise<T>, key?: string, lifetime?: number): Promise<T> {
10
+ throw new Error("Not implemented, use get() and set()");
11
+ }
12
+
13
+ public async get<T>(key: string): Promise<T | undefined> {
14
+ const { stdout, stderr } = await asyncExec(`op item get "${key}" --fields label=credential --format json`);
15
+
16
+ if (stderr) {
17
+ console.log(stderr);
18
+ throw new Error("ERROR");
19
+ }
20
+
21
+ return JSON.parse(stdout).value;
22
+ }
23
+
24
+ public async set<T>(key: string, value: T): Promise<void> {
25
+ let action = `op item edit '${key}' 'credential=${value}'`;
26
+
27
+ try {
28
+ const t = await this.get(key);
29
+ } catch (e) {
30
+ action = `op item create --category="API Credential" --title ${key} 'credential=${value}'`;
31
+ }
32
+
33
+ const { stdout, stderr } = await asyncExec(action);
34
+
35
+ if (stderr) {
36
+ console.log(stderr);
37
+ throw new Error("ERROR");
38
+ }
39
+ }
40
+
41
+ public async remove(key: string): Promise<void> {
42
+ await asyncExec(`op item delete '${key}'`);
43
+ }
44
+
45
+ public clear(): void {
46
+ // Loopje maken en heel 1password leeg maken?
47
+ }
48
+ }
package/src/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { AbstractCache, NullCache } from "./cache";
2
+ import { AuthModule, JingleModule, UserModule } from "./api";
3
+ import {
4
+ BadRequestException,
5
+ ForbiddenException,
6
+ NotFoundException,
7
+ UnauthorizedException,
8
+ UnprocessableEntityException,
9
+ } from "@nestjs/common";
10
+
11
+ export * from "./api";
12
+ export * from "./cache";
13
+
14
+ export interface SDKRequestInit extends RequestInit {
15
+ method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
16
+ }
17
+
18
+ export enum CacheType {
19
+ jingle = "jingle",
20
+ auth = "auth",
21
+ user = "user",
22
+ }
23
+
24
+ export type CacheConfiguration = {
25
+ [key in keyof typeof CacheType]?: AbstractCache;
26
+ };
27
+
28
+ export class TssApi {
29
+ auth: AuthModule;
30
+
31
+ jingle: JingleModule;
32
+
33
+ user: UserModule;
34
+
35
+ constructor(cacheConfiguration?: CacheConfiguration) {
36
+ this.jingle = new JingleModule(this, cacheConfiguration?.jingle ?? new NullCache("jingle"));
37
+ this.auth = new AuthModule(this, cacheConfiguration?.auth ?? new NullCache("auth"));
38
+ this.user = new UserModule(this, cacheConfiguration?.user ?? new NullCache("user"));
39
+ }
40
+
41
+ async fetch<T>(url: string, config: SDKRequestInit = {}) {
42
+ config.method = config.method ?? "GET";
43
+ config.headers = {
44
+ ...config.headers,
45
+ "Content-Type": "application/json",
46
+ Authorization: `Bearer ${await this.auth.getToken()}`,
47
+ };
48
+
49
+ const apiPrefix = process.env.API_URL ?? "https://localhost:8081";
50
+ const response = await fetch(apiPrefix + "/api/" + url, config);
51
+ switch (response.status) {
52
+ case 400:
53
+ throw new BadRequestException(response.statusText);
54
+ case 401:
55
+ throw new UnauthorizedException(response.statusText);
56
+ case 403:
57
+ throw new ForbiddenException(response.statusText);
58
+ case 404:
59
+ throw new NotFoundException(response.statusText);
60
+ case 422:
61
+ throw new UnprocessableEntityException(response.statusText);
62
+ }
63
+
64
+ return response.json() as Promise<T>;
65
+ }
66
+
67
+ async post<T>(url: string, body: any) {
68
+ return this.fetch<T>(url, {
69
+ method: "POST",
70
+ body: JSON.stringify(body),
71
+ });
72
+ }
73
+ }
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { BaseModule } from "../../src/api/base";
3
+ import { NullCache } from "../../src/cache";
4
+ import { TssApi } from "../../src/index";
5
+
6
+ describe("Base module", () => {
7
+ it("returns the cache", () => {
8
+ const cache = new NullCache("base");
9
+ const base = new BaseModule(new TssApi(), cache);
10
+ expect(base.getCache()).toBe(cache);
11
+ });
12
+
13
+ it("sets the cache", () => {
14
+ const cache = new NullCache("base");
15
+ const base = new BaseModule(new TssApi(), cache);
16
+ const newCache = new NullCache("base");
17
+ base.setCache(newCache);
18
+ expect(base.getCache()).toBe(newCache);
19
+ });
20
+ });
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { TssApi } from "../../src";
3
+ import { NullCache, OnePasswordCache } from "../../src/cache";
4
+
5
+ describe("Jingle API", () => {
6
+ const api = new TssApi({
7
+ jingle: new NullCache("jingle"),
8
+ auth: new OnePasswordCache("TSS-TOKEN"),
9
+ });
10
+
11
+ it("Retrieves jingles when calling getGrouped", async () => {
12
+ const items = await api.jingle.getGrouped();
13
+
14
+ expect(items.length).toBeGreaterThan(0);
15
+ expect(items[0].folder).not.toBe("");
16
+ expect(items[0].jingles).toBeInstanceOf(Array);
17
+ expect(items[0].jingles[0].file).not.toBe("");
18
+ });
19
+
20
+ it("Retrieves jingles when calling getAll", async () => {
21
+ const items = await api.jingle.getAll();
22
+
23
+ expect(items.length).toBeGreaterThan(0);
24
+ expect(items[0].folder).not.toBe("");
25
+ expect(items[0].file).not.toBe("");
26
+ });
27
+
28
+ it("Plays a jingle", async () => {
29
+ const result = await api.jingle.play("keeskankerkachel", "aan.mp3");
30
+ // TODO fix dit
31
+ expect(result.success).toBe(true);
32
+ });
33
+ });
@@ -0,0 +1,141 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { FilesystemCache } from "../../src/cache";
3
+ import { existsSync, unlinkSync, writeFileSync } from "fs";
4
+ import path from "path";
5
+
6
+ describe("filesystemCache", () => {
7
+ let cache: FilesystemCache;
8
+
9
+ beforeEach(() => {
10
+ cache = new FilesystemCache(`fs-test-${Math.random()}`);
11
+ });
12
+
13
+ afterEach(() => {
14
+ if (existsSync(cache.getFilePath())) {
15
+ unlinkSync(cache.getFilePath());
16
+ }
17
+
18
+ vi.restoreAllMocks();
19
+ });
20
+
21
+ it("Should call the callback only once", async () => {
22
+ const callback = vi.fn(() => Promise.resolve(1));
23
+ const result = await cache.cacheOrGet(callback, "key");
24
+ expect(result).toBe(1);
25
+ expect(callback).toBeCalledTimes(1);
26
+
27
+ await Promise.all([
28
+ cache.cacheOrGet(callback, "key"),
29
+ cache.cacheOrGet(callback, "key"),
30
+ cache.cacheOrGet(callback, "key"),
31
+ ]);
32
+ expect(callback).toBeCalledTimes(1);
33
+ });
34
+
35
+ it("Should call the callback only once when using the same key", async () => {
36
+ const key = "test";
37
+ const callback = vi.fn(() => Promise.resolve(1));
38
+ const result = await cache.cacheOrGet(callback, key);
39
+ expect(result).toBe(1);
40
+ expect(callback).toBeCalledTimes(1);
41
+
42
+ await cache.cacheOrGet(callback, key);
43
+ expect(callback).toBeCalledTimes(1);
44
+ });
45
+
46
+ it("Should call the callback twice when using different keys", async () => {
47
+ const callback = vi.fn(() => Promise.resolve(1));
48
+ const result = await cache.cacheOrGet(callback, "key");
49
+ expect(result).toBe(1);
50
+ expect(callback).toBeCalledTimes(1);
51
+
52
+ await cache.cacheOrGet(callback, `key-${Math.random()}`);
53
+ expect(callback).toBeCalledTimes(2);
54
+ });
55
+
56
+ it("Should throw an error when no key is passed and the function name can not be determined", async () => {
57
+ const callback = () => Promise.resolve(1);
58
+ await expect(cache.cacheOrGet(callback)).rejects.toThrowError();
59
+ });
60
+
61
+ it("Should use the function name as key, when no key is passed", async () => {
62
+ let expected: number = 0;
63
+ await (async function doeHet() {
64
+ expected = await cache.cacheOrGet(() => Promise.resolve(50));
65
+ })();
66
+
67
+ const nonCalledFunction = vi.fn(() => Promise.resolve(1));
68
+ const result = await cache.cacheOrGet(nonCalledFunction, "doeHet");
69
+ expect(result).toBeGreaterThan(0);
70
+ expect(result).toBe(expected);
71
+ expect(nonCalledFunction).toBeCalledTimes(0);
72
+ });
73
+
74
+ it("Should return the set value", async () => {
75
+ const key = "test";
76
+ const value = 1;
77
+ cache.set(key, value);
78
+ const result = await cache.cacheOrGet(() => Promise.resolve(2), key);
79
+ expect(result).toBe(value);
80
+ });
81
+
82
+ it("Should be able to remove a value", async () => {
83
+ const key = "test";
84
+ const otherKey = "test2";
85
+ const correctValue = 123;
86
+ const otherCorrectValue = 456;
87
+ cache.set(key, 1);
88
+ cache.set(otherKey, otherCorrectValue);
89
+ cache.remove(key);
90
+
91
+ const result = await cache.cacheOrGet(() => Promise.resolve(correctValue), key);
92
+ expect(result).toBe(correctValue);
93
+
94
+ const cachedValue = await cache.cacheOrGet(() => Promise.resolve(1), otherKey);
95
+ expect(cachedValue).toBe(otherCorrectValue);
96
+ });
97
+
98
+ it("Should be able to clear the cache", async () => {
99
+ expect(existsSync(cache.getFilePath())).toBe(false);
100
+ cache.set("test", 1);
101
+ expect(existsSync(cache.getFilePath())).toBe(true);
102
+ cache.clear();
103
+ expect(existsSync(cache.getFilePath())).toBe(false);
104
+ });
105
+
106
+ it("Should use the cache when making a second instance", async () => {
107
+ const instance = new FilesystemCache("testfile2");
108
+ const callback = vi.fn(() => Promise.resolve(123));
109
+ await instance.cacheOrGet(callback, "test");
110
+
111
+ const instance2 = new FilesystemCache("testfile2");
112
+ await instance2.cacheOrGet(callback, "test");
113
+
114
+ expect(callback).toBeCalledTimes(1);
115
+
116
+ instance2.clear();
117
+ });
118
+
119
+ it("Should use the cache if a cache file already exists", async () => {
120
+ cache.set("test", 123);
121
+
122
+ const cacheName = path.basename(cache.getFilePath(), path.extname(cache.getFilePath()));
123
+ const instance = new FilesystemCache(cacheName);
124
+ const callback = vi.fn(() => Promise.resolve(789));
125
+
126
+ const result = await instance.cacheOrGet(callback, "test");
127
+ expect(result).toBe(123);
128
+
129
+ instance.clear();
130
+ });
131
+
132
+ it("Should be able to set a custom cache lifetime", async () => {
133
+ const result = await cache.cacheOrGet(() => Promise.resolve(1), "test", 1);
134
+ const result2 = await cache.cacheOrGet(() => Promise.resolve(1), "test", 1);
135
+ expect(result).toBe(1);
136
+ expect(result2).toBe(1);
137
+ await new Promise((resolve) => setTimeout(resolve, 1100));
138
+ const result3 = await cache.cacheOrGet(() => Promise.resolve(2), "test", 1);
139
+ expect(result3).toBe(2);
140
+ });
141
+ });
@@ -0,0 +1,47 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { NullCache } from "../../src/cache";
3
+
4
+ describe("NullCache", () => {
5
+ const cache = new NullCache("nullCache");
6
+
7
+ it("should return the same value", async () => {
8
+ const input = "hoi";
9
+ const output = await cache.cacheOrGet(() => Promise.resolve(input));
10
+ expect(output).toBe(input);
11
+ });
12
+
13
+ it("should return the same value when using a key", async () => {
14
+ const input = "hoi";
15
+ const callback = vi.fn(() => Promise.resolve(input));
16
+ const output = await cache.cacheOrGet(callback, "key");
17
+ expect(input).toBe(output);
18
+ expect(callback).toBeCalledTimes(1);
19
+ });
20
+
21
+ it("should not do anything when calling set", async () => {
22
+ const key = "key";
23
+ const input = "hoi";
24
+ cache.set(key, "invalid value");
25
+ const output = await cache.cacheOrGet(() => Promise.resolve(input), key);
26
+ expect(input).toBe(output);
27
+ });
28
+
29
+ it("Can't test remove and clear", () => {
30
+ const coverage = 100;
31
+ cache.remove("test");
32
+ cache.clear();
33
+ expect(coverage).toBe(100);
34
+ });
35
+
36
+ it("Should throw an error when no key is provided", () => {
37
+ let catchedException = false;
38
+ try {
39
+ cache.getCacheKey();
40
+ } catch (e) {
41
+ catchedException = true;
42
+ }
43
+
44
+ // Can't use expect().toThrow() since it uses a named function, which screws up the context
45
+ expect(catchedException).toBe(true);
46
+ });
47
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "rootDir": "src",
4
+ "outDir": "dist",
5
+ "strict": true,
6
+ "target": "ESNext",
7
+ "module": "ES2022",
8
+ "sourceMap": true,
9
+ "esModuleInterop": true,
10
+ "moduleResolution": "Node",
11
+ "strictNullChecks": true,
12
+ "noImplicitAny": true
13
+ }
14
+ }