@ondewo/s2t-client-typescript 7.5.0 → 7.5.1

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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * gRPC-web endpoint helper: turns `host` / `port` / `useSecureChannel` into the `hostname` URL and the
3
+ * client options every generated `*Client` / `*PromiseClient` constructor takes.
4
+ *
5
+ * TLS in a browser belongs to the browser: the server certificate is verified against the browser's /
6
+ * operating system's trust store, and a client certificate (mutual TLS) can only come from the
7
+ * browser's own certificate store. So this helper accepts no CA, no client certificate and no private
8
+ * key -- a config carrying one (e.g. ported from the Python SDK's `grpc_cert` / `grpc_client_cert` /
9
+ * `grpc_client_key`) is refused instead of silently dropped, and a private key never has to be shipped to
10
+ * a browser. See the README section "TLS, mutual TLS and certificates".
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ /** Connection settings of one gRPC-web (Envoy) endpoint. */
15
+ export interface GrpcWebEndpointConfig {
16
+ /** Bare host name or IP address (IPv6 with or without brackets); no scheme, port or path. */
17
+ host: string;
18
+ /** TCP port, 1-65535. */
19
+ port: number | string;
20
+ /** `true` (the default) builds an `https://` URL; `false` builds a plaintext `http://` one and warns. */
21
+ useSecureChannel?: boolean;
22
+ /**
23
+ * Send credentials on cross-origin calls (gRPC-web's `withCredentials` client option, default
24
+ * `false`). Cookies, HTTP auth and the browser's TLS client certificate (mutual TLS) are only sent on
25
+ * a cross-origin request when this is `true`.
26
+ */
27
+ withCredentials?: boolean;
28
+ }
29
+ /** The client options this helper sets; pass them as the third argument of a generated client. */
30
+ export interface GrpcWebClientOptions {
31
+ /** gRPC-web's `withCredentials` option. */
32
+ withCredentials: boolean;
33
+ }
34
+ /** What a generated gRPC-web client constructor takes: `new XClient(hostname, null, options)`. */
35
+ export interface GrpcWebEndpoint {
36
+ /** `https://host:port` (or `http://` for an insecure channel); IPv6 hosts are bracketed. */
37
+ readonly hostname: string;
38
+ /** The gRPC-web client options. */
39
+ readonly options: GrpcWebClientOptions;
40
+ }
41
+ /** Where the insecure-channel warning goes (the global `console` by default). */
42
+ export interface GrpcWebEndpointLogger {
43
+ /**
44
+ * Write one warning.
45
+ *
46
+ * @param message - The warning text.
47
+ */
48
+ warn(message: string): void;
49
+ }
50
+ /**
51
+ * Config fields of the other ONDEWO SDKs (camelCase and Python's snake_case) that a browser cannot use,
52
+ * with the reason each is refused. A non-empty value for any of them raises; an empty one is ignored.
53
+ */
54
+ export declare const REFUSED_TLS_FIELDS: Readonly<Record<string, string>>;
55
+ /**
56
+ * Render `host:port` for a URL, bracketing a bare IPv6 literal (`::1` -> `[::1]:50051`).
57
+ *
58
+ * @param host - A bare host name, IPv4 address, or IPv6 address with or without brackets.
59
+ * @param port - The port.
60
+ * @returns `host:port`.
61
+ * @throws {Error} When the host contains a colon but is neither a bracketed nor a bare IPv6 literal.
62
+ */
63
+ export declare function hostAndPort(host: string, port: number | string): string;
64
+ /**
65
+ * Validate a gRPC-web endpoint config and build the `hostname` URL and client options for a generated client.
66
+ *
67
+ * ```ts
68
+ * const endpoint: GrpcWebEndpoint = createGrpcWebEndpoint({ host: 'nlu.example.com', port: 443 });
69
+ * const agents: AgentsClient = new AgentsClient(endpoint.hostname, null, endpoint.options);
70
+ * ```
71
+ *
72
+ * Error messages name the offending field only, never its value.
73
+ *
74
+ * @param config - Host, port and channel settings.
75
+ * @param logger - Receives the warning for an insecure channel (default: the global `console`).
76
+ * @returns The endpoint URL and the gRPC-web client options.
77
+ * @throws {Error} When `host` / `port` / `useSecureChannel` / `withCredentials` is invalid, or the config
78
+ * carries a CA, client certificate or private key (see {@link REFUSED_TLS_FIELDS}).
79
+ */
80
+ export declare function createGrpcWebEndpoint(config: GrpcWebEndpointConfig, logger?: GrpcWebEndpointLogger): GrpcWebEndpoint;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ // Copyright 2021-2026 ONDEWO GmbH
3
+ //
4
+ // Licensed under the Apache License, Version 2.0 (the "License");
5
+ // you may not use this file except in compliance with the License.
6
+ // You may obtain a copy of the License at
7
+ //
8
+ // http://www.apache.org/licenses/LICENSE-2.0
9
+ //
10
+ // Unless required by applicable law or agreed to in writing, software
11
+ // distributed under the License is distributed on an "AS IS" BASIS,
12
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ // See the License for the specific language governing permissions and
14
+ // limitations under the License.
15
+ //
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.REFUSED_TLS_FIELDS = void 0;
18
+ exports.hostAndPort = hostAndPort;
19
+ exports.createGrpcWebEndpoint = createGrpcWebEndpoint;
20
+ /** Why a browser cannot use a CA certificate given in code. */
21
+ const CA_REASON = 'a browser verifies the server certificate against its own trust store; install the CA there or use a publicly trusted certificate';
22
+ /** Why a browser cannot use a client certificate given in code. */
23
+ const CLIENT_CERT_REASON = 'a browser presents a TLS client certificate only from its own certificate store; install the client certificate there';
24
+ /** Why a private key is refused. */
25
+ const CLIENT_KEY_REASON = 'a private key must never be shipped to a browser; install the client certificate in its certificate store';
26
+ /**
27
+ * Config fields of the other ONDEWO SDKs (camelCase and Python's snake_case) that a browser cannot use,
28
+ * with the reason each is refused. A non-empty value for any of them raises; an empty one is ignored.
29
+ */
30
+ exports.REFUSED_TLS_FIELDS = {
31
+ grpcCert: CA_REASON,
32
+ grpc_cert: CA_REASON,
33
+ grpcClientCert: CLIENT_CERT_REASON,
34
+ grpc_client_cert: CLIENT_CERT_REASON,
35
+ grpcClientKey: CLIENT_KEY_REASON,
36
+ grpc_client_key: CLIENT_KEY_REASON
37
+ };
38
+ /** A bare IPv6 literal: hex groups, colons and an optional embedded IPv4 tail, at least two colons. */
39
+ const BARE_IPV6_PATTERN = /^(?=(?:[^:]*:){2})[0-9A-Fa-f:.]+$/;
40
+ /**
41
+ * Render `host:port` for a URL, bracketing a bare IPv6 literal (`::1` -> `[::1]:50051`).
42
+ *
43
+ * @param host - A bare host name, IPv4 address, or IPv6 address with or without brackets.
44
+ * @param port - The port.
45
+ * @returns `host:port`.
46
+ * @throws {Error} When the host contains a colon but is neither a bracketed nor a bare IPv6 literal.
47
+ */
48
+ function hostAndPort(host, port) {
49
+ if (host.startsWith('[') && host.endsWith(']')) {
50
+ return `${host}:${port}`;
51
+ }
52
+ if (BARE_IPV6_PATTERN.test(host)) {
53
+ return `[${host}]:${port}`;
54
+ }
55
+ if (host.includes(':')) {
56
+ throw new Error('GrpcWebEndpointConfig.host must not contain a port; pass the port in GrpcWebEndpointConfig.port');
57
+ }
58
+ return `${host}:${port}`;
59
+ }
60
+ /**
61
+ * Validate a gRPC-web endpoint config and build the `hostname` URL and client options for a generated client.
62
+ *
63
+ * ```ts
64
+ * const endpoint: GrpcWebEndpoint = createGrpcWebEndpoint({ host: 'nlu.example.com', port: 443 });
65
+ * const agents: AgentsClient = new AgentsClient(endpoint.hostname, null, endpoint.options);
66
+ * ```
67
+ *
68
+ * Error messages name the offending field only, never its value.
69
+ *
70
+ * @param config - Host, port and channel settings.
71
+ * @param logger - Receives the warning for an insecure channel (default: the global `console`).
72
+ * @returns The endpoint URL and the gRPC-web client options.
73
+ * @throws {Error} When `host` / `port` / `useSecureChannel` / `withCredentials` is invalid, or the config
74
+ * carries a CA, client certificate or private key (see {@link REFUSED_TLS_FIELDS}).
75
+ */
76
+ function createGrpcWebEndpoint(config, logger = console) {
77
+ const fields = config;
78
+ for (const [name, reason] of Object.entries(exports.REFUSED_TLS_FIELDS)) {
79
+ const value = fields[name];
80
+ if (value !== undefined && value !== null && value !== '') {
81
+ throw new Error(`GrpcWebEndpointConfig.${name} is not supported by a gRPC-web client: ${reason}`);
82
+ }
83
+ }
84
+ const host = config.host;
85
+ if (typeof host !== 'string' || host.trim() === '' || host.trim() !== host) {
86
+ throw new Error('GrpcWebEndpointConfig.host must be a non-empty host name or IP address without whitespace');
87
+ }
88
+ if (host.includes('/') || host.includes('@')) {
89
+ throw new Error('GrpcWebEndpointConfig.host must be a bare host name or IP address without scheme, credentials or path; ' +
90
+ 'the scheme comes from GrpcWebEndpointConfig.useSecureChannel');
91
+ }
92
+ const port = config.port;
93
+ let portNumber = Number.NaN;
94
+ if (typeof port === 'number') {
95
+ portNumber = port;
96
+ }
97
+ else if (typeof port === 'string' && /^[0-9]{1,5}$/.test(port)) {
98
+ portNumber = Number(port);
99
+ }
100
+ if (!Number.isInteger(portNumber) || portNumber < 1 || portNumber > 65535) {
101
+ throw new Error('GrpcWebEndpointConfig.port must be an integer between 1 and 65535');
102
+ }
103
+ const useSecureChannel = config.useSecureChannel ?? true;
104
+ if (typeof useSecureChannel !== 'boolean') {
105
+ throw new Error('GrpcWebEndpointConfig.useSecureChannel must be a boolean');
106
+ }
107
+ const withCredentials = config.withCredentials ?? false;
108
+ if (typeof withCredentials !== 'boolean') {
109
+ throw new Error('GrpcWebEndpointConfig.withCredentials must be a boolean');
110
+ }
111
+ const target = hostAndPort(host, portNumber);
112
+ let scheme = 'https';
113
+ if (!useSecureChannel) {
114
+ scheme = 'http';
115
+ logger.warn(`ONDEWO gRPC-web: the channel to ${target} is NOT encrypted (useSecureChannel=false); use it only for local development`);
116
+ }
117
+ return {
118
+ hostname: `${scheme}://${target}`,
119
+ options: { withCredentials }
120
+ };
121
+ }
@@ -92,6 +92,21 @@ export declare const INSECURE_AGENT_OPTIONS: {
92
92
  * @returns A {@link TokenFetch} that resolves the global fetch at call time (honoring test overrides).
93
93
  */
94
94
  export declare function createDefaultTokenFetch(verifySsl: boolean): TokenFetch;
95
+ /** What {@link OfflineTokenProvider.toJSON} renders in place of a non-empty token. */
96
+ export declare const REDACTED: string;
97
+ /** The logging-safe view of an {@link OfflineTokenProvider}: both tokens redacted. */
98
+ export interface OfflineTokenProviderLogView {
99
+ /** The OIDC token endpoint. */
100
+ tokenEndpoint: string;
101
+ /** The Keycloak client id. */
102
+ clientId: string;
103
+ /** `***REDACTED***`, or the token as is when it is `null` (before login) or empty. */
104
+ accessToken: string | null;
105
+ /** `***REDACTED***`, or the token as is when it is `null` (before login) or empty. */
106
+ refreshToken: string | null;
107
+ /** Whether {@link OfflineTokenProvider.stop} was called. */
108
+ stopped: boolean;
109
+ }
95
110
  /**
96
111
  * A live access-token holder backed by a bounded auto-refresh loop. Obtain one from {@link login};
97
112
  * read {@link getAuthorizationHeader} for the gRPC `Authorization` metadata and call {@link stop} when done.
@@ -174,6 +189,15 @@ export declare class OfflineTokenProvider {
174
189
  * @throws {@link TokenError} If no access token is available (login not completed or already lapsed).
175
190
  */
176
191
  getAuthorizationHeader(): string;
192
+ /**
193
+ * A logging-safe view of this provider: the access and refresh tokens render as `***REDACTED***`
194
+ * (`null` before login and an empty token stay as they are). `JSON.stringify(provider)` uses it, and so
195
+ * do Node's `console.log(provider)` / `util.inspect(provider)` through the hook below, so none of them
196
+ * prints a token. {@link OfflineTokenProvider.getAuthorizationHeader} still returns the real one.
197
+ *
198
+ * @returns The endpoint, client id and stop flag, with both tokens redacted.
199
+ */
200
+ toJSON(): OfflineTokenProviderLogView;
177
201
  /**
178
202
  * Stop the auto-refresh loop and clear any pending timer. Idempotent; safe to call from any state.
179
203
  * After this the access token is no longer renewed and will eventually lapse.
@@ -191,3 +215,4 @@ export declare class OfflineTokenProvider {
191
215
  * bootstrap login fails (see {@link OfflineTokenProvider.bootstrap}).
192
216
  */
193
217
  export declare function login(options: OfflineTokenLoginOptions): Promise<OfflineTokenProvider>;
218
+ export * from './grpcWebEndpoint';
@@ -46,8 +46,11 @@ var __importStar = (this && this.__importStar) || (function () {
46
46
  return result;
47
47
  };
48
48
  })();
