@t-0/provider-sdk 1.1.28 → 1.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/lib/cjs/crypto/hash.d.ts +2 -0
- package/lib/cjs/crypto/hash.js +17 -0
- package/lib/cjs/crypto/index.d.ts +5 -0
- package/lib/cjs/crypto/index.js +14 -0
- package/lib/cjs/crypto/keys.d.ts +2 -0
- package/lib/cjs/crypto/keys.js +19 -0
- package/lib/cjs/crypto/request.d.ts +20 -0
- package/lib/cjs/crypto/request.js +49 -0
- package/lib/cjs/crypto/verify.d.ts +1 -0
- package/lib/cjs/crypto/verify.js +19 -0
- package/lib/cjs/index.d.ts +3 -0
- package/lib/cjs/index.js +8 -1
- package/lib/cjs/service/health_pb.js +5 -2
- package/lib/cjs/service/service.d.ts +12 -1
- package/lib/cjs/service/service.js +6 -16
- package/lib/cjs/service/validate.d.ts +25 -0
- package/lib/cjs/service/validate.js +42 -0
- package/lib/cjs/service/validate_response.d.ts +15 -1
- package/lib/cjs/service/validate_response.js +29 -2
- package/lib/cjs/version.d.ts +1 -1
- package/lib/cjs/version.js +1 -1
- package/lib/esm/crypto/hash.d.ts +2 -0
- package/lib/esm/crypto/hash.js +13 -0
- package/lib/esm/crypto/index.d.ts +5 -0
- package/lib/esm/crypto/index.js +4 -0
- package/lib/esm/crypto/keys.d.ts +2 -0
- package/lib/esm/crypto/keys.js +15 -0
- package/lib/esm/crypto/request.d.ts +20 -0
- package/lib/esm/crypto/request.js +45 -0
- package/lib/esm/crypto/verify.d.ts +1 -0
- package/lib/esm/crypto/verify.js +16 -0
- package/lib/esm/index.d.ts +3 -0
- package/lib/esm/index.js +3 -0
- package/lib/esm/service/health_pb.js +5 -2
- package/lib/esm/service/service.d.ts +12 -1
- package/lib/esm/service/service.js +6 -16
- package/lib/esm/service/validate.d.ts +25 -0
- package/lib/esm/service/validate.js +38 -0
- package/lib/esm/service/validate_response.d.ts +15 -1
- package/lib/esm/service/validate_response.js +29 -2
- package/lib/esm/version.d.ts +1 -1
- package/lib/esm/version.js +1 -1
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +15 -5
package/lib/esm/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export * from "./crypto/index.js";
|
|
1
2
|
export * from "./client/client.js";
|
|
2
3
|
export * from "./service/service.js";
|
|
3
4
|
export * from "./service/validate_response.js";
|
|
5
|
+
export * from "./service/validate.js";
|
|
4
6
|
export * from "./service/node.js";
|
|
7
|
+
export { default as NetworkHeaders } from "./common/headers.js";
|
|
5
8
|
export { connectNodeAdapter as nodeAdapter } from "@connectrpc/connect-node";
|
|
6
9
|
export type { Client, HandlerContext } from "@connectrpc/connect";
|
|
7
10
|
export * from './common/gen/tzero/v1/common/common_pb.js';
|
package/lib/esm/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export * from "./crypto/index.js";
|
|
1
2
|
export * from "./client/client.js";
|
|
2
3
|
export * from "./service/service.js";
|
|
3
4
|
export * from "./service/validate_response.js";
|
|
5
|
+
export * from "./service/validate.js";
|
|
4
6
|
export * from "./service/node.js";
|
|
7
|
+
export { default as NetworkHeaders } from "./common/headers.js";
|
|
5
8
|
export { connectNodeAdapter as nodeAdapter } from "@connectrpc/connect-node";
|
|
6
9
|
export * from './common/gen/tzero/v1/common/common_pb.js';
|
|
7
10
|
export * from './common/gen/tzero/v1/common/payment_method_pb.js';
|
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
// buf.gen.yaml pin -- plugin and @bufbuild/protobuf runtime must
|
|
6
6
|
// agree on major).
|
|
7
7
|
//
|
|
8
|
-
// Regenerate
|
|
8
|
+
// Regenerate with the following, which keeps this header and replaces
|
|
9
|
+
// everything from the gRPC copyright line down. Do not just `mv` the generated
|
|
10
|
+
// file over this one -- that would delete these instructions along with it.
|
|
9
11
|
//
|
|
10
12
|
// cd node/sdk
|
|
11
13
|
// buf generate buf.build/grpc/grpc --path grpc/health/v1/health.proto \
|
|
12
14
|
// --template '{"version":"v2","plugins":[{"remote":"buf.build/bufbuild/es:v2.12.0","out":"gen-health","opt":["target=ts","import_extension=js"]}]}'
|
|
13
|
-
//
|
|
15
|
+
// sed '/^\/\/ Copyright 2015 The gRPC Authors/,$d' src/service/health_pb.ts > gen-health/header
|
|
16
|
+
// cat gen-health/header gen-health/grpc/health/v1/health_pb.ts > src/service/health_pb.ts
|
|
14
17
|
// rm -rf gen-health
|
|
15
18
|
//
|
|
16
19
|
// Vendored rather than depended on: the equivalent package
|
|
@@ -2,11 +2,22 @@ import { ConnectRouter } from "@connectrpc/connect";
|
|
|
2
2
|
import type { Interceptor } from "@connectrpc/connect";
|
|
3
3
|
import type { DescService } from "@bufbuild/protobuf";
|
|
4
4
|
import type { ServiceImpl } from "@connectrpc/connect";
|
|
5
|
+
import { type Logger } from "./validate_response.js";
|
|
6
|
+
export interface CreateServiceOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Logger used by the SDK for error-level events (currently:
|
|
9
|
+
* response-validation failures from the interceptor safety net). The same
|
|
10
|
+
* logger will be used for any future server-wide SDK log sites.
|
|
11
|
+
*
|
|
12
|
+
* If omitted, the SDK logs to `console.error` with a JSON-encoded payload.
|
|
13
|
+
*/
|
|
14
|
+
logger?: Logger;
|
|
15
|
+
}
|
|
5
16
|
export declare const REQUEST_VALIDITY_MILLIS = 60000;
|
|
6
17
|
interface Router {
|
|
7
18
|
service: <T extends DescService, I extends ServiceImpl<T>>(service: T, implementation: I) => void;
|
|
8
19
|
}
|
|
9
|
-
export declare const createService: (networkPublicKey: string | Buffer, registerRoutes: (router: Router) => void) => {
|
|
20
|
+
export declare const createService: (networkPublicKey: string | Buffer, registerRoutes: (router: Router) => void, options?: CreateServiceOptions) => {
|
|
10
21
|
routes: (router: ConnectRouter) => void;
|
|
11
22
|
interceptors: Interceptor[];
|
|
12
23
|
grpcWeb: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Code, ConnectError, createContextKey, createContextValues } from "@connectrpc/connect";
|
|
2
2
|
import NetworkHeaders from "../common/headers.js";
|
|
3
|
-
import {
|
|
3
|
+
import { verifySignature } from '../crypto/verify.js';
|
|
4
4
|
import { createValidationInterceptor } from "./validate_response.js";
|
|
5
5
|
import { Health } from "./health_pb.js";
|
|
6
6
|
import { createHealthServiceImpl } from "./health.js";
|
|
@@ -14,29 +14,19 @@ const createSignatureVerification = (networkPublicKey) => (next) => async (req)
|
|
|
14
14
|
if (networkPublicKey.compare(publicKey) !== 0) {
|
|
15
15
|
throw new ConnectError(`${NetworkHeaders.PublicKey} value is not network public key`, Code.Unauthenticated);
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
if (signature.length === 65) {
|
|
19
|
-
signature = signature.subarray(0, 64);
|
|
20
|
-
}
|
|
17
|
+
const signature = decodeHex(getHeader(req, NetworkHeaders.Signature));
|
|
21
18
|
const hasher = req.contextValues.get(kHash);
|
|
22
19
|
const tsBuf = Buffer.alloc(8);
|
|
23
20
|
tsBuf.writeBigUInt64LE(BigInt(ts)); // 64‑bit little‑endian timestamp
|
|
24
|
-
const
|
|
21
|
+
const digest = hasher
|
|
25
22
|
.update(tsBuf)
|
|
26
23
|
.digest();
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
signatureValid = secp256k1.verify(signature, hash, publicKey, { prehash: false });
|
|
30
|
-
}
|
|
31
|
-
catch (e) {
|
|
32
|
-
throw new ConnectError(`${NetworkHeaders.Signature} has invalid signature or public key format: ${e}`, Code.Unauthenticated);
|
|
33
|
-
}
|
|
34
|
-
if (!signatureValid) {
|
|
24
|
+
if (!verifySignature(publicKey, digest, signature)) {
|
|
35
25
|
throw new ConnectError(`${NetworkHeaders.Signature} has invalid signature`, Code.Unauthenticated);
|
|
36
26
|
}
|
|
37
27
|
return await next(req);
|
|
38
28
|
};
|
|
39
|
-
export const createService = (networkPublicKey, registerRoutes) => {
|
|
29
|
+
export const createService = (networkPublicKey, registerRoutes, options) => {
|
|
40
30
|
if (typeof networkPublicKey == "string") {
|
|
41
31
|
networkPublicKey = decodeHex(networkPublicKey);
|
|
42
32
|
}
|
|
@@ -57,7 +47,7 @@ export const createService = (networkPublicKey, registerRoutes) => {
|
|
|
57
47
|
collected.push(Health.typeName);
|
|
58
48
|
origService(Health, createHealthServiceImpl(collected));
|
|
59
49
|
},
|
|
60
|
-
interceptors: [createSignatureVerification(networkPublicKey), createValidationInterceptor()],
|
|
50
|
+
interceptors: [createSignatureVerification(networkPublicKey), createValidationInterceptor(options?.logger)],
|
|
61
51
|
grpcWeb: false,
|
|
62
52
|
contextValues: (req) => {
|
|
63
53
|
return createContextValues().set(kHash, req.hasher);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DescMessage, MessageShape } from "@bufbuild/protobuf";
|
|
2
|
+
/**
|
|
3
|
+
* Shared protovalidate validator instance for the public {@link validate} helper.
|
|
4
|
+
* Construction is cheap, but reusing one instance avoids repeated compilation
|
|
5
|
+
* of the same rules.
|
|
6
|
+
*/
|
|
7
|
+
export declare const validator: import("@bufbuild/protovalidate").Validator;
|
|
8
|
+
/**
|
|
9
|
+
* Validates a response message against its buf.validate proto annotations.
|
|
10
|
+
*
|
|
11
|
+
* On success, returns the message unchanged (typed). On failure, throws a
|
|
12
|
+
* {@link ConnectError} with {@link Code.Internal} and a message matching the
|
|
13
|
+
* shape emitted by the SDK's response-validation interceptor — so propagating
|
|
14
|
+
* the error from a handler produces the same wire response as not calling
|
|
15
|
+
* `validate` at all.
|
|
16
|
+
*
|
|
17
|
+
* Intended use:
|
|
18
|
+
* ```ts
|
|
19
|
+
* return validate(PayOutResponseSchema, { result: { case: "accepted", value: {} } });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Catch the error to convert it into a domain-level failure (e.g. the `Failed`
|
|
23
|
+
* arm of a `oneof result`) instead of an opaque `Code.Internal`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validate<Desc extends DescMessage>(schema: Desc, msg: MessageShape<Desc>): MessageShape<Desc>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Code, ConnectError } from "@connectrpc/connect";
|
|
2
|
+
import { createValidator } from "@bufbuild/protovalidate";
|
|
3
|
+
/**
|
|
4
|
+
* Shared protovalidate validator instance for the public {@link validate} helper.
|
|
5
|
+
* Construction is cheap, but reusing one instance avoids repeated compilation
|
|
6
|
+
* of the same rules.
|
|
7
|
+
*/
|
|
8
|
+
export const validator = createValidator();
|
|
9
|
+
/**
|
|
10
|
+
* Validates a response message against its buf.validate proto annotations.
|
|
11
|
+
*
|
|
12
|
+
* On success, returns the message unchanged (typed). On failure, throws a
|
|
13
|
+
* {@link ConnectError} with {@link Code.Internal} and a message matching the
|
|
14
|
+
* shape emitted by the SDK's response-validation interceptor — so propagating
|
|
15
|
+
* the error from a handler produces the same wire response as not calling
|
|
16
|
+
* `validate` at all.
|
|
17
|
+
*
|
|
18
|
+
* Intended use:
|
|
19
|
+
* ```ts
|
|
20
|
+
* return validate(PayOutResponseSchema, { result: { case: "accepted", value: {} } });
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Catch the error to convert it into a domain-level failure (e.g. the `Failed`
|
|
24
|
+
* arm of a `oneof result`) instead of an opaque `Code.Internal`.
|
|
25
|
+
*/
|
|
26
|
+
export function validate(schema, msg) {
|
|
27
|
+
const result = validator.validate(schema, msg);
|
|
28
|
+
if (result.kind === "invalid") {
|
|
29
|
+
const details = result.violations
|
|
30
|
+
.map((v) => `${v.field?.toString() ?? ""}: ${v.message}`)
|
|
31
|
+
.join("; ");
|
|
32
|
+
throw new ConnectError(`response validation failed: ${details}`, Code.Internal);
|
|
33
|
+
}
|
|
34
|
+
if (result.kind === "error") {
|
|
35
|
+
throw new ConnectError(`response validation error: ${result.error.message}`, Code.Internal);
|
|
36
|
+
}
|
|
37
|
+
return msg;
|
|
38
|
+
}
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import type { Interceptor } from "@connectrpc/connect";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal logger contract accepted by the SDK. Providers may pass `console`
|
|
4
|
+
* directly, or adapt their preferred logger (e.g. pino) with:
|
|
5
|
+
*
|
|
6
|
+
* { error: (msg, fields) => pinoInstance.error(fields, msg) }
|
|
7
|
+
*/
|
|
8
|
+
export interface Logger {
|
|
9
|
+
error(msg: string, fields?: Record<string, unknown>): void;
|
|
10
|
+
}
|
|
2
11
|
/**
|
|
3
12
|
* Creates a ConnectRPC interceptor that validates provider responses against
|
|
4
13
|
* buf.validate proto annotations before they are serialized and sent.
|
|
5
14
|
* Also validates incoming requests using the official @connectrpc/validate interceptor.
|
|
6
15
|
*
|
|
7
16
|
* Invalid requests return Code.InvalidArgument; invalid responses return Code.Internal.
|
|
17
|
+
*
|
|
18
|
+
* On invalid responses, a single structured `error`-level line is emitted to
|
|
19
|
+
* the supplied {@link Logger} (default: `console.error` with JSON-encoded
|
|
20
|
+
* fields) before the `Code.Internal` error is thrown. This is the safety net
|
|
21
|
+
* for handler code paths that skipped the public `validate()` helper.
|
|
8
22
|
*/
|
|
9
|
-
export declare function createValidationInterceptor(): Interceptor;
|
|
23
|
+
export declare function createValidationInterceptor(logger?: Logger): Interceptor;
|
|
10
24
|
/**
|
|
11
25
|
* @deprecated Use createValidationInterceptor instead.
|
|
12
26
|
*/
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
2
2
|
import { createValidator } from "@bufbuild/protovalidate";
|
|
3
3
|
import { createValidateInterceptor } from "@connectrpc/validate";
|
|
4
|
+
import { SDK_VERSION } from "../version.js";
|
|
4
5
|
const validator = createValidator();
|
|
6
|
+
const defaultLogger = {
|
|
7
|
+
error: (msg, fields) =>
|
|
8
|
+
// eslint-disable-next-line no-console
|
|
9
|
+
console.error(JSON.stringify({ msg, ...(fields ?? {}) })),
|
|
10
|
+
};
|
|
5
11
|
/**
|
|
6
12
|
* Creates a ConnectRPC interceptor that validates provider responses against
|
|
7
13
|
* buf.validate proto annotations before they are serialized and sent.
|
|
8
14
|
* Also validates incoming requests using the official @connectrpc/validate interceptor.
|
|
9
15
|
*
|
|
10
16
|
* Invalid requests return Code.InvalidArgument; invalid responses return Code.Internal.
|
|
17
|
+
*
|
|
18
|
+
* On invalid responses, a single structured `error`-level line is emitted to
|
|
19
|
+
* the supplied {@link Logger} (default: `console.error` with JSON-encoded
|
|
20
|
+
* fields) before the `Code.Internal` error is thrown. This is the safety net
|
|
21
|
+
* for handler code paths that skipped the public `validate()` helper.
|
|
11
22
|
*/
|
|
12
|
-
export function createValidationInterceptor() {
|
|
23
|
+
export function createValidationInterceptor(logger = defaultLogger) {
|
|
13
24
|
const requestInterceptor = createValidateInterceptor();
|
|
14
25
|
return (next) => async (req) => {
|
|
15
26
|
// Validate request (delegates to official interceptor which throws on invalid)
|
|
@@ -19,10 +30,26 @@ export function createValidationInterceptor() {
|
|
|
19
30
|
const msg = resp.message;
|
|
20
31
|
const result = validator.validate(schema, msg);
|
|
21
32
|
if (result.kind === "invalid") {
|
|
22
|
-
const
|
|
33
|
+
const violations = result.violations.map((v) => ({
|
|
34
|
+
field: v.field?.toString() ?? "",
|
|
35
|
+
message: v.message,
|
|
36
|
+
}));
|
|
37
|
+
const details = violations.map((v) => `${v.field}: ${v.message}`).join("; ");
|
|
38
|
+
logger.error("response validation failed", {
|
|
39
|
+
rpc_method: `${req.service.typeName}/${req.method.name}`,
|
|
40
|
+
response_type: schema.typeName,
|
|
41
|
+
violations,
|
|
42
|
+
sdk_version: SDK_VERSION,
|
|
43
|
+
});
|
|
23
44
|
throw new ConnectError(`response validation failed: ${details}`, Code.Internal);
|
|
24
45
|
}
|
|
25
46
|
if (result.kind === "error") {
|
|
47
|
+
logger.error("response validation error", {
|
|
48
|
+
rpc_method: `${req.service.typeName}/${req.method.name}`,
|
|
49
|
+
response_type: schema.typeName,
|
|
50
|
+
error: result.error.message,
|
|
51
|
+
sdk_version: SDK_VERSION,
|
|
52
|
+
});
|
|
26
53
|
throw new ConnectError(`response validation error: ${result.error.message}`, Code.Internal);
|
|
27
54
|
}
|
|
28
55
|
return resp;
|
package/lib/esm/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "1.1.
|
|
1
|
+
export declare const SDK_VERSION = "1.1.29";
|
package/lib/esm/version.js
CHANGED