@centia-io/sdk 0.0.27 → 0.0.28

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 MapCentia ApS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,588 @@
1
+
2
+ //#region src/util/jwt-decode.ts
3
+ var InvalidTokenError = class extends Error {};
4
+ InvalidTokenError.prototype.name = "InvalidTokenError";
5
+ function b64DecodeUnicode(str) {
6
+ return decodeURIComponent(atob(str).replace(/(.)/g, (m, p) => {
7
+ let code = p.charCodeAt(0).toString(16).toUpperCase();
8
+ if (code.length < 2) code = "0" + code;
9
+ return "%" + code;
10
+ }));
11
+ }
12
+ function base64UrlDecode(str) {
13
+ let output = str.replace(/-/g, "+").replace(/_/g, "/");
14
+ switch (output.length % 4) {
15
+ case 0: break;
16
+ case 2:
17
+ output += "==";
18
+ break;
19
+ case 3:
20
+ output += "=";
21
+ break;
22
+ default: throw new Error("base64 string is not of the correct length");
23
+ }
24
+ try {
25
+ return b64DecodeUnicode(output);
26
+ } catch (err) {
27
+ return atob(output);
28
+ }
29
+ }
30
+ function jwtDecode(token, options) {
31
+ if (typeof token !== "string") throw new InvalidTokenError("Invalid token specified: must be a string");
32
+ options ||= {};
33
+ const pos = options.header === true ? 0 : 1;
34
+ const part = token.split(".")[pos];
35
+ if (typeof part !== "string") throw new InvalidTokenError(`Invalid token specified: missing part #${pos + 1}`);
36
+ let decoded;
37
+ try {
38
+ decoded = base64UrlDecode(part);
39
+ } catch (e) {
40
+ throw new InvalidTokenError(`Invalid token specified: invalid base64 for part #${pos + 1} (${e.message})`);
41
+ }
42
+ try {
43
+ return JSON.parse(decoded);
44
+ } catch (e) {
45
+ throw new InvalidTokenError(`Invalid token specified: invalid json for part #${pos + 1} (${e.message})`);
46
+ }
47
+ }
48
+
49
+ //#endregion
50
+ //#region src/util/storage.ts
51
+ var MemoryStorage = class {
52
+ constructor() {
53
+ this.store = /* @__PURE__ */ new Map();
54
+ }
55
+ getItem(key) {
56
+ return this.store.has(key) ? this.store.get(key) : null;
57
+ }
58
+ setItem(key, value) {
59
+ this.store.set(key, String(value));
60
+ }
61
+ removeItem(key) {
62
+ this.store.delete(key);
63
+ }
64
+ };
65
+ let cached = null;
66
+ function getStorage() {
67
+ if (cached) return cached;
68
+ try {
69
+ const g$1 = typeof globalThis !== "undefined" ? globalThis : window;
70
+ if (g$1 && g$1.localStorage && typeof g$1.localStorage.getItem === "function") {
71
+ cached = g$1.localStorage;
72
+ return cached;
73
+ }
74
+ } catch (e) {}
75
+ const g = typeof globalThis !== "undefined" ? globalThis : {};
76
+ if (!g.__gc2_memory_storage) g.__gc2_memory_storage = new MemoryStorage();
77
+ cached = g.__gc2_memory_storage;
78
+ return cached;
79
+ }
80
+
81
+ //#endregion
82
+ //#region src/util/utils.ts
83
+ const generatePkceChallenge = async () => {
84
+ const generateRandomString = () => {
85
+ const array = new Uint32Array(28);
86
+ crypto.getRandomValues(array);
87
+ return Array.from(array, (dec) => ("0" + dec.toString(16)).substr(-2)).join("");
88
+ };
89
+ const sha256 = (plain) => {
90
+ const data = new TextEncoder().encode(plain);
91
+ return crypto.subtle.digest("SHA-256", data);
92
+ };
93
+ const base64urlEncode = (str) => {
94
+ return btoa(String.fromCharCode.apply(null, [...new Uint8Array(str)])).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
95
+ };
96
+ async function pkceChallengeFromVerifier(v) {
97
+ return base64urlEncode(await sha256(v));
98
+ }
99
+ const { state, codeVerifier } = {
100
+ state: generateRandomString(),
101
+ codeVerifier: generateRandomString()
102
+ };
103
+ return {
104
+ state,
105
+ codeVerifier,
106
+ codeChallenge: await pkceChallengeFromVerifier(codeVerifier)
107
+ };
108
+ };
109
+ const isTokenExpired = (token) => {
110
+ let isJwtExpired = false;
111
+ const { exp } = jwtDecode(token);
112
+ const currentTime = (/* @__PURE__ */ new Date()).getTime() / 1e3;
113
+ if (exp) {
114
+ if (currentTime > exp) isJwtExpired = true;
115
+ }
116
+ return isJwtExpired;
117
+ };
118
+ const claims = (token) => {
119
+ return jwtDecode(token);
120
+ };
121
+ const isLogin = async (gc2) => {
122
+ const { accessToken, refreshToken } = getTokens();
123
+ if (!accessToken && !refreshToken) return false;
124
+ if (!accessToken || accessToken && isTokenExpired(accessToken)) {
125
+ if (refreshToken && isTokenExpired(refreshToken)) {
126
+ clearTokens();
127
+ clearOptions();
128
+ throw new Error("Refresh token has expired. Please login again.");
129
+ }
130
+ if (refreshToken) try {
131
+ const data = await gc2.getRefreshToken(refreshToken);
132
+ setTokens({
133
+ accessToken: data.access_token,
134
+ refreshToken,
135
+ idToken: data?.id_token
136
+ });
137
+ console.log("Access token refreshed");
138
+ } catch (e) {
139
+ throw new Error("Could not get refresh token.");
140
+ }
141
+ }
142
+ return true;
143
+ };
144
+ const setTokens = (tokens) => {
145
+ getStorage().setItem("gc2_tokens", JSON.stringify({
146
+ "accessToken": tokens.accessToken,
147
+ "refreshToken": tokens.refreshToken,
148
+ "idToken": tokens?.idToken || ""
149
+ }));
150
+ };
151
+ const getTokens = () => {
152
+ const str = getStorage().getItem("gc2_tokens");
153
+ const tokens = str ? JSON.parse(str) : {};
154
+ return {
155
+ accessToken: tokens?.accessToken || "",
156
+ refreshToken: tokens?.refreshToken || "",
157
+ idToken: tokens?.idToken || ""
158
+ };
159
+ };
160
+ const setOptions = (options) => {
161
+ getStorage().setItem("gc2_options", JSON.stringify({
162
+ "clientId": options.clientId,
163
+ "host": options.host,
164
+ "redirectUri": options.redirectUri
165
+ }));
166
+ };
167
+ const getOptions = () => {
168
+ const str = getStorage().getItem("gc2_options");
169
+ const options = str ? JSON.parse(str) : {};
170
+ return {
171
+ clientId: options?.clientId || "",
172
+ host: options?.host || "",
173
+ redirectUri: options?.redirectUri || ""
174
+ };
175
+ };
176
+ const clearTokens = () => {
177
+ getStorage().removeItem("gc2_tokens");
178
+ };
179
+ const clearOptions = () => {
180
+ getStorage().removeItem("gc2_options");
181
+ };
182
+ const getNonce = () => {
183
+ return getStorage().getItem("gc2_nonce");
184
+ };
185
+ const clearNonce = () => {
186
+ getStorage().removeItem("gc2_nonce");
187
+ };
188
+
189
+ //#endregion
190
+ //#region src/services/gc2.services.ts
191
+ var Gc2Service = class {
192
+ constructor(options) {
193
+ this.options = options;
194
+ this.host = options.host;
195
+ }
196
+ isCodeFlowOptions(options) {
197
+ return "redirectUri" in options;
198
+ }
199
+ isPasswordFlowOptions(options) {
200
+ return "username" in options;
201
+ }
202
+ buildUrl(path) {
203
+ if (path.startsWith("http://") || path.startsWith("https://")) return path;
204
+ return `${this.host}${path}`;
205
+ }
206
+ async request(url, method, body, contentType = "application/json") {
207
+ const headers = { "Content-Type": contentType };
208
+ let payload;
209
+ if (contentType === "application/json") payload = JSON.stringify(body);
210
+ else payload = new URLSearchParams(body).toString();
211
+ const response = await fetch(url, {
212
+ method,
213
+ headers,
214
+ body: payload
215
+ });
216
+ if (!response.ok) {
217
+ const errText = await response.text();
218
+ throw new Error(`HTTP error ${response.status}: ${errText}`);
219
+ }
220
+ return response.json();
221
+ }
222
+ async getDeviceCode() {
223
+ const path = this.options.deviceUri ?? `${this.host}/api/v4/oauth/device`;
224
+ return this.request(this.buildUrl(path), "POST", { client_id: this.options.clientId });
225
+ }
226
+ async pollToken(deviceCode, interval) {
227
+ const path = this.options.tokenUri ?? `${this.host}/api/v4/oauth`;
228
+ const getToken = async () => {
229
+ try {
230
+ return await this.request(this.buildUrl(path), "POST", {
231
+ client_id: this.options.clientId,
232
+ device_code: deviceCode,
233
+ grant_type: "device_code"
234
+ });
235
+ } catch (e) {
236
+ const err = JSON.parse(e.message.split(": ")[1]);
237
+ if (err.error === "authorization_pending") return null;
238
+ return err.error_description;
239
+ }
240
+ };
241
+ let response = await getToken();
242
+ while (response === null) {
243
+ await new Promise((resolve) => setTimeout(resolve, interval * 1100));
244
+ response = await getToken();
245
+ }
246
+ if (typeof response === "string") throw new Error(response);
247
+ return response;
248
+ }
249
+ getAuthorizationCodeURL(codeChallenge, state) {
250
+ let redirectUri;
251
+ if (this.isCodeFlowOptions(this.options)) redirectUri = this.options.redirectUri;
252
+ else throw new Error("CodeFlow options required for this operation");
253
+ const base = this.options.authUri ?? `${this.host}/auth/`;
254
+ const params = new URLSearchParams();
255
+ const nonce = getNonce();
256
+ params.set("response_type", "code");
257
+ params.set("client_id", this.options.clientId);
258
+ params.set("redirect_uri", redirectUri);
259
+ params.set("state", state);
260
+ params.set("code_challenge", codeChallenge);
261
+ params.set("code_challenge_method", "S256");
262
+ if (nonce) params.set("nonce", nonce);
263
+ if (this.options.scope) params.set("scope", this.options.scope);
264
+ return `${base}?${params.toString()}`;
265
+ }
266
+ async getAuthorizationCodeToken(code, codeVerifier) {
267
+ let redirectUri;
268
+ if (this.isCodeFlowOptions(this.options)) redirectUri = this.options.redirectUri;
269
+ else throw new Error("CodeFlow options required for this operation");
270
+ const path = this.options.tokenUri ?? `${this.host}/api/v4/oauth`;
271
+ return this.request(this.buildUrl(path), "POST", {
272
+ client_id: this.options.clientId,
273
+ redirect_uri: redirectUri,
274
+ grant_type: "authorization_code",
275
+ code,
276
+ code_verifier: codeVerifier
277
+ }, "application/x-www-form-urlencoded");
278
+ }
279
+ async getPasswordToken() {
280
+ let username, password, database;
281
+ if (this.isPasswordFlowOptions(this.options)) {
282
+ username = this.options.username;
283
+ password = this.options.password;
284
+ database = this.options.database;
285
+ } else throw new Error("PasswordFlow options required for this operation");
286
+ const path = `${this.host}/api/v4/oauth`;
287
+ return this.request(this.buildUrl(path), "POST", {
288
+ client_id: this.options.clientId,
289
+ grant_type: "password",
290
+ username,
291
+ password,
292
+ database
293
+ });
294
+ }
295
+ async getRefreshToken(token) {
296
+ const path = this.options.tokenUri ?? `${this.host}/api/v4/oauth`;
297
+ return this.request(this.buildUrl(path), "POST", {
298
+ client_id: this.options.clientId,
299
+ grant_type: "refresh_token",
300
+ refresh_token: token
301
+ });
302
+ }
303
+ getSignOutURL() {
304
+ let redirectUri;
305
+ if (this.isCodeFlowOptions(this.options)) redirectUri = this.options.redirectUri;
306
+ else throw new Error("CodeFlow options required for this operation");
307
+ const params = new URLSearchParams({ redirect_uri: redirectUri });
308
+ return this.options.logoutUri ?? `${this.host}/signout?${params.toString()}`;
309
+ }
310
+ };
311
+
312
+ //#endregion
313
+ //#region src/CodeFlow.ts
314
+ var CodeFlow = class {
315
+ constructor(options) {
316
+ this.options = options;
317
+ this.service = new Gc2Service(options);
318
+ }
319
+ async redirectHandle() {
320
+ const url = window.location.search;
321
+ const queryParams = new URLSearchParams(url);
322
+ if (queryParams.get("error")) throw new Error(`Failed to redirect: ${url}`);
323
+ const code = queryParams.get("code");
324
+ if (code) {
325
+ if (queryParams.get("state") !== getStorage().getItem("state")) throw new Error("Possible CSRF attack. Aborting login!");
326
+ try {
327
+ const { access_token, refresh_token, id_token } = await this.service.getAuthorizationCodeToken(code, getStorage().getItem("codeVerifier"));
328
+ setTokens({
329
+ accessToken: access_token,
330
+ refreshToken: refresh_token,
331
+ idToken: id_token
332
+ });
333
+ setOptions({
334
+ clientId: this.options.clientId,
335
+ host: this.options.host,
336
+ redirectUri: this.options.redirectUri
337
+ });
338
+ getStorage().removeItem("state");
339
+ getStorage().removeItem("codeVerifier");
340
+ const params = new URLSearchParams(window.location.search);
341
+ params.delete("code");
342
+ params.delete("state");
343
+ const loc = window.location;
344
+ const newUrl = loc.origin + loc.pathname + (params.size > 0 ? "?" + params.toString() : "");
345
+ history.pushState(null, "", newUrl);
346
+ return Promise.resolve(true);
347
+ } catch (e) {
348
+ throw new Error(e.message);
349
+ }
350
+ }
351
+ return await isLogin(this.service);
352
+ }
353
+ async signIn() {
354
+ const { state, codeVerifier, codeChallenge } = await generatePkceChallenge();
355
+ getStorage().setItem("state", state);
356
+ getStorage().setItem("codeVerifier", codeVerifier);
357
+ window.location = this.service.getAuthorizationCodeURL(codeChallenge, state);
358
+ }
359
+ signOut() {
360
+ this.clear();
361
+ window.location = this.service.getSignOutURL();
362
+ }
363
+ clear() {
364
+ clearTokens();
365
+ clearOptions();
366
+ clearNonce();
367
+ }
368
+ };
369
+
370
+ //#endregion
371
+ //#region src/PasswordFlow.ts
372
+ var PasswordFlow = class {
373
+ constructor(options) {
374
+ this.options = options;
375
+ this.service = new Gc2Service(options);
376
+ }
377
+ async signIn() {
378
+ const { access_token, refresh_token } = await this.service.getPasswordToken();
379
+ setTokens({
380
+ accessToken: access_token,
381
+ refreshToken: refresh_token
382
+ });
383
+ setOptions({
384
+ clientId: this.options.clientId,
385
+ host: this.options.host,
386
+ redirectUri: ""
387
+ });
388
+ }
389
+ signOut() {
390
+ this.clear();
391
+ }
392
+ clear() {
393
+ clearTokens();
394
+ clearOptions();
395
+ clearNonce();
396
+ }
397
+ };
398
+
399
+ //#endregion
400
+ //#region src/util/request-headers.ts
401
+ const getHeaders = async (contentType = "application/json") => {
402
+ if (!await isLogin(new Gc2Service(getOptions()))) return Promise.reject("Is not logged in");
403
+ const { accessToken } = getTokens();
404
+ const headers = {
405
+ Accept: "application/json",
406
+ Cookie: "XDEBUG_SESSION=XDEBUG_ECLIPSE",
407
+ Authorization: accessToken ? "Bearer " + accessToken : null
408
+ };
409
+ if (contentType) headers["Content-Type"] = contentType;
410
+ return headers;
411
+ };
412
+ var request_headers_default = getHeaders;
413
+
414
+ //#endregion
415
+ //#region src/util/make-request.ts
416
+ const make = async (version, resource, method, payload, contentType = "application/json") => {
417
+ const options = getOptions();
418
+ let request = {
419
+ method,
420
+ headers: await request_headers_default(contentType),
421
+ redirect: "manual"
422
+ };
423
+ if (payload) request.body = contentType === "application/json" ? JSON.stringify(payload) : payload;
424
+ return await fetch(options.host + `/api/v${version}/${resource}`, request);
425
+ };
426
+ var make_request_default = make;
427
+
428
+ //#endregion
429
+ //#region src/util/get-response.ts
430
+ const get = async (response, expectedCode) => {
431
+ let res = null;
432
+ let bodyText = "";
433
+ try {
434
+ bodyText = await response.text();
435
+ } catch (e) {}
436
+ if (bodyText) try {
437
+ res = JSON.parse(bodyText);
438
+ } catch (e) {}
439
+ if (response.status !== expectedCode) {
440
+ const msg = res && (res.message || res.error) || bodyText || `Unexpected status ${response.status}`;
441
+ throw new Error(msg);
442
+ }
443
+ return res;
444
+ };
445
+ var get_response_default = get;
446
+
447
+ //#endregion
448
+ //#region src/Sql.ts
449
+ var Sql = class {
450
+ async exec(request) {
451
+ return await get_response_default(await make_request_default("4", `sql`, "POST", request), 200);
452
+ }
453
+ };
454
+
455
+ //#endregion
456
+ //#region src/Rpc.ts
457
+ var Rpc = class {
458
+ async call(request) {
459
+ return await get_response_default(await make_request_default("4", `call`, "POST", request), 200);
460
+ }
461
+ };
462
+
463
+ //#endregion
464
+ //#region src/Meta.ts
465
+ var Meta = class {
466
+ async query(rel) {
467
+ return await get_response_default(await make_request_default("3", `meta/${rel}`, "GET", null), 200);
468
+ }
469
+ };
470
+
471
+ //#endregion
472
+ //#region src/Status.ts
473
+ var Status = class {
474
+ isAuth() {
475
+ const tokens = getTokens();
476
+ return !(!tokens.accessToken && !tokens.refreshToken);
477
+ }
478
+ getTokens() {
479
+ return getTokens();
480
+ }
481
+ };
482
+
483
+ //#endregion
484
+ //#region src/Claims.ts
485
+ var Claims = class {
486
+ get() {
487
+ const tokens = getTokens().accessToken;
488
+ return claims(tokens);
489
+ }
490
+ };
491
+
492
+ //#endregion
493
+ //#region src/Users.ts
494
+ var Users = class {
495
+ async get(user) {
496
+ return await get_response_default(await make_request_default("4", `users/${user}`, "GET", null), 200);
497
+ }
498
+ };
499
+
500
+ //#endregion
501
+ //#region src/Ws.ts
502
+ var Ws = class {
503
+ constructor(options) {
504
+ this.options = options;
505
+ }
506
+ connect() {
507
+ const me = this;
508
+ const { accessToken } = getTokens();
509
+ const connect = () => {
510
+ const ws = new WebSocket(this.options.host + `/?token=` + accessToken);
511
+ ws.onopen = function() {
512
+ console.log("WebSocket connected!");
513
+ };
514
+ ws.onmessage = function(event) {
515
+ me.options?.callBack(event.data);
516
+ };
517
+ ws.onclose = function(event) {
518
+ if (accessToken !== "") {
519
+ console.log("WebSocket closed, reconnecting in 3 seconds...", event.reason);
520
+ setTimeout(connect, 3e3);
521
+ }
522
+ };
523
+ ws.onerror = function(err) {
524
+ console.error("WebSocket error observed:", err);
525
+ ws.close();
526
+ };
527
+ };
528
+ if (accessToken !== "") connect();
529
+ }
530
+ };
531
+
532
+ //#endregion
533
+ //#region src/Stats.ts
534
+ var Stats = class {
535
+ async get() {
536
+ return await get_response_default(await make_request_default("4", `stats`, "GET", null), 200);
537
+ }
538
+ };
539
+
540
+ //#endregion
541
+ //#region src/Tables.ts
542
+ var Tables = class {
543
+ async get(schema, table) {
544
+ return await get_response_default(await make_request_default("4", `schemas/${encodeURIComponent(schema)}/tables/${encodeURIComponent(table)}`, "GET", null), 200);
545
+ }
546
+ async create(schema, table, payload) {
547
+ return await get_response_default(await make_request_default("4", `schemas/${encodeURIComponent(schema)}/tables/${encodeURIComponent(table)}`, "POST", payload), 200);
548
+ }
549
+ async patch(schema, table, payload) {
550
+ return await get_response_default(await make_request_default("4", `schemas/${encodeURIComponent(schema)}/tables/${encodeURIComponent(table)}`, "PATCH", payload), 200);
551
+ }
552
+ async delete(schema, table) {
553
+ return await get_response_default(await make_request_default("4", `schemas/${encodeURIComponent(schema)}/tables/${encodeURIComponent(table)}`, "DELETE", null), 204);
554
+ }
555
+ };
556
+
557
+ //#endregion
558
+ //#region src/Api.ts
559
+ async function dispatch(name, args) {
560
+ const rpc = new Rpc();
561
+ const request = {
562
+ jsonrpc: "2.0",
563
+ method: name,
564
+ id: 1,
565
+ params: args
566
+ };
567
+ return (await rpc.call(request)).result.data;
568
+ }
569
+ function createApi() {
570
+ return new Proxy({}, { get(_target, prop) {
571
+ if (typeof prop !== "string") return void 0;
572
+ return (...args) => dispatch(prop, ...args);
573
+ } });
574
+ }
575
+
576
+ //#endregion
577
+ exports.Claims = Claims;
578
+ exports.CodeFlow = CodeFlow;
579
+ exports.Meta = Meta;
580
+ exports.PasswordFlow = PasswordFlow;
581
+ exports.Rpc = Rpc;
582
+ exports.Sql = Sql;
583
+ exports.Stats = Stats;
584
+ exports.Status = Status;
585
+ exports.Tables = Tables;
586
+ exports.Users = Users;
587
+ exports.Ws = Ws;
588
+ exports.createApi = createApi;