@push-rpc/next
Advanced tools
Comparing version 2.0.10 to 2.0.11
@@ -1,2 +0,1 @@ | ||
import { ClientCookies } from "../utils/cookies.js"; | ||
export declare class HttpClient { | ||
@@ -6,4 +5,3 @@ private url; | ||
private getHeaders; | ||
private cookies; | ||
constructor(url: string, clientId: string, getHeaders: () => Promise<Record<string, string>>, cookies: ClientCookies); | ||
constructor(url: string, clientId: string, getHeaders: () => Promise<Record<string, string>>); | ||
call(itemName: string, params: unknown[], callTimeout: number): Promise<unknown>; | ||
@@ -10,0 +8,0 @@ subscribe(itemName: string, params: unknown[], callTimeout: number): Promise<unknown>; |
@@ -6,9 +6,7 @@ "use strict"; | ||
const json_js_1 = require("../utils/json.js"); | ||
const env_js_1 = require("../utils/env.js"); | ||
class HttpClient { | ||
constructor(url, clientId, getHeaders, cookies) { | ||
constructor(url, clientId, getHeaders) { | ||
this.url = url; | ||
this.clientId = clientId; | ||
this.getHeaders = getHeaders; | ||
this.cookies = cookies; | ||
} | ||
@@ -31,8 +29,2 @@ async call(itemName, params, callTimeout) { | ||
const { signal, finished } = timeoutSignal(callTimeout); | ||
if (env_js_1.environment != env_js_1.Environment.Browser) { | ||
const cookie = this.cookies.getCookieString(); | ||
if (cookie) { | ||
headers["Cookie"] = cookie; | ||
} | ||
} | ||
const response = await fetch(itemUrl, { | ||
@@ -49,8 +41,2 @@ method, | ||
finished(); | ||
if (env_js_1.environment != env_js_1.Environment.Browser) { | ||
const cookie = response.headers.get("set-cookie"); | ||
if (cookie) { | ||
this.cookies.updateCookies(cookie.split(",")); | ||
} | ||
} | ||
if (response.status == 204) { | ||
@@ -57,0 +43,0 @@ return; |
@@ -19,2 +19,3 @@ import { RpcContext, Services } from "../rpc.js"; | ||
middleware: Middleware<RpcContext>[]; | ||
updatesMiddleware: Middleware<RpcContext>[]; | ||
connectOnCreate: boolean; | ||
@@ -21,0 +22,0 @@ onConnected: () => void; |
@@ -30,2 +30,3 @@ "use strict"; | ||
middleware: [], | ||
updatesMiddleware: [], | ||
connectOnCreate: false, | ||
@@ -32,0 +33,0 @@ onConnected: () => { }, |
@@ -11,3 +11,2 @@ import { Services } from "../rpc.js"; | ||
private readonly connection; | ||
private readonly cookies; | ||
isConnected(): boolean; | ||
@@ -14,0 +13,0 @@ close(): Promise<void>; |
@@ -11,3 +11,2 @@ "use strict"; | ||
const middleware_js_1 = require("../utils/middleware.js"); | ||
const cookies_js_1 = require("../utils/cookies.js"); | ||
class RpcClientImpl { | ||
@@ -17,3 +16,2 @@ constructor(url, options) { | ||
this.clientId = (0, nanoid_1.nanoid)(); | ||
this.cookies = new cookies_js_1.ClientCookies(); | ||
this.call = (itemName, parameters, callOptions) => { | ||
@@ -65,5 +63,5 @@ return this.invoke(itemName, rpc_js_1.InvocationType.Call, (...parameters) => this.httpClient.call(itemName, parameters, callOptions?.timeout ?? this.options.callTimeout), parameters); | ||
}; | ||
this.httpClient = new HttpClient_js_1.HttpClient(url, this.clientId, options.getHeaders, this.cookies); | ||
this.httpClient = new HttpClient_js_1.HttpClient(url, this.clientId, options.getHeaders); | ||
this.remoteSubscriptions = new RemoteSubscriptions_js_1.RemoteSubscriptions(); | ||
this.connection = new WebSocketConnection_js_1.WebSocketConnection(options.getSubscriptionsUrl(url), this.clientId, this.cookies, { | ||
this.connection = new WebSocketConnection_js_1.WebSocketConnection(options.getSubscriptionsUrl(url), this.clientId, { | ||
subscriptions: options.subscriptions, | ||
@@ -74,3 +72,9 @@ errorDelayMaxDuration: options.errorDelayMaxDuration, | ||
}, (itemName, parameters, data) => { | ||
this.remoteSubscriptions.consume(itemName, parameters, data); | ||
const ctx = { | ||
clientId: this.clientId, | ||
itemName, | ||
invocationType: rpc_js_1.InvocationType.Update, | ||
}; | ||
const next = async (p = data) => this.remoteSubscriptions.consume(itemName, parameters, p); | ||
return (0, middleware_js_1.withMiddlewares)(ctx, this.options.updatesMiddleware, next, data); | ||
}, () => { | ||
@@ -77,0 +81,0 @@ this.resubscribe(); |
@@ -1,6 +0,4 @@ | ||
import { ClientCookies } from "../utils/cookies.js"; | ||
export declare class WebSocketConnection { | ||
private readonly url; | ||
private readonly clientId; | ||
private readonly cookies; | ||
private readonly options; | ||
@@ -10,3 +8,3 @@ private readonly consume; | ||
private readonly onDisconnected; | ||
constructor(url: string, clientId: string, cookies: ClientCookies, options: { | ||
constructor(url: string, clientId: string, options: { | ||
subscriptions: boolean; | ||
@@ -13,0 +11,0 @@ reconnectDelay: number; |
@@ -7,8 +7,6 @@ "use strict"; | ||
const promises_js_1 = require("../utils/promises.js"); | ||
const env_js_1 = require("../utils/env.js"); | ||
class WebSocketConnection { | ||
constructor(url, clientId, cookies, options, consume, onConnected, onDisconnected) { | ||
constructor(url, clientId, options, consume, onConnected, onDisconnected) { | ||
this.url = url; | ||
this.clientId = clientId; | ||
this.cookies = cookies; | ||
this.options = options; | ||
@@ -95,20 +93,3 @@ this.consume = consume; | ||
try { | ||
let socket; | ||
if ([env_js_1.Environment.ReactNative, env_js_1.Environment.Node].includes(env_js_1.environment)) { | ||
// use RN WS or node-ws headers extensions to set cookie | ||
let options = undefined; | ||
const cookie = this.cookies.getCookieString(); | ||
if (cookie) { | ||
options = { | ||
headers: { | ||
Cookie: cookie, | ||
}, | ||
}; | ||
} | ||
socket = new WebSocket(this.url, this.clientId, options); | ||
} | ||
else { | ||
// rely on browser cookie handling | ||
socket = new WebSocket(this.url, this.clientId); | ||
} | ||
const socket = new WebSocket(this.url, this.clientId); | ||
let connected = false; | ||
@@ -115,0 +96,0 @@ socket.addEventListener("open", () => { |
@@ -6,2 +6,3 @@ export type { RemoteFunction, Services, Consumer, RpcContext, RpcConnectionContext } from "./rpc.js"; | ||
export type { RpcServer, PublishServicesOptions } from "./server/index.js"; | ||
export type { HttpServerHooks } from "./server/http.js"; | ||
export { publishServices } from "./server/index.js"; | ||
@@ -8,0 +9,0 @@ export type { ServicesWithTriggers } from "./server/local.js"; |
@@ -35,3 +35,4 @@ import { ExtractPromiseResult } from "./utils/types.js"; | ||
Unsubscribe = "Unsubscribe",// client only | ||
Update = "Update",// client only | ||
Trigger = "Trigger" | ||
} |
@@ -30,4 +30,5 @@ "use strict"; | ||
InvocationType["Unsubscribe"] = "Unsubscribe"; | ||
InvocationType["Update"] = "Update"; | ||
InvocationType["Trigger"] = "Trigger"; | ||
})(InvocationType || (exports.InvocationType = InvocationType = {})); | ||
//# sourceMappingURL=rpc.js.map |
@@ -6,2 +6,3 @@ /// <reference types="node" /> | ||
import http, { IncomingMessage } from "http"; | ||
import { HttpServerHooks } from "./http"; | ||
export declare function publishServices<S extends Services<S>, C extends RpcContext>(services: S, overrideOptions: Partial<PublishServicesOptions<C>> & ({ | ||
@@ -27,2 +28,3 @@ port: number; | ||
createConnectionContext(req: IncomingMessage): Promise<RpcConnectionContext>; | ||
createServerHooks?(hooks: HttpServerHooks, req: IncomingMessage): HttpServerHooks; | ||
} & ({ | ||
@@ -29,0 +31,0 @@ server: http.Server; |
@@ -115,7 +115,12 @@ "use strict"; | ||
} | ||
this.httpServer.addListener("request", (req, res) => (0, http_js_1.serveHttpRequest)(req, res, options.path, { | ||
call: this.call, | ||
subscribe: this.subscribe, | ||
unsubscribe: this.unsubscribe, | ||
}, options.createConnectionContext)); | ||
this.httpServer.addListener("request", (req, res) => { | ||
const hooks = { | ||
call: this.call, | ||
subscribe: this.subscribe, | ||
unsubscribe: this.unsubscribe, | ||
}; | ||
(0, http_js_1.serveHttpRequest)(req, res, options.path, options.createServerHooks ? options.createServerHooks(hooks, req) : hooks, options.createConnectionContext).catch((e) => { | ||
logger_js_1.log.warn("Unhandled error serving HTTP request", e); | ||
}); | ||
}); | ||
} | ||
@@ -122,0 +127,0 @@ async createConnectionsServer() { |
{ | ||
"name": "@push-rpc/next", | ||
"version": "2.0.10", | ||
"version": "2.0.11", | ||
"main": "dist/index.js", | ||
@@ -5,0 +5,0 @@ "types": "dist/index.d.ts", |
@@ -23,3 +23,3 @@ Client/server framework | ||
**Middlewares**. Middlewares are used to intercept client and server requests. Both calls and subscriptions can be | ||
intercepted?. Middlewares can be attached on both client and server side. Middlewares receive context as the last | ||
intercepted. Middlewares can be attached on both client and server side. Middlewares receive context as the last | ||
arguments in the invocation. Middleware can modify context. | ||
@@ -46,5 +46,5 @@ | ||
document). Bun/Deno should also work, but not officially supported. | ||
- Limited cookie support. Due to limited support in React Native, Cookies can be set and read during HTTP requests and | ||
set during WS connection establishment. So if you need to share cookies between HTTP and WS, you need to make call ( | ||
HTTP POST) right after creating client, and establish WS connection (ie make subscribe, HTTP PUT) only after that. | ||
Cookies cannot be shared when `connectOnCreate` client option is true. | ||
# Limitations | ||
- Cookies are not been sent during HTTP & WS requests. |
import {CLIENT_ID_HEADER, RpcErrors} from "../rpc.js" | ||
import {safeParseJson, safeStringify} from "../utils/json.js" | ||
import {ClientCookies} from "../utils/cookies.js" | ||
import {environment, Environment} from "../utils/env.js" | ||
@@ -10,4 +8,3 @@ export class HttpClient { | ||
private clientId: string, | ||
private getHeaders: () => Promise<Record<string, string>>, | ||
private cookies: ClientCookies | ||
private getHeaders: () => Promise<Record<string, string>> | ||
) {} | ||
@@ -43,10 +40,2 @@ | ||
if (environment != Environment.Browser) { | ||
const cookie = this.cookies.getCookieString() | ||
if (cookie) { | ||
headers["Cookie"] = cookie | ||
} | ||
} | ||
const response = await fetch(itemUrl, { | ||
@@ -65,10 +54,2 @@ method, | ||
if (environment != Environment.Browser) { | ||
const cookie = response.headers.get("set-cookie") | ||
if (cookie) { | ||
this.cookies.updateCookies(cookie.split(",")) | ||
} | ||
} | ||
if (response.status == 204) { | ||
@@ -75,0 +56,0 @@ return |
@@ -25,2 +25,3 @@ import {RpcContext, Services} from "../rpc.js" | ||
middleware: Middleware<RpcContext>[] | ||
updatesMiddleware: Middleware<RpcContext>[] | ||
connectOnCreate: boolean | ||
@@ -68,2 +69,3 @@ onConnected: () => void | ||
middleware: [], | ||
updatesMiddleware: [], | ||
connectOnCreate: false, | ||
@@ -70,0 +72,0 @@ onConnected: () => {}, |
@@ -9,3 +9,2 @@ import {CallOptions, InvocationType, RpcContext, Services} from "../rpc.js" | ||
import {withMiddlewares} from "../utils/middleware.js" | ||
import {ClientCookies} from "../utils/cookies.js" | ||
@@ -17,3 +16,3 @@ export class RpcClientImpl<S extends Services<S>> implements RpcClient { | ||
) { | ||
this.httpClient = new HttpClient(url, this.clientId, options.getHeaders, this.cookies) | ||
this.httpClient = new HttpClient(url, this.clientId, options.getHeaders) | ||
this.remoteSubscriptions = new RemoteSubscriptions() | ||
@@ -24,3 +23,2 @@ | ||
this.clientId, | ||
this.cookies, | ||
{ | ||
@@ -33,3 +31,10 @@ subscriptions: options.subscriptions, | ||
(itemName, parameters, data) => { | ||
this.remoteSubscriptions.consume(itemName, parameters, data) | ||
const ctx: RpcContext = { | ||
clientId: this.clientId, | ||
itemName, | ||
invocationType: InvocationType.Update, | ||
} | ||
const next = async (p = data) => this.remoteSubscriptions.consume(itemName, parameters, p) | ||
return withMiddlewares(ctx, this.options.updatesMiddleware, next, data) | ||
}, | ||
@@ -50,3 +55,2 @@ () => { | ||
private readonly connection: WebSocketConnection | ||
private readonly cookies: ClientCookies = new ClientCookies() | ||
@@ -53,0 +57,0 @@ isConnected() { |
import {log} from "../logger.js" | ||
import {safeParseJson} from "../utils/json.js" | ||
import {adelay} from "../utils/promises.js" | ||
import {environment, Environment} from "../utils/env.js" | ||
import {ClientCookies} from "../utils/cookies.js" | ||
import type {IncomingMessage} from "http" | ||
@@ -12,3 +9,2 @@ export class WebSocketConnection { | ||
private readonly clientId: string, | ||
private readonly cookies: ClientCookies, | ||
private readonly options: { | ||
@@ -126,23 +122,4 @@ subscriptions: boolean | ||
try { | ||
let socket: WebSocket | ||
const socket = new WebSocket(this.url, this.clientId) | ||
if ([Environment.ReactNative, Environment.Node].includes(environment)) { | ||
// use RN WS or node-ws headers extensions to set cookie | ||
let options = undefined | ||
const cookie = this.cookies.getCookieString() | ||
if (cookie) { | ||
options = { | ||
headers: { | ||
Cookie: cookie, | ||
}, | ||
} | ||
} | ||
socket = new (WebSocket as any)(this.url, this.clientId, options) | ||
} else { | ||
// rely on browser cookie handling | ||
socket = new WebSocket(this.url, this.clientId) | ||
} | ||
let connected = false | ||
@@ -149,0 +126,0 @@ |
@@ -8,2 +8,3 @@ export type {RemoteFunction, Services, Consumer, RpcContext, RpcConnectionContext} from "./rpc.js" | ||
export type {RpcServer, PublishServicesOptions} from "./server/index.js" | ||
export type {HttpServerHooks} from "./server/http.js" | ||
export {publishServices} from "./server/index.js" | ||
@@ -10,0 +11,0 @@ |
@@ -50,3 +50,4 @@ import {ExtractPromiseResult} from "./utils/types.js" | ||
Unsubscribe = "Unsubscribe", // client only | ||
Update = "Update", // client only | ||
Trigger = "Trigger", // server only | ||
} |
@@ -6,2 +6,3 @@ import {CLIENT_ID_HEADER, RpcConnectionContext, RpcContext, Services} from "../rpc.js" | ||
import http, {IncomingMessage} from "http" | ||
import {HttpServerHooks} from "./http" | ||
@@ -45,2 +46,3 @@ export async function publishServices<S extends Services<S>, C extends RpcContext>( | ||
createConnectionContext(req: IncomingMessage): Promise<RpcConnectionContext> | ||
createServerHooks?(hooks: HttpServerHooks, req: IncomingMessage): HttpServerHooks | ||
} & ( | ||
@@ -47,0 +49,0 @@ | { |
@@ -41,3 +41,9 @@ import {PublishServicesOptions, RpcServer} from "./index.js" | ||
this.httpServer.addListener("request", (req, res) => | ||
this.httpServer.addListener("request", (req, res) => { | ||
const hooks = { | ||
call: this.call, | ||
subscribe: this.subscribe, | ||
unsubscribe: this.unsubscribe, | ||
} | ||
serveHttpRequest( | ||
@@ -47,10 +53,8 @@ req, | ||
options.path, | ||
{ | ||
call: this.call, | ||
subscribe: this.subscribe, | ||
unsubscribe: this.unsubscribe, | ||
}, | ||
options.createServerHooks ? options.createServerHooks(hooks, req) : hooks, | ||
options.createConnectionContext | ||
) | ||
) | ||
).catch((e) => { | ||
log.warn("Unhandled error serving HTTP request", e) | ||
}) | ||
}) | ||
} | ||
@@ -57,0 +61,0 @@ |
import {assert} from "chai" | ||
import {createTestClient, startTestServer, TEST_PORT, testClient, testServer} from "./testUtils.js" | ||
import WebSocket, {WebSocketServer} from "ws" | ||
import {createTestClient, startTestServer, testClient, testServer} from "./testUtils.js" | ||
import WebSocket from "ws" | ||
import {adelay} from "../src/utils/promises.js" | ||
import http, {IncomingMessage} from "http" | ||
import {parseCookies} from "../src/utils/cookies.js" | ||
@@ -137,85 +135,2 @@ describe("connection", () => { | ||
}) | ||
describe("cookies", () => { | ||
it("handle cookies in subseq requests", async () => { | ||
let call = 0 | ||
let sentClientCookies: Record<string, string> = {} | ||
const httpServer = http.createServer((req, res) => { | ||
const headers: Record<string, string> = {"Content-Type": "text/plain"} | ||
if (!call++) { | ||
headers["Set-Cookie"] = `name=value; path=/; secure; samesite=none; httponly` | ||
} else { | ||
sentClientCookies = parseCookies(req.headers.cookie || "") | ||
} | ||
res.writeHead(200, headers) | ||
res.end("ok") | ||
}) | ||
let resolveStarted = () => {} | ||
const started = new Promise<void>((r) => (resolveStarted = r)) | ||
httpServer.listen(TEST_PORT, () => resolveStarted()) | ||
await started | ||
const client = await createTestClient<{call(): Promise<string>}>() | ||
await client.call() | ||
await client.call() | ||
assert.equal(Object.keys(sentClientCookies).length, 1) | ||
assert.equal(sentClientCookies["name"], "value") | ||
let resolveStopped = () => {} | ||
const stopped = new Promise<void>((r) => (resolveStopped = r)) | ||
httpServer.closeAllConnections() | ||
httpServer.close(() => resolveStopped()) | ||
await stopped | ||
}) | ||
it("set cookie in http, use in ws", async () => { | ||
const httpServer = http.createServer((req, res) => { | ||
const headers: Record<string, string> = { | ||
"Content-Type": "text/plain", | ||
"Set-Cookie": "name=value; path=/; secure; samesite=none; httponly", | ||
} | ||
res.writeHead(200, headers) | ||
res.end("ok") | ||
}) | ||
const wss = new WebSocketServer({server: httpServer}) | ||
let sentClientCookies: Record<string, string> = {} | ||
wss.on("connection", (ws: unknown, req: IncomingMessage) => { | ||
sentClientCookies = parseCookies(req.headers.cookie || "") | ||
}) | ||
let resolveStarted = () => {} | ||
const started = new Promise<void>((r) => (resolveStarted = r)) | ||
httpServer.listen(TEST_PORT, () => resolveStarted()) | ||
await started | ||
const client = await createTestClient<{call(): Promise<string>}>() | ||
await client.call() | ||
await client.call.subscribe(() => {}) | ||
assert.equal(Object.keys(sentClientCookies).length, 1) | ||
assert.equal(sentClientCookies["name"], "value") | ||
await testClient!.close() | ||
let resolveStopped = () => {} | ||
const stopped = new Promise<void>((r) => (resolveStopped = r)) | ||
httpServer.closeIdleConnections() | ||
httpServer.closeAllConnections() | ||
httpServer.close(() => resolveStopped()) | ||
await stopped | ||
}) | ||
}) | ||
}) |
@@ -112,2 +112,33 @@ import {assert} from "chai" | ||
}) | ||
it("updates middlewares", async () => { | ||
let count = 1 | ||
const services = await startTestServer({ | ||
async remote() { | ||
return count++ | ||
}, | ||
}) | ||
const client = await createTestClient<typeof services>({ | ||
updatesMiddleware: [ | ||
(ctx, next, r) => { | ||
assert.ok(ctx.itemName) | ||
return next((r as number) + 1) | ||
}, | ||
], | ||
}) | ||
let response | ||
await client.remote.subscribe((r) => { | ||
response = r | ||
}) | ||
services.remote.trigger() | ||
await adelay(20) | ||
assert.equal(response, 3) | ||
}) | ||
}) |
@@ -39,2 +39,8 @@ import { | ||
): Promise<ServicesWithSubscriptions<S>> { | ||
if (!options) options = {} | ||
if (!options.middleware) options.middleware = [] | ||
options.middleware = [logMiddleware, ...options.middleware] | ||
if (!options.updatesMiddleware) options.updatesMiddleware = [] | ||
options.updatesMiddleware = [logUpdatesMiddleware, ...options.updatesMiddleware] | ||
const r = await consumeServices<S>(`http://127.0.0.1:${TEST_PORT}/rpc`, options) | ||
@@ -57,1 +63,20 @@ testClient = r.client | ||
}) | ||
async function logMiddleware(ctx: RpcContext, next: any, ...params: any) { | ||
try { | ||
console.log(`OUT:${ctx.invocationType} '${ctx.itemName}'`, ...params) | ||
const r = await next() | ||
console.log(`IN:${ctx.invocationType} '${ctx.itemName}'`, r) | ||
return r | ||
} catch (e) { | ||
console.log(`ERR:${ctx.invocationType} '${ctx.itemName}'`, e) | ||
throw e | ||
} | ||
} | ||
async function logUpdatesMiddleware(ctx: RpcContext, next: any, res: any) { | ||
console.log(`IN:${ctx.invocationType} '${ctx.itemName}'`, res) | ||
return next() | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
217674
4844