Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions server/ServerSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2919,7 +2919,7 @@ export function remote(targetOrOptions?: RemoteMethodOptions | ServerSession, me
*/
export function free(resource: (...args: any[]) => any | Readable_fromNodePackage | Readable_fromReadableStreamPackage | ReadableStream | ReadableStreamDefaultReader) { // TODO: list writables
if(typeof resource === "function") {
const clientCallback = resource as ClientCallback;
const clientCallback = (resource as Partial<WithTrimmedClientCallback>)[withTrimSourceCallback] ?? (resource as ClientCallback);
if(clientCallback.socketConnection === undefined) { //
throw new Error("The passed argument is not a client callback function.")
}
Expand All @@ -2932,6 +2932,21 @@ export function free(resource: (...args: any[]) => any | Readable_fromNodePackag

export type UnknownFunction = (...args: unknown[]) => unknown

const withTrimSourceCallback = Symbol("withTrimSourceCallback");

type WithTrimmedClientCallback = ClientCallback & {
[withTrimSourceCallback]: ClientCallback;
};

type WithTrimCacheEntry = {
trimArguments: boolean;
trimResult: boolean;
useSignatureFrom?: UnknownFunction;
wrapped: WithTrimmedClientCallback;
}

const withTrimCache = new WeakMap<ClientCallback, WithTrimCacheEntry[]>();

/**
* Returns a version where, during call, extra properties get trimmed off the arguments or result, so they don't produce a validation error.
* <p>
Expand All @@ -2949,9 +2964,8 @@ export type UnknownFunction = (...args: unknown[]) => unknown
* </code></pre>
*
* <p>
* Note: withTrim creates a new function instance every time. So i.e. you can't hand these to addEventListener(...) + removeEventListener(...) registries then.
* TODO: This could be improved for convenience. But is it really worth it ? Star this issue then: https://github.com/bogeeee/restfuncs/issues/8
* TODO: In that case, also make the util/EventEmitter class's resource-freeing mechanism aware of these derivatives.
* Note: withTrim returns the same wrapped function instance when called repeatedly with the same callback and options.
* This allows it to be used with addEventListener(...) + removeEventListener(...) style registries.
* </p>
* @param callbackFn
* @param trimArguments
Expand All @@ -2977,11 +2991,36 @@ export function withTrim<CB extends UnknownFunction>(callbackFn: CB, trimArgumen
}

const clientCallback = callbackFn as any as ClientCallback;
let entries = withTrimCache.get(clientCallback);
if(entries === undefined) {
entries = [];
withTrimCache.set(clientCallback, entries);
}

const existing = entries.find(entry =>
entry.trimArguments === trimArguments &&
entry.trimResult === trimResult &&
entry.useSignatureFrom === useSignatureFrom
);
if(existing !== undefined) {
return existing.wrapped as any as CB;
}

//@ts-ignore
return (...args: unknown[]) => {
const wrapped: WithTrimmedClientCallback = ((...args: unknown[]) => {
return clientCallback._validateAndCall(args, trimArguments, trimResult, useSignatureFrom);
}
}) as WithTrimmedClientCallback;
wrapped[withTrimSourceCallback] = clientCallback;
wrapped._type = clientCallback._type;
wrapped.id = clientCallback.id;
wrapped.socketConnection = clientCallback.socketConnection;
wrapped.free = () => free(clientCallback);
wrapped._handedUpViaRemoteMethods = clientCallback._handedUpViaRemoteMethods;
wrapped._validateAndCall = (args, _trimArguments, _trimResult, _useSignatureForTrim, diagnosis) =>
clientCallback._validateAndCall(args, trimArguments, trimResult, useSignatureFrom, diagnosis);

entries.push({trimArguments, trimResult, useSignatureFrom, wrapped});
return wrapped as any as CB;
}

/**
Expand Down Expand Up @@ -3074,4 +3113,4 @@ function checkIfSecurityFieldsAreValid(session: SecurityRelevantSessionFields) {

export function isClientCallback(fn: UnknownFunction) {
return ((fn as ClientCallback).socketConnection !== undefined);
}
}
29 changes: 27 additions & 2 deletions tests/clientServer/runtime-typechecking.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { tags } from "typia";
import 'reflect-metadata'
import {ClientCallback, ServerSession} from "restfuncs-server";
import {ClientCallback, ClientCallbackSet, ServerSession} from "restfuncs-server";
import {withTrim} from "restfuncs-server/ServerSession";
import express from "express";
import {reflect} from "typescript-rtti";
Expand Down Expand Up @@ -781,6 +781,21 @@ describe("callbacks", () => {
withTrim(cb)(objWithExtraProps);
return objWithExtraProps.extraProp === "extra"; // is still intact ?
}

@remote()
withTrimCanBeRemovedFromCallbackSet(cb: (obj: {x: string}) => void) {
const callbackSet = new ClientCallbackSet<[obj: {x: string}]>();
const added = withTrim(cb);
const removed = withTrim(cb);

callbackSet.add(added);

return {
sameWrapper: added === removed,
removed: callbackSet.remove(removed),
size: callbackSet.size,
};
}
}

it("should allow legal args in a simple callback", () => runClientServerTests(new ServerAPI, async (apiProxy) => {
Expand Down Expand Up @@ -929,6 +944,16 @@ describe("callbacks", () => {
}, {
useSocket: true
}));

test("withTrim should return stable wrappers for callback registries", () => runClientServerTests(new ServerAPI, async (apiProxy) => {
await expect(apiProxy.withTrimCanBeRemovedFromCallbackSet((dummy: any) => {})).resolves.toStrictEqual({
sameWrapper: true,
removed: true,
size: 0,
});
}, {
useSocket: true
}));
});

describe("callbacks with mixed security requirements", () => {
Expand Down Expand Up @@ -1138,4 +1163,4 @@ test('Test anonymous object as service', async () => {
}
);
})
*/
*/