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
15 changes: 9 additions & 6 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ insert_final_newline = true
max_line_length = 140
tab_width = 2

# [{*.ats,*.cts,*.mts,*.ts}]
# max_line_length = 120
# tab_width = 2

# [{*.cjs,*.js}]
# max_line_length = 80
[*.json]
ij_json_object_wrapping = normal
ij_json_array_wrapping = normal
ij_json_property_alignment = do_not_align
ij_json_keep_blank_lines_in_code = 1
ij_json_spaces_within_braces = false
ij_json_spaces_within_brackets = false
ij_json_wrap_long_lines = false
max_line_length = off
1,670 changes: 1,510 additions & 160 deletions package-lock.json

Large diffs are not rendered by default.

34 changes: 19 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"bundle": "rollup -c",
"cleanup": "mv dist/index.d.ts dist/index.tmp && find ./dist -name '*.d.ts' -type f -delete && mv dist/index.tmp dist/index.d.ts"
},

"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
Expand All @@ -32,33 +31,38 @@
"dependencies": {
"uuid": "^9.0.0"
},
"peerDependencies": {
"request": "^2.88.2"
},
"devDependencies": {
"@babel/cli": "^7.23.4",
"@babel/core": "^7.23.7",
"@babel/preset-env": "^7.23.8",
"@babel/preset-typescript": "^7.23.3",
"@jest/globals": "^29.7.0",
"@types/jest": "^29.5.11",
"@types/node": "^20.10.0",
"@types/uuid": "^9.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.8",
"@types/uuid": "^9.0.8",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"rollup": "^4.9.6",
"rollup-plugin-dts": "^6.1.0",
"rollup-plugin-string": "^3.0.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"rimraf": "^5.0.5",
"babel-loader": "^9.1.3",
"babel-jest": "^29.7.0",
"babel-loader": "^9.1.3",
"babel-plugin-inline-import": "^3.0.0",
"eslint": "^8.56.0",
"express": "^4.19.2",
"jest": "^29.7.0",
"type-fest": "^4.9.0",
"typescript": "^5.3.3",
"tslib": "^2.6.2",
"express": "^4.18.2"
"rimraf": "^5.0.5",
"rollup": "^4.21.2",
"rollup-plugin-dts": "^6.1.1",
"rollup-plugin-string": "^3.0.0",
"ts-node": "^10.9.2",
"tslib": "^2.7.0",
"type-fest": "^4.26.0",
"typeconv": "^2.3.1",
"typescript": "^5.5.4"
},
"jest": {
"transform": {
Expand Down
100 changes: 61 additions & 39 deletions src/client/DefaultJsonRpcSigner.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import {TrustlyApiClientSettingsData} from './TrustlyApiClientSettings';
import {Serializer} from './Serializer';
import {JsonRpcRequest} from '../domain/base/JsonRpcRequest';
import {IRequestParamsData} from '../domain/base/IRequestParamsData';
import {JsonRpcResponse} from '../domain/base/JsonRpcResponse';
import {IResponseResultData} from '../domain/base/IResponseResultData';
import {JsonRpcSigner} from './JsonRpcSigner';
import {IData} from '../domain/base/IData';
import {IRequest} from '../domain/base/IRequest';
import {IRequestParams} from '../domain/base/IRequestParams';
import {TrustlyStringUtils} from '../util/TrustlyStringUtils';
import {WithoutSignature} from '../domain/base/modifiers/WithoutSignature';
import {WithoutSignature} from '../domain/WithoutSignature';
import * as crypto from 'crypto';
import {NotificationRequest} from "../domain/base/NotificationRequest";
import {
AbstractRequestData,
AbstractRequestDataAttributes,
JsonRpcErrorResponse,
JsonRpcNotification,
JsonRpcNotificationParams,
JsonRpcRequest,
JsonRpcRequestParams,
JsonRpcResponse,
ResponseResult
} from "../domain/models";

export class DefaultJsonRpcSigner implements JsonRpcSigner {

Expand All @@ -27,7 +30,7 @@ export class DefaultJsonRpcSigner implements JsonRpcSigner {
return `${method}${uuid}${serializedData}`;
}

public signRequest<D extends IRequestParamsData, T extends JsonRpcRequest<D>>(v: WithoutSignature<T>): JsonRpcRequest<D> {
public signRequest<TReqData extends AbstractRequestData<AbstractRequestDataAttributes>, M extends string>(v: WithoutSignature<JsonRpcRequest<JsonRpcRequestParams<TReqData>, M>>): JsonRpcRequest<JsonRpcRequestParams<TReqData>, M> {

const signature = this.createSignature(v.method, v.params.UUID, v.params.Data);

Expand All @@ -40,10 +43,10 @@ export class DefaultJsonRpcSigner implements JsonRpcSigner {
};
}

public signResponse<D extends IResponseResultData, T extends JsonRpcResponse<D> = JsonRpcResponse<D>>(response: WithoutSignature<T>): JsonRpcResponse<D> {
public signResponse<TResData, M extends string>(response: WithoutSignature<JsonRpcResponse<ResponseResult<TResData, M>>>): JsonRpcResponse<ResponseResult<TResData, M>> {

if (response.result) {
const signature = this.createSignature(response.result.method, response.result.uuid, response.result.data);
const signature = this.createSignature(response.result.method, response.result.uuid, response.result.data as object);
return {
...response,
result: {
Expand All @@ -52,24 +55,28 @@ export class DefaultJsonRpcSigner implements JsonRpcSigner {
}
};

} else if (response.error && response.error.error) {
const signature = this.createSignature(response.error.error.method, response.error.error.uuid, response.error.error.data);
return {
...response,
error: {
...response.error,
error: {
...response.error.error,
signature: signature,
},
},
};
} else {
throw new Error(`There must be either a result or an error`);
}
}

private createSignature<T extends IData>(method: string, uuid: string, data: T): string {
public signErrorResponse(response: WithoutSignature<JsonRpcErrorResponse>): JsonRpcErrorResponse {

const error = response.error.error!;
const signature = this.createSignature(error.method, error.uuid, error.data);
return {
...response,
error: {
...response.error,
error: {
...error,
signature: signature,
},
},
};
}

private createSignature<T extends object>(method: string, uuid: string, data: T): string {
const serializedData = this.serializer.serializeData(data);
const plainText = this.createPlaintext(serializedData, method, uuid);

Expand All @@ -78,30 +85,45 @@ export class DefaultJsonRpcSigner implements JsonRpcSigner {
return sign.sign(this.settings.clientPrivateKey, 'base64');
}

public verifyRequest<D extends IRequestParamsData, P extends IRequestParams<D>>(request: IRequest<P>): void {
verifyRequest<M extends string, D extends AbstractRequestData<AbstractRequestDataAttributes>, P extends JsonRpcRequestParams<D>>(request: JsonRpcRequest<P, M>): void {

const uuid = request.params.UUID;
const signature = request.params.Signature;
const data = request.params.Data;
const uuid = request.params?.UUID as string;
const signature = request.params?.Signature as string;
const data = request.params?.Data as object;

this.verify(request.method, uuid, signature, data);
}

public verifyNotificationRequest<D extends IRequestParamsData>(request: NotificationRequest<D>): void {
verifyNotificationRequest<M extends string, D, P extends JsonRpcNotificationParams<D>>(request: JsonRpcNotification<P, M>): void {

const uuid = request.params.uuid;
const signature = request.params.signature;
const data = request.params.data;
const uuid = request.params?.uuid as string;
const signature = request.params?.signature as string;
const data = request.params?.data as object;

this.verify(request.method, uuid, signature, data);
}

public verifyResponse<T extends IResponseResultData>(response: JsonRpcResponse<T>): void {
public verifyResponse<M extends string, D, TRes extends ResponseResult<D, M>>(response?: JsonRpcResponse<TRes>): void {

const method = response?.result.method;
const uuid = response?.result.uuid;
const signature = response?.result.signature;
const data = response?.result.data;

if (!method || !uuid || !signature || !data) {
throw new Error(`Missing the method, uuid, signature or data from the response`);
}

this.verify(method, uuid, signature, data);
}

public verifyErrorResponse(response?: JsonRpcErrorResponse): void {

const method = response.result ? response.result.method : response.error?.error?.method;
const uuid = response.result ? response.result.uuid : response.error?.error?.uuid;
const signature = response.result ? response.result.signature : response.error?.error?.signature;
const data = response.result ? response.result.data : response.error?.error?.data;
const error = response?.error?.error as Record<string, unknown>;
const method = error.method as string;
const uuid = error.uuid as string;
const signature = error.signature as string;
const data = error.data as object;

if (!method || !uuid || !signature || !data) {
throw new Error(`Missing the method, uuid, signature or data from the response`);
Expand All @@ -110,7 +132,7 @@ export class DefaultJsonRpcSigner implements JsonRpcSigner {
this.verify(method, uuid, signature, data);
}

private verify(method: string, uuid: string, expectedSignature: string, data: IData, dataNode?: Record<string, unknown>): void {
private verify(method: string, uuid: string, expectedSignature: string, data: object, dataNode?: Record<string, unknown>): void {

if (TrustlyStringUtils.isBlank(expectedSignature)) {
throw new Error('There was no expected signature given. The payload seems malformed');
Expand Down
7 changes: 3 additions & 4 deletions src/client/JsonRpcFactory.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import {JsonRpcRequest} from '../domain/base/JsonRpcRequest';
import {IRequestParamsData} from '../domain/base/IRequestParamsData';
import * as u from 'uuid';
import {WithoutSignature} from '../domain/base/modifiers/WithoutSignature';
import {WithoutSignature} from '../domain/WithoutSignature';
import {AbstractRequestData, AbstractRequestDataAttributes, JsonRpcRequest, JsonRpcRequestParams} from "../domain/models";

export class JsonRpcFactory {

public create<D extends IRequestParamsData>(requestData: D, method: string, uuid?: string): WithoutSignature<JsonRpcRequest<D>> {
public create<M extends string, D extends AbstractRequestData<AbstractRequestDataAttributes>>(requestData: D, method: M, uuid?: string): WithoutSignature<JsonRpcRequest<JsonRpcRequestParams<D>, M>> {

if (!uuid) {
uuid = u.v4().toString();
Expand Down
29 changes: 16 additions & 13 deletions src/client/JsonRpcSigner.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
import {IRequestParamsData} from '../domain/base/IRequestParamsData';
import {JsonRpcRequest} from '../domain/base/JsonRpcRequest';
import {IResponseResultData} from '../domain/base/IResponseResultData';
import {JsonRpcResponse} from '../domain/base/JsonRpcResponse';
import {IRequestParams} from '../domain/base/IRequestParams';
import {IRequest} from '../domain/base/IRequest';
import {WithoutSignature} from '../domain/base/modifiers/WithoutSignature';
import {NotificationRequest} from "../domain/base/NotificationRequest";
import {WithoutSignature} from '../domain/WithoutSignature';
import {
AbstractRequestData,
AbstractRequestDataAttributes, JsonRpcErrorResponse, JsonRpcNotification, JsonRpcNotificationParams,
JsonRpcRequest,
JsonRpcRequestParams,
JsonRpcResponse,
ResponseResult
} from "../domain/models";

export interface JsonRpcSigner {

signRequest<D extends IRequestParamsData, T extends JsonRpcRequest<D>>(request: WithoutSignature<T>): JsonRpcRequest<D>;
signRequest<TReqData extends AbstractRequestData<AbstractRequestDataAttributes>, M extends string>(request: WithoutSignature<JsonRpcRequest<JsonRpcRequestParams<TReqData>, M>>): JsonRpcRequest<JsonRpcRequestParams<TReqData>, M>;

signResponse<D extends IResponseResultData, T extends JsonRpcResponse<D>>(response: WithoutSignature<T>): JsonRpcResponse<D>;
signResponse<TResData, M extends string>(response: WithoutSignature<JsonRpcResponse<ResponseResult<TResData, M>>>): JsonRpcResponse<ResponseResult<TResData, M>>;
signErrorResponse(response: WithoutSignature<JsonRpcErrorResponse>): JsonRpcErrorResponse;

verifyRequest<D extends IRequestParamsData, P extends IRequestParams<D>>(request: IRequest<P>): void;
verifyRequest<M extends string, D extends AbstractRequestData<AbstractRequestDataAttributes>, P extends JsonRpcRequestParams<D>>(request: JsonRpcRequest<P, M>): void;

verifyNotificationRequest<D extends IRequestParamsData>(request: NotificationRequest<D>): void;
verifyNotificationRequest<M extends string, D, P extends JsonRpcNotificationParams<D>>(request: JsonRpcNotification<P, M>): void;

verifyResponse<T extends IResponseResultData>(response?: JsonRpcResponse<T>): void;
verifyResponse<M extends string, D, TRes extends ResponseResult<D, M>>(response?: JsonRpcResponse<TRes>): void;
verifyErrorResponse(response?: JsonRpcErrorResponse): void;
}
41 changes: 12 additions & 29 deletions src/client/NotificationArgs.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,26 @@
import {IRequestParamsData} from '../domain/base/IRequestParamsData';
import {NotificationAckTypes, NotificationTypes} from "./TrustlyApiClient";

export type NotificationOkHandler = (method: string, uuid: string) => Promise<void>;
export type NotificationResponseHandler<M extends string> =
(method: M, uuid: string, response: NotificationAckTypes[M]) => Promise<void>;

export type NotificationFailHandler = (method: string, uuid: string, message: string) => Promise<void>;
export class NotificationArgs<M extends string> {

export class NotificationArgs<D extends IRequestParamsData> {

readonly data: D;

readonly method: string;
readonly data: NotificationTypes[M];
readonly method: M;
readonly uuid: string;

readonly onOK?: NotificationOkHandler;
readonly onFailed?: NotificationFailHandler;
readonly onResponse?: NotificationResponseHandler<M>;

constructor(data: D, method: string, uuid: string, onOK?: NotificationOkHandler, onFailed?: NotificationFailHandler) {
constructor(method: M, data: NotificationTypes[M], uuid: string, onResponse?: NotificationResponseHandler<M>) {
this.data = data;
this.method = method;
this.uuid = uuid;
if (onOK) {
this.onOK = onOK;
}

if (onFailed) {
this.onFailed = onFailed;
}
}

public respondWithOk(): Promise<void> {
if (this.onOK) {
return this.onOK(this.method, this.uuid);
} else {
return Promise.resolve();
}
this.onResponse = onResponse;
}

public respondWithFailed(message: string): Promise<void> {
if (this.onFailed) {
return this.onFailed(this.method, this.uuid, message);
public respondWith(response: NotificationAckTypes[M]): Promise<void> {
if (this.onResponse) {
return this.onResponse(this.method, this.uuid, response);
} else {
return Promise.resolve();
}
Expand Down
3 changes: 1 addition & 2 deletions src/client/NotificationListener.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {IFromTrustlyRequestData} from '../domain/base/IFromTrustlyRequestData';
import {NotificationArgs} from './NotificationArgs';

export type NotificationListener<D extends IFromTrustlyRequestData> = (args: NotificationArgs<D>) => Promise<void>;
export type NotificationListener<M extends string> = (args: NotificationArgs<M>) => Promise<void>;
9 changes: 2 additions & 7 deletions src/client/Serializer.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import {describe, expect} from "@jest/globals";
import {Serializer} from "./Serializer";
import {JsonRpcFactory} from "./JsonRpcFactory";
import {DepositRequestData} from "../domain/methods/deposit/DepositRequestData";
import {RegisterAccountResponseData} from "../domain/methods/registeraccount/RegisterAccountResponseData";
import {TrustlyApiClient} from "./TrustlyApiClient";
import {NotificationResponse} from "../domain/base/NotificationResponse";
import {TrustlyApiClientSettings} from "./TrustlyApiClientSettings";
import {DepositRequestData, RegisterAccountResponseData} from "../domain/models";

describe('serialize', () => {

Expand Down Expand Up @@ -98,9 +96,8 @@ describe('serialize', () => {
.andTrustlyCertificate();

const client = new TrustlyApiClient(settings);
// const signer = new DefaultJsonRpcSigner(serializer, settings);

const rpcResponse = client.createResponsePackage<NotificationResponse>(
const rpcResponse = client.createResponsePackage(
"account",
"e76ffbe5-e0f9-4402-8689-f868ed2021f8",
{
Expand All @@ -111,8 +108,6 @@ describe('serialize', () => {
const serialized = serializer.serializeData(rpcResponse.result?.data);
expect(serialized).toEqual("statusOK");

// const signedResponse = signer.signResponse(rpcResponse);

expect(rpcResponse.result?.signature).toEqual(
"J28IN0yXZN3dlV2ikg4nQKwnP98kso8lzpmuwBcfbXr8i3XeEyydRM4jRwsOOeF0ilGuXyr1Kyb3+1j4mVtgU0SwjVgBHWrYPMegNeykY3meto/aoATH0mvop4Ex1OKO7w/S/ktR2J0J5Npn/EuiKGiVy5GztHYTh9hWmZBCElYPZf4Rsd1CJQJAPlZeAuRcrb5dnbiGJvTEaL/7VLcPT27oqAUefSNb/zNt5yL+wH6BihlkpZ/mtE61lX5OpC46iql6hpsrlOBD3BroYfcwgk1t3YdcNOhVWrmkrlVptGQ/oy6T/LSIKbkG/tJsuV8sl6w1Z31IesK6MZDfSJbcXw=="
);
Expand Down
3 changes: 1 addition & 2 deletions src/client/Serializer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {IData} from '../domain/base/IData';

export class Serializer {

public serializeData<D extends IData>(data: D | undefined): string {
public serializeData<D extends object>(data: D | undefined): string {
return this.serializeNode(data);
}

Expand Down
Loading