@t-0/provider-sdk 1.1.27 → 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 -3
- 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.d.ts +1 -1
- package/lib/cjs/service/health.js +1 -5
- package/lib/cjs/service/health_pb.d.ts +157 -0
- package/lib/cjs/service/health_pb.js +90 -0
- package/lib/cjs/service/service.d.ts +12 -1
- package/lib/cjs/service/service.js +7 -17
- 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.d.ts +1 -1
- package/lib/esm/service/health.js +1 -5
- package/lib/esm/service/health_pb.d.ts +157 -0
- package/lib/esm/service/health_pb.js +87 -0
- package/lib/esm/service/service.d.ts +12 -1
- package/lib/esm/service/service.js +7 -17
- 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 -6
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validator = void 0;
|
|
4
|
+
exports.validate = validate;
|
|
5
|
+
const connect_1 = require("@connectrpc/connect");
|
|
6
|
+
const protovalidate_1 = require("@bufbuild/protovalidate");
|
|
7
|
+
/**
|
|
8
|
+
* Shared protovalidate validator instance for the public {@link validate} helper.
|
|
9
|
+
* Construction is cheap, but reusing one instance avoids repeated compilation
|
|
10
|
+
* of the same rules.
|
|
11
|
+
*/
|
|
12
|
+
exports.validator = (0, protovalidate_1.createValidator)();
|
|
13
|
+
/**
|
|
14
|
+
* Validates a response message against its buf.validate proto annotations.
|
|
15
|
+
*
|
|
16
|
+
* On success, returns the message unchanged (typed). On failure, throws a
|
|
17
|
+
* {@link ConnectError} with {@link Code.Internal} and a message matching the
|
|
18
|
+
* shape emitted by the SDK's response-validation interceptor — so propagating
|
|
19
|
+
* the error from a handler produces the same wire response as not calling
|
|
20
|
+
* `validate` at all.
|
|
21
|
+
*
|
|
22
|
+
* Intended use:
|
|
23
|
+
* ```ts
|
|
24
|
+
* return validate(PayOutResponseSchema, { result: { case: "accepted", value: {} } });
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Catch the error to convert it into a domain-level failure (e.g. the `Failed`
|
|
28
|
+
* arm of a `oneof result`) instead of an opaque `Code.Internal`.
|
|
29
|
+
*/
|
|
30
|
+
function validate(schema, msg) {
|
|
31
|
+
const result = exports.validator.validate(schema, msg);
|
|
32
|
+
if (result.kind === "invalid") {
|
|
33
|
+
const details = result.violations
|
|
34
|
+
.map((v) => `${v.field?.toString() ?? ""}: ${v.message}`)
|
|
35
|
+
.join("; ");
|
|
36
|
+
throw new connect_1.ConnectError(`response validation failed: ${details}`, connect_1.Code.Internal);
|
|
37
|
+
}
|
|
38
|
+
if (result.kind === "error") {
|
|
39
|
+
throw new connect_1.ConnectError(`response validation error: ${result.error.message}`, connect_1.Code.Internal);
|
|
40
|
+
}
|
|
41
|
+
return msg;
|
|
42
|
+
}
|
|
@@ -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
|
*/
|
|
@@ -5,15 +5,26 @@ exports.createValidationInterceptor = createValidationInterceptor;
|
|
|
5
5
|
const connect_1 = require("@connectrpc/connect");
|
|
6
6
|
const protovalidate_1 = require("@bufbuild/protovalidate");
|
|
7
7
|
const validate_1 = require("@connectrpc/validate");
|
|
8
|
+
const version_js_1 = require("../version.js");
|
|
8
9
|
const validator = (0, protovalidate_1.createValidator)();
|
|
10
|
+
const defaultLogger = {
|
|
11
|
+
error: (msg, fields) =>
|
|
12
|
+
// eslint-disable-next-line no-console
|
|
13
|
+
console.error(JSON.stringify({ msg, ...(fields ?? {}) })),
|
|
14
|
+
};
|
|
9
15
|
/**
|
|
10
16
|
* Creates a ConnectRPC interceptor that validates provider responses against
|
|
11
17
|
* buf.validate proto annotations before they are serialized and sent.
|
|
12
18
|
* Also validates incoming requests using the official @connectrpc/validate interceptor.
|
|
13
19
|
*
|
|
14
20
|
* Invalid requests return Code.InvalidArgument; invalid responses return Code.Internal.
|
|
21
|
+
*
|
|
22
|
+
* On invalid responses, a single structured `error`-level line is emitted to
|
|
23
|
+
* the supplied {@link Logger} (default: `console.error` with JSON-encoded
|
|
24
|
+
* fields) before the `Code.Internal` error is thrown. This is the safety net
|
|
25
|
+
* for handler code paths that skipped the public `validate()` helper.
|
|
15
26
|
*/
|
|
16
|
-
function createValidationInterceptor() {
|
|
27
|
+
function createValidationInterceptor(logger = defaultLogger) {
|
|
17
28
|
const requestInterceptor = (0, validate_1.createValidateInterceptor)();
|
|
18
29
|
return (next) => async (req) => {
|
|
19
30
|
// Validate request (delegates to official interceptor which throws on invalid)
|
|
@@ -23,10 +34,26 @@ function createValidationInterceptor() {
|
|
|
23
34
|
const msg = resp.message;
|
|
24
35
|
const result = validator.validate(schema, msg);
|
|
25
36
|
if (result.kind === "invalid") {
|
|
26
|
-
const
|
|
37
|
+
const violations = result.violations.map((v) => ({
|
|
38
|
+
field: v.field?.toString() ?? "",
|
|
39
|
+
message: v.message,
|
|
40
|
+
}));
|
|
41
|
+
const details = violations.map((v) => `${v.field}: ${v.message}`).join("; ");
|
|
42
|
+
logger.error("response validation failed", {
|
|
43
|
+
rpc_method: `${req.service.typeName}/${req.method.name}`,
|
|
44
|
+
response_type: schema.typeName,
|
|
45
|
+
violations,
|
|
46
|
+
sdk_version: version_js_1.SDK_VERSION,
|
|
47
|
+
});
|
|
27
48
|
throw new connect_1.ConnectError(`response validation failed: ${details}`, connect_1.Code.Internal);
|
|
28
49
|
}
|
|
29
50
|
if (result.kind === "error") {
|
|
51
|
+
logger.error("response validation error", {
|
|
52
|
+
rpc_method: `${req.service.typeName}/${req.method.name}`,
|
|
53
|
+
response_type: schema.typeName,
|
|
54
|
+
error: result.error.message,
|
|
55
|
+
sdk_version: version_js_1.SDK_VERSION,
|
|
56
|
+
});
|
|
30
57
|
throw new connect_1.ConnectError(`response validation error: ${result.error.message}`, connect_1.Code.Internal);
|
|
31
58
|
}
|
|
32
59
|
return resp;
|
package/lib/cjs/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/cjs/version.js
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
2
|
+
export function keccak256(...inputs) {
|
|
3
|
+
const h = keccak_256.create();
|
|
4
|
+
for (const input of inputs) {
|
|
5
|
+
h.update(input);
|
|
6
|
+
}
|
|
7
|
+
return Buffer.from(h.digest());
|
|
8
|
+
}
|
|
9
|
+
export function computeDigest(body, timestampMs) {
|
|
10
|
+
const tsBuf = Buffer.alloc(8);
|
|
11
|
+
tsBuf.writeBigUInt64LE(BigInt(timestampMs));
|
|
12
|
+
return keccak256(body, tsBuf);
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { verifySignature } from './verify.js';
|
|
2
|
+
export { keccak256, computeDigest } from './hash.js';
|
|
3
|
+
export { parsePublicKey, publicKeysEqual } from './keys.js';
|
|
4
|
+
export { createRequestVerifier, DEFAULT_TOLERANCE_MS } from './request.js';
|
|
5
|
+
export type { CreateVerifierOptions, VerifyRequest, VerifyRequestResult, VerifyRequestFailure, RequestVerifier } from './request.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function parsePublicKey(key) {
|
|
2
|
+
if (typeof key === 'string') {
|
|
3
|
+
key = Buffer.from(key.startsWith('0x') ? key.slice(2) : key, 'hex');
|
|
4
|
+
}
|
|
5
|
+
if (key.length !== 65 || key[0] !== 0x04) {
|
|
6
|
+
throw new Error('Public key must be 65 bytes in uncompressed format (0x04 prefix)');
|
|
7
|
+
}
|
|
8
|
+
return Buffer.from(key);
|
|
9
|
+
}
|
|
10
|
+
export function publicKeysEqual(a, b) {
|
|
11
|
+
if (a.length !== b.length) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return Buffer.from(a).compare(Buffer.from(b)) === 0;
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const DEFAULT_TOLERANCE_MS = 60000;
|
|
2
|
+
export interface CreateVerifierOptions {
|
|
3
|
+
networkPublicKey: string | Buffer;
|
|
4
|
+
toleranceMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface VerifyRequest {
|
|
7
|
+
body: Uint8Array;
|
|
8
|
+
signatureHeader: string;
|
|
9
|
+
publicKeyHeader: string;
|
|
10
|
+
timestampHeader: string;
|
|
11
|
+
}
|
|
12
|
+
export type VerifyRequestFailure = 'invalid_timestamp' | 'timestamp_out_of_range' | 'invalid_public_key' | 'unknown_public_key' | 'invalid_signature_format' | 'signature_failed';
|
|
13
|
+
export type VerifyRequestResult = {
|
|
14
|
+
valid: true;
|
|
15
|
+
} | {
|
|
16
|
+
valid: false;
|
|
17
|
+
reason: VerifyRequestFailure;
|
|
18
|
+
};
|
|
19
|
+
export type RequestVerifier = (req: VerifyRequest) => VerifyRequestResult;
|
|
20
|
+
export declare function createRequestVerifier(opts: CreateVerifierOptions): RequestVerifier;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { verifySignature } from './verify.js';
|
|
2
|
+
import { computeDigest } from './hash.js';
|
|
3
|
+
import { parsePublicKey, publicKeysEqual } from './keys.js';
|
|
4
|
+
export const DEFAULT_TOLERANCE_MS = 60000;
|
|
5
|
+
export function createRequestVerifier(opts) {
|
|
6
|
+
const networkKey = parsePublicKey(opts.networkPublicKey);
|
|
7
|
+
const tolerance = opts.toleranceMs ?? DEFAULT_TOLERANCE_MS;
|
|
8
|
+
return (req) => {
|
|
9
|
+
const ts = parseInt(req.timestampHeader, 10);
|
|
10
|
+
if (!Number.isFinite(ts) || ts < 0) {
|
|
11
|
+
return { valid: false, reason: 'invalid_timestamp' };
|
|
12
|
+
}
|
|
13
|
+
if (Math.abs(Date.now() - ts) > tolerance) {
|
|
14
|
+
return { valid: false, reason: 'timestamp_out_of_range' };
|
|
15
|
+
}
|
|
16
|
+
let publicKey;
|
|
17
|
+
try {
|
|
18
|
+
publicKey = parsePublicKey(req.publicKeyHeader);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return { valid: false, reason: 'invalid_public_key' };
|
|
22
|
+
}
|
|
23
|
+
if (!publicKeysEqual(publicKey, networkKey)) {
|
|
24
|
+
return { valid: false, reason: 'unknown_public_key' };
|
|
25
|
+
}
|
|
26
|
+
let signature;
|
|
27
|
+
try {
|
|
28
|
+
const hex = req.signatureHeader.startsWith('0x')
|
|
29
|
+
? req.signatureHeader.slice(2)
|
|
30
|
+
: req.signatureHeader;
|
|
31
|
+
signature = Buffer.from(hex, 'hex');
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { valid: false, reason: 'invalid_signature_format' };
|
|
35
|
+
}
|
|
36
|
+
if (signature.length !== 64 && signature.length !== 65) {
|
|
37
|
+
return { valid: false, reason: 'invalid_signature_format' };
|
|
38
|
+
}
|
|
39
|
+
const digest = computeDigest(req.body, ts);
|
|
40
|
+
if (!verifySignature(publicKey, digest, signature)) {
|
|
41
|
+
return { valid: false, reason: 'signature_failed' };
|
|
42
|
+
}
|
|
43
|
+
return { valid: true };
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function verifySignature(publicKey: Uint8Array, digest: Uint8Array, signature: Uint8Array): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
2
|
+
export function verifySignature(publicKey, digest, signature) {
|
|
3
|
+
if (digest.length !== 32) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
if (signature.length !== 64 && signature.length !== 65) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const sig64 = signature.length === 65 ? signature.subarray(0, 64) : signature;
|
|
10
|
+
try {
|
|
11
|
+
return secp256k1.verify(sig64, digest, publicKey, { prehash: false });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
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';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ServiceImpl } from "@connectrpc/connect";
|
|
2
|
-
import { Health } from "
|
|
2
|
+
import { Health } from "./health_pb.js";
|
|
3
3
|
/**
|
|
4
4
|
* Headers carrying the identity of the SDK answering the probe. They ride on
|
|
5
5
|
* the health response and nowhere else: `HealthCheckResponse` has a single
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { create } from "@bufbuild/protobuf";
|
|
2
2
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
3
|
-
import { HealthCheckResponse_ServingStatus, HealthCheckResponseSchema, } from "
|
|
3
|
+
import { HealthCheckResponse_ServingStatus, HealthCheckResponseSchema, } from "./health_pb.js";
|
|
4
4
|
import { SDK_VERSION } from "../version.js";
|
|
5
5
|
/**
|
|
6
6
|
* Headers carrying the identity of the SDK answering the probe. They ride on
|
|
@@ -30,10 +30,6 @@ export const createHealthServiceImpl = (services) => {
|
|
|
30
30
|
check(req, ctx) {
|
|
31
31
|
ctx.responseHeader.set(SDK_ECOSYSTEM_HEADER, SDK_ECOSYSTEM);
|
|
32
32
|
ctx.responseHeader.set(SDK_VERSION_HEADER, SDK_VERSION);
|
|
33
|
-
// The schema comes from a published .d.ts, whose type brands do not
|
|
34
|
-
// survive ServiceImpl's generics — connect-es widens the request to
|
|
35
|
-
// Message<string>. The descriptor is still the real one at runtime, so
|
|
36
|
-
// this narrows back to what the wire actually carries.
|
|
37
33
|
const { service } = req;
|
|
38
34
|
// An empty service name asks about the process as a whole, which is up if
|
|
39
35
|
// this handler is running at all.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
|
2
|
+
import type { Message } from "@bufbuild/protobuf";
|
|
3
|
+
/**
|
|
4
|
+
* Describes the file grpc/health/v1/health.proto.
|
|
5
|
+
*/
|
|
6
|
+
export declare const file_grpc_health_v1_health: GenFile;
|
|
7
|
+
/**
|
|
8
|
+
* @generated from message grpc.health.v1.HealthCheckRequest
|
|
9
|
+
*/
|
|
10
|
+
export type HealthCheckRequest = Message<"grpc.health.v1.HealthCheckRequest"> & {
|
|
11
|
+
/**
|
|
12
|
+
* @generated from field: string service = 1;
|
|
13
|
+
*/
|
|
14
|
+
service: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Describes the message grpc.health.v1.HealthCheckRequest.
|
|
18
|
+
* Use `create(HealthCheckRequestSchema)` to create a new message.
|
|
19
|
+
*/
|
|
20
|
+
export declare const HealthCheckRequestSchema: GenMessage<HealthCheckRequest>;
|
|
21
|
+
/**
|
|
22
|
+
* @generated from message grpc.health.v1.HealthCheckResponse
|
|
23
|
+
*/
|
|
24
|
+
export type HealthCheckResponse = Message<"grpc.health.v1.HealthCheckResponse"> & {
|
|
25
|
+
/**
|
|
26
|
+
* @generated from field: grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;
|
|
27
|
+
*/
|
|
28
|
+
status: HealthCheckResponse_ServingStatus;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Describes the message grpc.health.v1.HealthCheckResponse.
|
|
32
|
+
* Use `create(HealthCheckResponseSchema)` to create a new message.
|
|
33
|
+
*/
|
|
34
|
+
export declare const HealthCheckResponseSchema: GenMessage<HealthCheckResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* @generated from enum grpc.health.v1.HealthCheckResponse.ServingStatus
|
|
37
|
+
*/
|
|
38
|
+
export declare enum HealthCheckResponse_ServingStatus {
|
|
39
|
+
/**
|
|
40
|
+
* @generated from enum value: UNKNOWN = 0;
|
|
41
|
+
*/
|
|
42
|
+
UNKNOWN = 0,
|
|
43
|
+
/**
|
|
44
|
+
* @generated from enum value: SERVING = 1;
|
|
45
|
+
*/
|
|
46
|
+
SERVING = 1,
|
|
47
|
+
/**
|
|
48
|
+
* @generated from enum value: NOT_SERVING = 2;
|
|
49
|
+
*/
|
|
50
|
+
NOT_SERVING = 2,
|
|
51
|
+
/**
|
|
52
|
+
* Used only by the Watch method.
|
|
53
|
+
*
|
|
54
|
+
* @generated from enum value: SERVICE_UNKNOWN = 3;
|
|
55
|
+
*/
|
|
56
|
+
SERVICE_UNKNOWN = 3
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Describes the enum grpc.health.v1.HealthCheckResponse.ServingStatus.
|
|
60
|
+
*/
|
|
61
|
+
export declare const HealthCheckResponse_ServingStatusSchema: GenEnum<HealthCheckResponse_ServingStatus>;
|
|
62
|
+
/**
|
|
63
|
+
* @generated from message grpc.health.v1.HealthListRequest
|
|
64
|
+
*/
|
|
65
|
+
export type HealthListRequest = Message<"grpc.health.v1.HealthListRequest"> & {};
|
|
66
|
+
/**
|
|
67
|
+
* Describes the message grpc.health.v1.HealthListRequest.
|
|
68
|
+
* Use `create(HealthListRequestSchema)` to create a new message.
|
|
69
|
+
*/
|
|
70
|
+
export declare const HealthListRequestSchema: GenMessage<HealthListRequest>;
|
|
71
|
+
/**
|
|
72
|
+
* @generated from message grpc.health.v1.HealthListResponse
|
|
73
|
+
*/
|
|
74
|
+
export type HealthListResponse = Message<"grpc.health.v1.HealthListResponse"> & {
|
|
75
|
+
/**
|
|
76
|
+
* statuses contains all the services and their respective status.
|
|
77
|
+
*
|
|
78
|
+
* @generated from field: map<string, grpc.health.v1.HealthCheckResponse> statuses = 1;
|
|
79
|
+
*/
|
|
80
|
+
statuses: {
|
|
81
|
+
[key: string]: HealthCheckResponse;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Describes the message grpc.health.v1.HealthListResponse.
|
|
86
|
+
* Use `create(HealthListResponseSchema)` to create a new message.
|
|
87
|
+
*/
|
|
88
|
+
export declare const HealthListResponseSchema: GenMessage<HealthListResponse>;
|
|
89
|
+
/**
|
|
90
|
+
* Health is gRPC's mechanism for checking whether a server is able to handle
|
|
91
|
+
* RPCs. Its semantics are documented in
|
|
92
|
+
* https://github.com/grpc/grpc/blob/master/doc/health-checking.md.
|
|
93
|
+
*
|
|
94
|
+
* @generated from service grpc.health.v1.Health
|
|
95
|
+
*/
|
|
96
|
+
export declare const Health: GenService<{
|
|
97
|
+
/**
|
|
98
|
+
* Check gets the health of the specified service. If the requested service
|
|
99
|
+
* is unknown, the call will fail with status NOT_FOUND. If the caller does
|
|
100
|
+
* not specify a service name, the server should respond with its overall
|
|
101
|
+
* health status.
|
|
102
|
+
*
|
|
103
|
+
* Clients should set a deadline when calling Check, and can declare the
|
|
104
|
+
* server unhealthy if they do not receive a timely response.
|
|
105
|
+
*
|
|
106
|
+
* @generated from rpc grpc.health.v1.Health.Check
|
|
107
|
+
*/
|
|
108
|
+
check: {
|
|
109
|
+
methodKind: "unary";
|
|
110
|
+
input: typeof HealthCheckRequestSchema;
|
|
111
|
+
output: typeof HealthCheckResponseSchema;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* List provides a non-atomic snapshot of the health of all the available
|
|
115
|
+
* services.
|
|
116
|
+
*
|
|
117
|
+
* The server may respond with a RESOURCE_EXHAUSTED error if too many services
|
|
118
|
+
* exist.
|
|
119
|
+
*
|
|
120
|
+
* Clients should set a deadline when calling List, and can declare the server
|
|
121
|
+
* unhealthy if they do not receive a timely response.
|
|
122
|
+
*
|
|
123
|
+
* Clients should keep in mind that the list of health services exposed by an
|
|
124
|
+
* application can change over the lifetime of the process.
|
|
125
|
+
*
|
|
126
|
+
* @generated from rpc grpc.health.v1.Health.List
|
|
127
|
+
*/
|
|
128
|
+
list: {
|
|
129
|
+
methodKind: "unary";
|
|
130
|
+
input: typeof HealthListRequestSchema;
|
|
131
|
+
output: typeof HealthListResponseSchema;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Performs a watch for the serving status of the requested service.
|
|
135
|
+
* The server will immediately send back a message indicating the current
|
|
136
|
+
* serving status. It will then subsequently send a new message whenever
|
|
137
|
+
* the service's serving status changes.
|
|
138
|
+
*
|
|
139
|
+
* If the requested service is unknown when the call is received, the
|
|
140
|
+
* server will send a message setting the serving status to
|
|
141
|
+
* SERVICE_UNKNOWN but will *not* terminate the call. If at some
|
|
142
|
+
* future point, the serving status of the service becomes known, the
|
|
143
|
+
* server will send a new message with the service's serving status.
|
|
144
|
+
*
|
|
145
|
+
* If the call terminates with status UNIMPLEMENTED, then clients
|
|
146
|
+
* should assume this method is not supported and should not retry the
|
|
147
|
+
* call. If the call terminates with any other status (including OK),
|
|
148
|
+
* clients should retry the call with appropriate exponential backoff.
|
|
149
|
+
*
|
|
150
|
+
* @generated from rpc grpc.health.v1.Health.Watch
|
|
151
|
+
*/
|
|
152
|
+
watch: {
|
|
153
|
+
methodKind: "server_streaming";
|
|
154
|
+
input: typeof HealthCheckRequestSchema;
|
|
155
|
+
output: typeof HealthCheckResponseSchema;
|
|
156
|
+
};
|
|
157
|
+
}>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Vendored generated stubs for grpc.health.v1 -- do not edit by hand.
|
|
2
|
+
//
|
|
3
|
+
// Source: buf.build/grpc/grpc, grpc/health/v1/health.proto
|
|
4
|
+
// Plugin: buf.build/bufbuild/es:v2.12.0 (kept in step with the root
|
|
5
|
+
// buf.gen.yaml pin -- plugin and @bufbuild/protobuf runtime must
|
|
6
|
+
// agree on major).
|
|
7
|
+
//
|
|
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.
|
|
11
|
+
//
|
|
12
|
+
// cd node/sdk
|
|
13
|
+
// buf generate buf.build/grpc/grpc --path grpc/health/v1/health.proto \
|
|
14
|
+
// --template '{"version":"v2","plugins":[{"remote":"buf.build/bufbuild/es:v2.12.0","out":"gen-health","opt":["target=ts","import_extension=js"]}]}'
|
|
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
|
|
17
|
+
// rm -rf gen-health
|
|
18
|
+
//
|
|
19
|
+
// Vendored rather than depended on: the equivalent package
|
|
20
|
+
// `@buf/grpc_grpc.bufbuild_es` is published only on the Buf Schema Registry, and
|
|
21
|
+
// reaching it needs registry config that a published npm package cannot hand its
|
|
22
|
+
// consumers -- so depending on it 404'd every downstream install. Rationale in
|
|
23
|
+
// docs/HEALTH_SERVICE.md.
|
|
24
|
+
//
|
|
25
|
+
// Lives under src/service/ rather than src/common/gen/ so the transport keeps
|
|
26
|
+
// importing nothing from common/gen.
|
|
27
|
+
import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
|
|
28
|
+
/**
|
|
29
|
+
* Describes the file grpc/health/v1/health.proto.
|
|
30
|
+
*/
|
|
31
|
+
export const file_grpc_health_v1_health = /*@__PURE__*/ fileDesc("ChtncnBjL2hlYWx0aC92MS9oZWFsdGgucHJvdG8SDmdycGMuaGVhbHRoLnYxIiUKEkhlYWx0aENoZWNrUmVxdWVzdBIPCgdzZXJ2aWNlGAEgASgJIqkBChNIZWFsdGhDaGVja1Jlc3BvbnNlEkEKBnN0YXR1cxgBIAEoDjIxLmdycGMuaGVhbHRoLnYxLkhlYWx0aENoZWNrUmVzcG9uc2UuU2VydmluZ1N0YXR1cyJPCg1TZXJ2aW5nU3RhdHVzEgsKB1VOS05PV04QABILCgdTRVJWSU5HEAESDwoLTk9UX1NFUlZJTkcQAhITCg9TRVJWSUNFX1VOS05PV04QAyITChFIZWFsdGhMaXN0UmVxdWVzdCKuAQoSSGVhbHRoTGlzdFJlc3BvbnNlEkIKCHN0YXR1c2VzGAEgAygLMjAuZ3JwYy5oZWFsdGgudjEuSGVhbHRoTGlzdFJlc3BvbnNlLlN0YXR1c2VzRW50cnkaVAoNU3RhdHVzZXNFbnRyeRILCgNrZXkYASABKAkSMgoFdmFsdWUYAiABKAsyIy5ncnBjLmhlYWx0aC52MS5IZWFsdGhDaGVja1Jlc3BvbnNlOgI4ATL9AQoGSGVhbHRoElAKBUNoZWNrEiIuZ3JwYy5oZWFsdGgudjEuSGVhbHRoQ2hlY2tSZXF1ZXN0GiMuZ3JwYy5oZWFsdGgudjEuSGVhbHRoQ2hlY2tSZXNwb25zZRJNCgRMaXN0EiEuZ3JwYy5oZWFsdGgudjEuSGVhbHRoTGlzdFJlcXVlc3QaIi5ncnBjLmhlYWx0aC52MS5IZWFsdGhMaXN0UmVzcG9uc2USUgoFV2F0Y2gSIi5ncnBjLmhlYWx0aC52MS5IZWFsdGhDaGVja1JlcXVlc3QaIy5ncnBjLmhlYWx0aC52MS5IZWFsdGhDaGVja1Jlc3BvbnNlMAFCcAoRaW8uZ3JwYy5oZWFsdGgudjFCC0hlYWx0aFByb3RvUAFaLGdvb2dsZS5nb2xhbmcub3JnL2dycGMvaGVhbHRoL2dycGNfaGVhbHRoX3YxogIMR3JwY0hlYWx0aFYxqgIOR3JwYy5IZWFsdGguVjFiBnByb3RvMw");
|
|
32
|
+
/**
|
|
33
|
+
* Describes the message grpc.health.v1.HealthCheckRequest.
|
|
34
|
+
* Use `create(HealthCheckRequestSchema)` to create a new message.
|
|
35
|
+
*/
|
|
36
|
+
export const HealthCheckRequestSchema = /*@__PURE__*/ messageDesc(file_grpc_health_v1_health, 0);
|
|
37
|
+
/**
|
|
38
|
+
* Describes the message grpc.health.v1.HealthCheckResponse.
|
|
39
|
+
* Use `create(HealthCheckResponseSchema)` to create a new message.
|
|
40
|
+
*/
|
|
41
|
+
export const HealthCheckResponseSchema = /*@__PURE__*/ messageDesc(file_grpc_health_v1_health, 1);
|
|
42
|
+
/**
|
|
43
|
+
* @generated from enum grpc.health.v1.HealthCheckResponse.ServingStatus
|
|
44
|
+
*/
|
|
45
|
+
export var HealthCheckResponse_ServingStatus;
|
|
46
|
+
(function (HealthCheckResponse_ServingStatus) {
|
|
47
|
+
/**
|
|
48
|
+
* @generated from enum value: UNKNOWN = 0;
|
|
49
|
+
*/
|
|
50
|
+
HealthCheckResponse_ServingStatus[HealthCheckResponse_ServingStatus["UNKNOWN"] = 0] = "UNKNOWN";
|
|
51
|
+
/**
|
|
52
|
+
* @generated from enum value: SERVING = 1;
|
|
53
|
+
*/
|
|
54
|
+
HealthCheckResponse_ServingStatus[HealthCheckResponse_ServingStatus["SERVING"] = 1] = "SERVING";
|
|
55
|
+
/**
|
|
56
|
+
* @generated from enum value: NOT_SERVING = 2;
|
|
57
|
+
*/
|
|
58
|
+
HealthCheckResponse_ServingStatus[HealthCheckResponse_ServingStatus["NOT_SERVING"] = 2] = "NOT_SERVING";
|
|
59
|
+
/**
|
|
60
|
+
* Used only by the Watch method.
|
|
61
|
+
*
|
|
62
|
+
* @generated from enum value: SERVICE_UNKNOWN = 3;
|
|
63
|
+
*/
|
|
64
|
+
HealthCheckResponse_ServingStatus[HealthCheckResponse_ServingStatus["SERVICE_UNKNOWN"] = 3] = "SERVICE_UNKNOWN";
|
|
65
|
+
})(HealthCheckResponse_ServingStatus || (HealthCheckResponse_ServingStatus = {}));
|
|
66
|
+
/**
|
|
67
|
+
* Describes the enum grpc.health.v1.HealthCheckResponse.ServingStatus.
|
|
68
|
+
*/
|
|
69
|
+
export const HealthCheckResponse_ServingStatusSchema = /*@__PURE__*/ enumDesc(file_grpc_health_v1_health, 1, 0);
|
|
70
|
+
/**
|
|
71
|
+
* Describes the message grpc.health.v1.HealthListRequest.
|
|
72
|
+
* Use `create(HealthListRequestSchema)` to create a new message.
|
|
73
|
+
*/
|
|
74
|
+
export const HealthListRequestSchema = /*@__PURE__*/ messageDesc(file_grpc_health_v1_health, 2);
|
|
75
|
+
/**
|
|
76
|
+
* Describes the message grpc.health.v1.HealthListResponse.
|
|
77
|
+
* Use `create(HealthListResponseSchema)` to create a new message.
|
|
78
|
+
*/
|
|
79
|
+
export const HealthListResponseSchema = /*@__PURE__*/ messageDesc(file_grpc_health_v1_health, 3);
|
|
80
|
+
/**
|
|
81
|
+
* Health is gRPC's mechanism for checking whether a server is able to handle
|
|
82
|
+
* RPCs. Its semantics are documented in
|
|
83
|
+
* https://github.com/grpc/grpc/blob/master/doc/health-checking.md.
|
|
84
|
+
*
|
|
85
|
+
* @generated from service grpc.health.v1.Health
|
|
86
|
+
*/
|
|
87
|
+
export const Health = /*@__PURE__*/ serviceDesc(file_grpc_health_v1_health, 0);
|
|
@@ -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,8 +1,8 @@
|
|
|
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
|
-
import { Health } from "
|
|
5
|
+
import { Health } from "./health_pb.js";
|
|
6
6
|
import { createHealthServiceImpl } from "./health.js";
|
|
7
7
|
export const REQUEST_VALIDITY_MILLIS = 60000;
|
|
8
8
|
const createSignatureVerification = (networkPublicKey) => (next) => async (req) => {
|
|
@@ -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);
|