From 60357366ec1db553789909889bf3489dea17a269 Mon Sep 17 00:00:00 2001 From: "Marc J. Schmidt" Date: Tue, 28 May 2024 03:19:37 +0200 Subject: [PATCH] fix(rpc): add interface for fetch this way the user can easily replace it with their own fetch implementation (e.g. Angular HttpClient) --- packages/rpc/index.ts | 1 + packages/rpc/src/client/client-websocket.ts | 2 +- packages/rpc/src/client/http.ts | 65 +++++++++++++++++---- packages/rpc/tsconfig.json | 3 +- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/packages/rpc/index.ts b/packages/rpc/index.ts index 48bb4d75c..6482bce4a 100644 --- a/packages/rpc/index.ts +++ b/packages/rpc/index.ts @@ -12,6 +12,7 @@ export * from './src/client/action.js'; export * from './src/client/client-direct.js'; export * from './src/client/client-websocket.js'; export * from './src/client/client.js'; +export * from './src/client/http.js'; export * from './src/client/message-subject.js'; export * from './src/client/entity-state.js'; export * from './src/server/action.js'; diff --git a/packages/rpc/src/client/client-websocket.ts b/packages/rpc/src/client/client-websocket.ts index acc541c4f..ca21ce472 100644 --- a/packages/rpc/src/client/client-websocket.ts +++ b/packages/rpc/src/client/client-websocket.ts @@ -52,7 +52,7 @@ export function createRpcWebSocketClientProvider(baseUrl: string = typeof locati [name: number]: number } = { 4200: 8080 }) { return { - provide: RpcWebSocketClient, + provide: RpcClient, useFactory: () => new RpcWebSocketClient(webSocketFromBaseUrl(baseUrl, portMapping)) }; } diff --git a/packages/rpc/src/client/http.ts b/packages/rpc/src/client/http.ts index a42efb3b4..c73669c89 100644 --- a/packages/rpc/src/client/http.ts +++ b/packages/rpc/src/client/http.ts @@ -1,12 +1,57 @@ -import { ClientTransportAdapter } from './client.js'; +import { ClientTransportAdapter, RpcClient } from './client.js'; import { TransportClientConnection } from '../transport.js'; import { RpcMessageDefinition } from '../protocol.js'; import { RpcTypes } from '../model.js'; import { HttpRpcMessage } from '../server/http.js'; import { serialize } from '@deepkit/type'; +export interface RpcHttpResponseInterface { + status: number; + headers: { [name: string]: string }; + body?: any; +} + +export interface RpcHttpInterface { + fetch(url: string, options: { + headers: { [name: string]: string }, + method: string, + body: any + }): Promise; +} + +export class RpcHttpFetch implements RpcHttpInterface { + async fetch(url: string, options: { + headers: { [name: string]: string }, + method: string, + body: any + }): Promise { + const res = await fetch(url, options); + + return { + status: res.status, + headers: Object.fromEntries(res.headers.entries()), + body: await res.json(), + }; + } +} + +export function createRpcHttpClientProvider( + baseUrl: string = typeof location !== 'undefined' ? location.origin : 'http://localhost', + headers: { [name: string]: string } = {}, + http?: RpcHttpInterface, +) { + return { + provide: RpcClient, + useFactory: () => new RpcClient(new RpcHttpClientAdapter(baseUrl, headers, http)), + }; +} + export class RpcHttpClientAdapter implements ClientTransportAdapter { - constructor(public url: string, public headers: { [name: string]: string } = {}) { + constructor( + public url: string, + public headers: { [name: string]: string } = {}, + public http: RpcHttpInterface = new RpcHttpFetch(), + ) { this.url = url.endsWith('/') ? url.slice(0, -1) : url; } @@ -44,7 +89,7 @@ export class RpcHttpClientAdapter implements ClientTransportAdapter { const allPrimitive = messageBody.args.every(v => ['string', 'number', 'boolean', 'bigint'].includes(typeof v)); if (allPrimitive) { for (const a of messageBody.args) { - qs.push('arg=' + encodeURIComponent(JSON.stringify(a))); + qs.push('arg=' + encodeURIComponent(String(a))); } method = 'GET'; } else { @@ -55,22 +100,22 @@ export class RpcHttpClientAdapter implements ClientTransportAdapter { throw new Error('Unsupported message type ' + message.type + ' for Http adapter'); } - const res = await fetch(this.url + '/' + path + '?' + qs.join('&'), { + const res = await this.http.fetch(this.url + '/' + path + '?' + qs.join('&'), { headers: Object.assign({ 'Content-Type': 'application/json', 'Accept': 'application/json', - 'Authorization': String(connection.token) + 'Authorization': String(connection.token), }, this.headers), method, body, }); - const type = Number(res.headers.get('X-Message-Type')); - const composite = 'true' === res.headers.get('X-Message-Composite'); - const routeType = Number(res.headers.get('X-Message-RouteType')); - let json = await res.json(); + const type = Number(res.headers['x-message-type']); + const composite = 'true' === res.headers['x-message-composite']; + const routeType = Number(res.headers['x-message-routetype']); + let json = res.body; if (type === RpcTypes.ResponseActionSimple) { - json = {v: json}; + json = { v: json }; } connection.read(new HttpRpcMessage(message.id, composite, type, routeType, {}, json)); }, diff --git a/packages/rpc/tsconfig.json b/packages/rpc/tsconfig.json index 719e1fcd1..10d7eb393 100644 --- a/packages/rpc/tsconfig.json +++ b/packages/rpc/tsconfig.json @@ -17,7 +17,8 @@ "lib": [ "es2021", "es2022.error", - "dom" + "dom", + "dom.iterable" ], "types": [ "dot-prop",