client-ts: Port services/server

This commit is contained in:
Elian Doran 2024-07-25 00:23:29 +03:00
parent ba7035a346
commit 5875aa3bef
No known key found for this signature in database
3 changed files with 63 additions and 29 deletions

View File

@ -1,13 +1,35 @@
import utils from './utils.js';
import ValidationError from "./validation_error.js";
async function getHeaders(headers) {
type Headers = Record<string, string | null | undefined>;
type Method = string;
interface Response {
headers: Headers;
body: unknown;
}
interface Arg extends Response {
statusCode: number;
method: Method;
url: string;
requestId: string;
}
interface RequestData {
resolve: (value: unknown) => any;
reject: (reason: unknown) => any;
silentNotFound: boolean;
}
async function getHeaders(headers?: Headers) {
const appContext = (await import('../components/app_context.js')).default;
const activeNoteContext = appContext.tabManager ? appContext.tabManager.getActiveContext() : null;
// headers need to be lowercase because node.js automatically converts them to lower case
// also avoiding using underscores instead of dashes since nginx filters them out by default
const allHeaders = {
const allHeaders: Headers = {
'trilium-component-id': glob.componentId,
'trilium-local-now-datetime': utils.localNowDateTime(),
'trilium-hoisted-note-id': activeNoteContext ? activeNoteContext.hoistedNoteId : null,
@ -28,31 +50,31 @@ async function getHeaders(headers) {
return allHeaders;
}
async function getWithSilentNotFound(url, componentId) {
return await call('GET', url, componentId, { silentNotFound: true });
async function getWithSilentNotFound<T>(url: string, componentId?: string) {
return await call<T>('GET', url, componentId, { silentNotFound: true });
}
async function get(url, componentId) {
return await call('GET', url, componentId);
async function get<T>(url: string, componentId?: string) {
return await call<T>('GET', url, componentId);
}
async function post(url, data, componentId) {
return await call('POST', url, componentId, { data });
async function post<T>(url: string, data: unknown, componentId?: string) {
return await call<T>('POST', url, componentId, { data });
}
async function put(url, data, componentId) {
return await call('PUT', url, componentId, { data });
async function put<T>(url: string, data: unknown, componentId?: string) {
return await call<T>('PUT', url, componentId, { data });
}
async function patch(url, data, componentId) {
return await call('PATCH', url, componentId, { data });
async function patch<T>(url: string, data: unknown, componentId?: string) {
return await call<T>('PATCH', url, componentId, { data });
}
async function remove(url, componentId) {
return await call('DELETE', url, componentId);
async function remove<T>(url: string, componentId?: string) {
return await call<T>('DELETE', url, componentId);
}
async function upload(url, fileToUpload) {
async function upload(url: string, fileToUpload: File) {
const formData = new FormData();
formData.append('upload', fileToUpload);
@ -68,11 +90,17 @@ async function upload(url, fileToUpload) {
}
let idCounter = 1;
const idToRequestMap = {};
const idToRequestMap: Record<string, RequestData> = {};
let maxKnownEntityChangeId = 0;
async function call(method, url, componentId, options = {}) {
interface CallOptions {
data?: unknown;
silentNotFound?: boolean;
}
async function call<T>(method: string, url: string, componentId?: string, options: CallOptions = {}) {
let resp;
const headers = await getHeaders({
@ -98,7 +126,7 @@ async function call(method, url, componentId, options = {}) {
url: `/${window.glob.baseApiUrl}${url}`,
data: data
});
});
}) as any;
}
else {
resp = await ajax(url, method, data, headers, !!options.silentNotFound);
@ -110,23 +138,25 @@ async function call(method, url, componentId, options = {}) {
maxKnownEntityChangeId = Math.max(maxKnownEntityChangeId, parseInt(maxEntityChangeIdStr));
}
return resp.body;
return resp.body as T;
}
function ajax(url, method, data, headers, silentNotFound) {
function ajax(url: string, method: string, data: unknown, headers: Headers, silentNotFound: boolean): Promise<Response> {
return new Promise((res, rej) => {
const options = {
const options: JQueryAjaxSettings = {
url: window.glob.baseApiUrl + url,
type: method,
headers: headers,
timeout: 60000,
success: (body, textStatus, jqXhr) => {
const respHeaders = {};
const respHeaders: Headers = {};
jqXhr.getAllResponseHeaders().trim().split(/[\r\n]+/).forEach(line => {
const parts = line.split(': ');
const header = parts.shift();
respHeaders[header] = parts.join(': ');
if (header) {
respHeaders[header] = parts.join(': ');
}
});
res({
@ -161,7 +191,7 @@ function ajax(url, method, data, headers, silentNotFound) {
if (utils.isElectron()) {
const ipc = utils.dynamicRequire('electron').ipcRenderer;
ipc.on('server-response', async (event, arg) => {
ipc.on('server-response', async (event: string, arg: Arg) => {
if (arg.statusCode >= 200 && arg.statusCode < 300) {
handleSuccessfulResponse(arg);
}
@ -178,8 +208,8 @@ if (utils.isElectron()) {
delete idToRequestMap[arg.requestId];
});
function handleSuccessfulResponse(arg) {
if (arg.headers['Content-Type'] === 'application/json') {
function handleSuccessfulResponse(arg: Arg) {
if (arg.headers['Content-Type'] === 'application/json' && typeof arg.body === "string") {
arg.body = JSON.parse(arg.body);
}
@ -195,13 +225,13 @@ if (utils.isElectron()) {
}
}
async function reportError(method, url, statusCode, response) {
async function reportError(method: string, url: string, statusCode: number, response: unknown) {
let message = response;
if (typeof response === 'string') {
try {
response = JSON.parse(response);
message = response.message;
message = (response as any).message;
}
catch (e) {}
}

View File

@ -21,6 +21,9 @@ interface CustomGlobals {
importMarkdownInline: () => Promise<unknown>;
SEARCH_HELP_TEXT: string;
activeDialog: JQuery<HTMLElement> | null;
componentId: string;
csrfToken: string;
baseApiUrl: string;
}
type RequireMethod = (moduleName: string) => any;

View File

@ -10,7 +10,8 @@
"lib": ["ES2022"],
"downlevelIteration": true,
"skipLibCheck": true,
"esModuleInterop": true
"esModuleInterop": true,
"allowJs": true
},
"include": [
"./src/**/*.js",