49
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
50
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
51
+ };
49
52
  Object.defineProperty(exports, "__esModule", { value: true });
50
- exports.OfflineTokenProvider = exports.INSECURE_AGENT_OPTIONS = exports.TokenError = void 0;
53
+ exports.OfflineTokenProvider = exports.REDACTED = exports.INSECURE_AGENT_OPTIONS = exports.TokenError = void 0;
51
54
  exports.createDefaultTokenFetch = createDefaultTokenFetch;
52
55
  exports.login = login;
53
56
  /**
@@ -175,6 +178,20 @@ function createDefaultTokenFetch(verifySsl) {
175
178
  return globalFetch(url, effectiveInit);
176
179
  };
177
180
  }
181
+ /** What {@link OfflineTokenProvider.toJSON} renders in place of a non-empty token. */
182
+ exports.REDACTED = '***REDACTED***';
183
+ /**
184
+ * Redact a token for logging: `null` and `''` render as is, anything else as {@link REDACTED}.
185
+ *
186
+ * @param token - The token to render.
187
+ * @returns The logging-safe rendering.
188
+ */
189
+ function redactToken(token) {
190
+ if (token === null || token === '') {
191
+ return token;
192
+ }
193
+ return exports.REDACTED;
194
+ }
178
195
  /**
179
196
  * A live access-token holder backed by a bounded auto-refresh loop. Obtain one from {@link login};
180
197
  * read {@link getAuthorizationHeader} for the gRPC `Authorization` metadata and call {@link stop} when done.
@@ -334,6 +351,33 @@ class OfflineTokenProvider {
334
351
  }
335
352
  return `Bearer ${this.accessToken}`;
336
353
  }
354
+ /**
355
+ * A logging-safe view of this provider: the access and refresh tokens render as `***REDACTED***`
356
+ * (`null` before login and an empty token stay as they are). `JSON.stringify(provider)` uses it, and so
357
+ * do Node's `console.log(provider)` / `util.inspect(provider)` through the hook below, so none of them
358
+ * prints a token. {@link OfflineTokenProvider.getAuthorizationHeader} still returns the real one.
359
+ *
360
+ * @returns The endpoint, client id and stop flag, with both tokens redacted.
361
+ */
362
+ toJSON() {
363
+ return {
364
+ tokenEndpoint: this.tokenEndpoint,
365
+ clientId: this.clientId,
366
+ accessToken: redactToken(this.accessToken),
367
+ refreshToken: redactToken(this.refreshToken),
368
+ stopped: this.stopped
369
+ };
370
+ }
371
+ /**
372
+ * Node's `util.inspect` hook (used by `console.log`): renders {@link OfflineTokenProvider.toJSON}, so
373
+ * logging the provider never prints a token. Looked up via `Symbol.for`, so no `util` import is needed
374
+ * and the module stays usable in a browser bundle.
375
+ *
376
+ * @returns The redacted view.
377
+ */
378
+ [Symbol.for('nodejs.util.inspect.custom')]() {
379
+ return this.toJSON();
380
+ }
337
381
  /**
338
382
  * Stop the auto-refresh loop and clear any pending timer. Idempotent; safe to call from any state.
339
383
  * After this the access token is no longer renewed and will eventually lapse.
@@ -372,3 +416,7 @@ async function login(options) {
372
416
  await provider.bootstrap(options.username, options.password);
373
417
  return provider;
374
418
  }
419
+ // The gRPC-web endpoint / TLS helper ships through this module: `make create_npm_package` compiles this
420
+ // file (tsc follows the import), so `@ondewo/s2t-client-typescript/auth/offlineTokenProvider` exports both
421
+ // hand-written helpers and a proto-compiler regeneration cannot drop it.
422
+ __exportStar(require("./grpcWebEndpoint"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ondewo/s2t-client-typescript",
3
- "version": "7.5.0",
3
+ "version": "7.5.1",
4
4
  "description": "ONDEWO Speech to Text (S2T) Client library for Typescript",
5
5
  "author": "ONDEWO GmbH <office@ondewo.com>",
6
6
  "homepage": "https://ondewo.com",
@@ -20,7 +20,7 @@
20
20
  "directory": "https://github.com/ondewo/ondewo-s2t-client-typescript"
21
21
  },
22
22
  "dependencies": {
23
- "google-protobuf": "3.21.4",
23
+ "google-protobuf": "4.0.2",
24
24
  "grpc-web": "^1.5.0",
25
25
  "tslib": "^2.8.1",
26
26
  "undici": "^6.27.0"
package/public-api.d.ts CHANGED
@@ -2,4 +2,5 @@ export * from './api/google/protobuf/struct_pb.d';
2
2
  export * from './api/google/protobuf/empty_pb.d';
3
3
  export * from './api/ondewo/s2t/speech-to-text_pb.d';
4
4
  export * from './api/ondewo/s2t/speech-to-text_grpc_web_pb.d';
5
+ export * from './auth/grpcWebEndpoint';
5
6
  export * from './auth/offlineTokenProvider';
package/public-api.js CHANGED
@@ -2,4 +2,5 @@ export * from './api/google/protobuf/struct_pb';
2
2
  export * from './api/google/protobuf/empty_pb';
3
3
  export * from './api/ondewo/s2t/speech-to-text_grpc_web_pb';
4
4
  export * from './api/ondewo/s2t/speech-to-text_pb';
5
+ export * from './auth/grpcWebEndpoint';
5
6
  export * from './auth/offlineTokenProvider';