mtmsdk 0.0.50 → 0.0.52
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/dist/gomtmapi/@tanstack/react-query.gen.d.ts +1 -13
- package/dist/gomtmapi/@tanstack/react-query.gen.d.ts.map +1 -1
- package/dist/gomtmapi/@tanstack/react-query.gen.js +1 -37
- package/dist/gomtmapi/@tanstack/react-query.gen.js.map +1 -1
- package/dist/gomtmapi/index.d.ts +2 -2
- package/dist/gomtmapi/index.d.ts.map +1 -1
- package/dist/gomtmapi/index.js +1 -1
- package/dist/gomtmapi/index.js.map +1 -1
- package/dist/gomtmapi/schemas.gen.d.ts +0 -57
- package/dist/gomtmapi/schemas.gen.d.ts.map +1 -1
- package/dist/gomtmapi/schemas.gen.js +0 -60
- package/dist/gomtmapi/schemas.gen.js.map +1 -1
- package/dist/gomtmapi/sdk.gen.d.ts +1 -13
- package/dist/gomtmapi/sdk.gen.d.ts.map +1 -1
- package/dist/gomtmapi/sdk.gen.js +0 -26
- package/dist/gomtmapi/sdk.gen.js.map +1 -1
- package/dist/gomtmapi/types.gen.d.ts +0 -94
- package/dist/gomtmapi/types.gen.d.ts.map +1 -1
- package/dist/gomtmapi/zod.gen.d.ts +0 -62
- package/dist/gomtmapi/zod.gen.d.ts.map +1 -1
- package/dist/gomtmapi/zod.gen.js +0 -51
- package/dist/gomtmapi/zod.gen.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/schema.d.ts +1 -11
- package/dist/lib/schema.d.ts.map +1 -1
- package/dist/lib/schema.js +0 -9
- package/dist/lib/schema.js.map +1 -1
- package/dist/mtgate_api/client.gen.d.ts.map +1 -1
- package/dist/mtgate_api/client.gen.js +1 -1
- package/dist/mtgate_api/client.gen.js.map +1 -1
- package/dist/mtgate_api/index.d.ts +2 -2
- package/dist/mtgate_api/index.d.ts.map +1 -1
- package/dist/mtgate_api/index.js +1 -1
- package/dist/mtgate_api/index.js.map +1 -1
- package/dist/mtgate_api/sdk.gen.d.ts +0 -8
- package/dist/mtgate_api/sdk.gen.d.ts.map +1 -1
- package/dist/mtgate_api/sdk.gen.js +1 -29
- package/dist/mtgate_api/sdk.gen.js.map +1 -1
- package/dist/mtgate_api/types.gen.d.ts +1 -200
- package/dist/mtgate_api/types.gen.d.ts.map +1 -1
- package/dist/openclaw/gateway.d.ts.map +1 -1
- package/dist/openclaw/gateway.js +1 -1
- package/dist/openclaw/gateway.js.map +1 -1
- package/dist/sbmng/@tanstack/react-query.gen.d.ts +39 -65
- package/dist/sbmng/@tanstack/react-query.gen.d.ts.map +1 -1
- package/dist/sbmng/@tanstack/react-query.gen.js +19 -65
- package/dist/sbmng/@tanstack/react-query.gen.js.map +1 -1
- package/dist/sbmng/index.d.ts +2 -2
- package/dist/sbmng/index.d.ts.map +1 -1
- package/dist/sbmng/index.js +1 -1
- package/dist/sbmng/index.js.map +1 -1
- package/dist/sbmng/schemas.gen.d.ts +6 -95
- package/dist/sbmng/schemas.gen.d.ts.map +1 -1
- package/dist/sbmng/schemas.gen.js +7 -139
- package/dist/sbmng/schemas.gen.js.map +1 -1
- package/dist/sbmng/sdk.gen.d.ts +7 -17
- package/dist/sbmng/sdk.gen.d.ts.map +1 -1
- package/dist/sbmng/sdk.gen.js +10 -32
- package/dist/sbmng/sdk.gen.js.map +1 -1
- package/dist/sbmng/types.gen.d.ts +41 -157
- package/dist/sbmng/types.gen.d.ts.map +1 -1
- package/dist/supabase/supabase-client.d.ts +85 -181
- package/dist/supabase/supabase-client.d.ts.map +1 -1
- package/dist/supabase/supabase.d.ts +170 -362
- package/dist/supabase/supabase.d.ts.map +1 -1
- package/dist/supabase/use-sb-query/build-query-opts.d.ts +6 -2
- package/dist/supabase/use-sb-query/build-query-opts.d.ts.map +1 -1
- package/dist/supabase/use-sb-query/build-query-opts.js +8 -4
- package/dist/supabase/use-sb-query/build-query-opts.js.map +1 -1
- package/dist/supabase/use-sb-query/use-query.d.ts +9 -6
- package/dist/supabase/use-sb-query/use-query.d.ts.map +1 -1
- package/dist/supabase/use-sb-query/use-query.js.map +1 -1
- package/dist/supabase/use-sb-query/use-rpc-query.d.ts +25 -17
- package/dist/supabase/use-sb-query/use-rpc-query.d.ts.map +1 -1
- package/dist/supabase/use-sb-query/use-rpc-query.js +48 -26
- package/dist/supabase/use-sb-query/use-rpc-query.js.map +1 -1
- package/dist/types/database.schemas.d.ts +129 -222
- package/dist/types/database.schemas.d.ts.map +1 -1
- package/dist/types/database.schemas.js +112 -124
- package/dist/types/database.schemas.js.map +1 -1
- package/dist/types/database.types.d.ts +85 -181
- package/dist/types/database.types.d.ts.map +1 -1
- package/dist/types/database.types.js.map +1 -1
- package/dist/types/index.d.ts +30 -59
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +16 -3
- package/dist/types/index.js.map +1 -1
- package/package.json +105 -110
- package/src/cloud-account/platform-configs.ts +73 -73
- package/src/gomtmapi/@tanstack/react-query.gen.ts +537 -575
- package/src/gomtmapi/client/client.gen.ts +311 -311
- package/src/gomtmapi/client/index.ts +25 -25
- package/src/gomtmapi/client/types.gen.ts +241 -241
- package/src/gomtmapi/client/utils.gen.ts +332 -332
- package/src/gomtmapi/client.gen.ts +16 -16
- package/src/gomtmapi/core/auth.gen.ts +42 -42
- package/src/gomtmapi/core/bodySerializer.gen.ts +100 -100
- package/src/gomtmapi/core/params.gen.ts +176 -176
- package/src/gomtmapi/core/pathSerializer.gen.ts +181 -181
- package/src/gomtmapi/core/queryKeySerializer.gen.ts +136 -136
- package/src/gomtmapi/core/serverSentEvents.gen.ts +266 -266
- package/src/gomtmapi/core/types.gen.ts +118 -118
- package/src/gomtmapi/core/utils.gen.ts +143 -143
- package/src/gomtmapi/index.ts +4 -4
- package/src/gomtmapi/schemas.gen.ts +532 -596
- package/src/gomtmapi/sdk.gen.ts +371 -399
- package/src/gomtmapi/types.gen.ts +854 -962
- package/src/gomtmapi/zod.gen.ts +448 -507
- package/src/index.ts +4 -5
- package/src/lib/aisdk/types.ts +7 -7
- package/src/lib/logger/index.ts +8 -8
- package/src/lib/schema.ts +37 -50
- package/src/mtgate_api/client/client.gen.ts +311 -311
- package/src/mtgate_api/client/index.ts +25 -25
- package/src/mtgate_api/client/types.gen.ts +241 -241
- package/src/mtgate_api/client/utils.gen.ts +332 -332
- package/src/mtgate_api/client.gen.ts +16 -16
- package/src/mtgate_api/core/auth.gen.ts +42 -42
- package/src/mtgate_api/core/bodySerializer.gen.ts +100 -100
- package/src/mtgate_api/core/params.gen.ts +176 -176
- package/src/mtgate_api/core/pathSerializer.gen.ts +181 -181
- package/src/mtgate_api/core/queryKeySerializer.gen.ts +136 -136
- package/src/mtgate_api/core/serverSentEvents.gen.ts +266 -266
- package/src/mtgate_api/core/types.gen.ts +118 -118
- package/src/mtgate_api/core/utils.gen.ts +143 -143
- package/src/mtgate_api/index.ts +4 -4
- package/src/mtgate_api/sdk.gen.ts +17 -54
- package/src/mtgate_api/types.gen.ts +5 -239
- package/src/openclaw/client-info.ts +54 -54
- package/src/openclaw/device-auth-utils.ts +26 -26
- package/src/openclaw/device-auth.ts +99 -99
- package/src/openclaw/device-identity.ts +108 -108
- package/src/openclaw/gateway.ts +392 -392
- package/src/openclaw/types.ts +518 -518
- package/src/sbmng/@tanstack/react-query.gen.ts +2967 -3017
- package/src/sbmng/client/client.gen.ts +311 -311
- package/src/sbmng/client/index.ts +25 -25
- package/src/sbmng/client/types.gen.ts +241 -241
- package/src/sbmng/client/utils.gen.ts +332 -332
- package/src/sbmng/client.gen.ts +16 -16
- package/src/sbmng/core/auth.gen.ts +42 -42
- package/src/sbmng/core/bodySerializer.gen.ts +100 -100
- package/src/sbmng/core/params.gen.ts +176 -176
- package/src/sbmng/core/pathSerializer.gen.ts +181 -181
- package/src/sbmng/core/queryKeySerializer.gen.ts +136 -136
- package/src/sbmng/core/serverSentEvents.gen.ts +266 -266
- package/src/sbmng/core/types.gen.ts +118 -118
- package/src/sbmng/core/utils.gen.ts +143 -143
- package/src/sbmng/index.ts +4 -4
- package/src/sbmng/schemas.gen.ts +9671 -9805
- package/src/sbmng/sdk.gen.ts +1755 -1780
- package/src/sbmng/types.gen.ts +7886 -8016
- package/src/supabase/auth-provider.tsx +67 -67
- package/src/supabase/context.tsx +51 -51
- package/src/supabase/cursor-pagination.ts +155 -155
- package/src/supabase/schema/browser.ts +44 -44
- package/src/supabase/schema/index.ts +25 -25
- package/src/supabase/supabase-client.ts +30 -30
- package/src/supabase/supabase.ts +67 -67
- package/src/supabase/use-sb-query/build-query-opts.ts +68 -56
- package/src/supabase/use-sb-query/key.ts +57 -57
- package/src/supabase/use-sb-query/use-query.ts +352 -340
- package/src/supabase/use-sb-query/use-rpc-mutation.ts +83 -83
- package/src/supabase/use-sb-query/use-rpc-query.ts +267 -231
- package/src/supabase/utils.ts +75 -75
- package/src/types/database.schemas.ts +4359 -4378
- package/src/types/database.types.ts +4182 -4288
- package/src/types/index.ts +85 -102
- package/dist/adk/api/client.d.ts +0 -9
- package/dist/adk/api/client.d.ts.map +0 -1
- package/dist/adk/api/client.js +0 -39
- package/dist/adk/api/client.js.map +0 -1
- package/dist/adk/core/constants/tool-icons.d.ts +0 -21
- package/dist/adk/core/constants/tool-icons.d.ts.map +0 -1
- package/dist/adk/core/constants/tool-icons.js +0 -47
- package/dist/adk/core/constants/tool-icons.js.map +0 -1
- package/dist/adk/core/models/AgentBuilder.d.ts +0 -77
- package/dist/adk/core/models/AgentBuilder.d.ts.map +0 -1
- package/dist/adk/core/models/AgentBuilder.js +0 -18
- package/dist/adk/core/models/AgentBuilder.js.map +0 -1
- package/dist/adk/core/models/AgentRunRequest.d.ts +0 -37
- package/dist/adk/core/models/AgentRunRequest.d.ts.map +0 -1
- package/dist/adk/core/models/AgentRunRequest.js +0 -18
- package/dist/adk/core/models/AgentRunRequest.js.map +0 -1
- package/dist/adk/core/models/Eval.d.ts +0 -55
- package/dist/adk/core/models/Eval.d.ts.map +0 -1
- package/dist/adk/core/models/Eval.js +0 -27
- package/dist/adk/core/models/Eval.js.map +0 -1
- package/dist/adk/core/models/LiveRequest.d.ts +0 -23
- package/dist/adk/core/models/LiveRequest.d.ts.map +0 -1
- package/dist/adk/core/models/LiveRequest.js +0 -18
- package/dist/adk/core/models/LiveRequest.js.map +0 -1
- package/dist/adk/core/models/RuntimeConfig.d.ts +0 -32
- package/dist/adk/core/models/RuntimeConfig.d.ts.map +0 -1
- package/dist/adk/core/models/RuntimeConfig.js +0 -18
- package/dist/adk/core/models/RuntimeConfig.js.map +0 -1
- package/dist/adk/core/models/Session.d.ts +0 -117
- package/dist/adk/core/models/Session.d.ts.map +0 -1
- package/dist/adk/core/models/Session.js +0 -31
- package/dist/adk/core/models/Session.js.map +0 -1
- package/dist/adk/core/models/Trace.d.ts +0 -40
- package/dist/adk/core/models/Trace.d.ts.map +0 -1
- package/dist/adk/core/models/Trace.js +0 -2
- package/dist/adk/core/models/Trace.js.map +0 -1
- package/dist/adk/core/models/types.d.ts +0 -338
- package/dist/adk/core/models/types.d.ts.map +0 -1
- package/dist/adk/core/models/types.js +0 -189
- package/dist/adk/core/models/types.js.map +0 -1
- package/dist/lib/utils.d.ts +0 -14
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/utils.js +0 -45
- package/dist/lib/utils.js.map +0 -1
- package/dist/mtgate_api/@tanstack/react-query.gen.d.ts +0 -166
- package/dist/mtgate_api/@tanstack/react-query.gen.d.ts.map +0 -1
- package/dist/mtgate_api/@tanstack/react-query.gen.js +0 -118
- package/dist/mtgate_api/@tanstack/react-query.gen.js.map +0 -1
- package/dist/mtgate_api/schemas.gen.d.ts +0 -233
- package/dist/mtgate_api/schemas.gen.d.ts.map +0 -1
- package/dist/mtgate_api/schemas.gen.js +0 -277
- package/dist/mtgate_api/schemas.gen.js.map +0 -1
- package/dist/mtgate_api/zod.gen.d.ts +0 -321
- package/dist/mtgate_api/zod.gen.d.ts.map +0 -1
- package/dist/mtgate_api/zod.gen.js +0 -148
- package/dist/mtgate_api/zod.gen.js.map +0 -1
- package/dist/mtmai_api/@tanstack/react-query.gen.d.ts +0 -949
- package/dist/mtmai_api/@tanstack/react-query.gen.d.ts.map +0 -1
- package/dist/mtmai_api/@tanstack/react-query.gen.js +0 -917
- package/dist/mtmai_api/@tanstack/react-query.gen.js.map +0 -1
- package/dist/mtmai_api/client/client.gen.d.ts +0 -3
- package/dist/mtmai_api/client/client.gen.d.ts.map +0 -1
- package/dist/mtmai_api/client/client.gen.js +0 -236
- package/dist/mtmai_api/client/client.gen.js.map +0 -1
- package/dist/mtmai_api/client/index.d.ts +0 -9
- package/dist/mtmai_api/client/index.d.ts.map +0 -1
- package/dist/mtmai_api/client/index.js +0 -7
- package/dist/mtmai_api/client/index.js.map +0 -1
- package/dist/mtmai_api/client/types.gen.d.ts +0 -118
- package/dist/mtmai_api/client/types.gen.d.ts.map +0 -1
- package/dist/mtmai_api/client/types.gen.js +0 -3
- package/dist/mtmai_api/client/types.gen.js.map +0 -1
- package/dist/mtmai_api/client/utils.gen.d.ts +0 -34
- package/dist/mtmai_api/client/utils.gen.d.ts.map +0 -1
- package/dist/mtmai_api/client/utils.gen.js +0 -232
- package/dist/mtmai_api/client/utils.gen.js.map +0 -1
- package/dist/mtmai_api/client.gen.d.ts +0 -13
- package/dist/mtmai_api/client.gen.d.ts.map +0 -1
- package/dist/mtmai_api/client.gen.js +0 -4
- package/dist/mtmai_api/client.gen.js.map +0 -1
- package/dist/mtmai_api/core/auth.gen.d.ts +0 -19
- package/dist/mtmai_api/core/auth.gen.d.ts.map +0 -1
- package/dist/mtmai_api/core/auth.gen.js +0 -15
- package/dist/mtmai_api/core/auth.gen.js.map +0 -1
- package/dist/mtmai_api/core/bodySerializer.gen.d.ts +0 -26
- package/dist/mtmai_api/core/bodySerializer.gen.d.ts.map +0 -1
- package/dist/mtmai_api/core/bodySerializer.gen.js +0 -58
- package/dist/mtmai_api/core/bodySerializer.gen.js.map +0 -1
- package/dist/mtmai_api/core/params.gen.d.ts +0 -44
- package/dist/mtmai_api/core/params.gen.d.ts.map +0 -1
- package/dist/mtmai_api/core/params.gen.js +0 -101
- package/dist/mtmai_api/core/params.gen.js.map +0 -1
- package/dist/mtmai_api/core/pathSerializer.gen.d.ts +0 -34
- package/dist/mtmai_api/core/pathSerializer.gen.d.ts.map +0 -1
- package/dist/mtmai_api/core/pathSerializer.gen.js +0 -115
- package/dist/mtmai_api/core/pathSerializer.gen.js.map +0 -1
- package/dist/mtmai_api/core/queryKeySerializer.gen.d.ts +0 -19
- package/dist/mtmai_api/core/queryKeySerializer.gen.d.ts.map +0 -1
- package/dist/mtmai_api/core/queryKeySerializer.gen.js +0 -100
- package/dist/mtmai_api/core/queryKeySerializer.gen.js.map +0 -1
- package/dist/mtmai_api/core/serverSentEvents.gen.d.ts +0 -72
- package/dist/mtmai_api/core/serverSentEvents.gen.d.ts.map +0 -1
- package/dist/mtmai_api/core/serverSentEvents.gen.js +0 -138
- package/dist/mtmai_api/core/serverSentEvents.gen.js.map +0 -1
- package/dist/mtmai_api/core/types.gen.d.ts +0 -79
- package/dist/mtmai_api/core/types.gen.d.ts.map +0 -1
- package/dist/mtmai_api/core/types.gen.js +0 -3
- package/dist/mtmai_api/core/types.gen.js.map +0 -1
- package/dist/mtmai_api/core/utils.gen.d.ts +0 -20
- package/dist/mtmai_api/core/utils.gen.d.ts.map +0 -1
- package/dist/mtmai_api/core/utils.gen.js +0 -88
- package/dist/mtmai_api/core/utils.gen.js.map +0 -1
- package/dist/mtmai_api/index.d.ts +0 -3
- package/dist/mtmai_api/index.d.ts.map +0 -1
- package/dist/mtmai_api/index.js +0 -4
- package/dist/mtmai_api/index.js.map +0 -1
- package/dist/mtmai_api/schemas.gen.d.ts +0 -7721
- package/dist/mtmai_api/schemas.gen.d.ts.map +0 -1
- package/dist/mtmai_api/schemas.gen.js +0 -9774
- package/dist/mtmai_api/schemas.gen.js.map +0 -1
- package/dist/mtmai_api/sdk.gen.d.ts +0 -282
- package/dist/mtmai_api/sdk.gen.d.ts.map +0 -1
- package/dist/mtmai_api/sdk.gen.js +0 -404
- package/dist/mtmai_api/sdk.gen.js.map +0 -1
- package/dist/mtmai_api/types.gen.d.ts +0 -7152
- package/dist/mtmai_api/types.gen.d.ts.map +0 -1
- package/dist/mtmai_api/types.gen.js +0 -253
- package/dist/mtmai_api/types.gen.js.map +0 -1
- package/dist/mtmai_api/zod.gen.d.ts +0 -55827
- package/dist/mtmai_api/zod.gen.d.ts.map +0 -1
- package/dist/mtmai_api/zod.gen.js +0 -4805
- package/dist/mtmai_api/zod.gen.js.map +0 -1
- package/dist/supabase/use-sb-query/prefetch.d.ts +0 -10
- package/dist/supabase/use-sb-query/prefetch.d.ts.map +0 -1
- package/dist/supabase/use-sb-query/prefetch.js +0 -14
- package/dist/supabase/use-sb-query/prefetch.js.map +0 -1
- package/src/adk/api/client.ts +0 -46
- package/src/adk/core/constants/tool-icons.ts +0 -51
- package/src/adk/core/models/AgentBuilder.ts +0 -80
- package/src/adk/core/models/AgentRunRequest.ts +0 -33
- package/src/adk/core/models/Eval.ts +0 -70
- package/src/adk/core/models/LiveRequest.ts +0 -23
- package/src/adk/core/models/RuntimeConfig.ts +0 -33
- package/src/adk/core/models/Session.ts +0 -52
- package/src/adk/core/models/Trace.ts +0 -42
- package/src/adk/core/models/types.ts +0 -255
- package/src/lib/utils.ts +0 -54
- package/src/mtgate_api/@tanstack/react-query.gen.ts +0 -142
- package/src/mtgate_api/schemas.gen.ts +0 -290
- package/src/mtgate_api/zod.gen.ts +0 -176
- package/src/mtmai_api/@tanstack/react-query.gen.ts +0 -1010
- package/src/mtmai_api/client/client.gen.ts +0 -311
- package/src/mtmai_api/client/index.ts +0 -25
- package/src/mtmai_api/client/types.gen.ts +0 -241
- package/src/mtmai_api/client/utils.gen.ts +0 -332
- package/src/mtmai_api/client.gen.ts +0 -16
- package/src/mtmai_api/core/auth.gen.ts +0 -42
- package/src/mtmai_api/core/bodySerializer.gen.ts +0 -100
- package/src/mtmai_api/core/params.gen.ts +0 -176
- package/src/mtmai_api/core/pathSerializer.gen.ts +0 -181
- package/src/mtmai_api/core/queryKeySerializer.gen.ts +0 -136
- package/src/mtmai_api/core/serverSentEvents.gen.ts +0 -266
- package/src/mtmai_api/core/types.gen.ts +0 -118
- package/src/mtmai_api/core/utils.gen.ts +0 -143
- package/src/mtmai_api/index.ts +0 -4
- package/src/mtmai_api/schemas.gen.ts +0 -9980
- package/src/mtmai_api/sdk.gen.ts +0 -471
- package/src/mtmai_api/types.gen.ts +0 -7609
- package/src/mtmai_api/zod.gen.ts +0 -5101
- package/src/supabase/use-sb-query/prefetch.ts +0 -59
package/src/openclaw/gateway.ts
CHANGED
|
@@ -1,392 +1,392 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
export type GatewayEventFrame = {
|
|
6
|
-
type: "event";
|
|
7
|
-
event: string;
|
|
8
|
-
payload?: unknown;
|
|
9
|
-
seq?: number;
|
|
10
|
-
stateVersion?: { presence: number; health: number };
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export type GatewayResponseFrame = {
|
|
14
|
-
type: "res";
|
|
15
|
-
id: string;
|
|
16
|
-
ok: boolean;
|
|
17
|
-
payload?: unknown;
|
|
18
|
-
error?: { code: string; message: string; details?: unknown };
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type GatewayErrorInfo = {
|
|
22
|
-
code: string;
|
|
23
|
-
message: string;
|
|
24
|
-
details?: unknown;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export type GatewayHelloOk = {
|
|
28
|
-
type: "hello-ok";
|
|
29
|
-
protocol: number;
|
|
30
|
-
features?: { methods?: string[]; events?: string[] };
|
|
31
|
-
snapshot?: unknown;
|
|
32
|
-
auth?: {
|
|
33
|
-
deviceToken?: string;
|
|
34
|
-
role?: string;
|
|
35
|
-
scopes?: string[];
|
|
36
|
-
issuedAtMs?: number;
|
|
37
|
-
};
|
|
38
|
-
policy?: { tickIntervalMs?: number };
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
type Pending = {
|
|
42
|
-
resolve: (value: unknown) => void;
|
|
43
|
-
reject: (err: unknown) => void;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export type GatewayBrowserClientOptions = {
|
|
47
|
-
url: string;
|
|
48
|
-
token?: string;
|
|
49
|
-
password?: string;
|
|
50
|
-
deviceIdentity?: DeviceIdentity | null;
|
|
51
|
-
clientName?: GatewayClientId;
|
|
52
|
-
clientVersion?: string;
|
|
53
|
-
platform?: string;
|
|
54
|
-
mode?: GatewayClientMode;
|
|
55
|
-
instanceId?: string;
|
|
56
|
-
onHello?: (hello: GatewayHelloOk) => void;
|
|
57
|
-
onEvent?: (evt: GatewayEventFrame) => void;
|
|
58
|
-
onClose?: (info: { code: number; reason: string; error?: GatewayErrorInfo | null }) => void;
|
|
59
|
-
onGap?: (info: { expected: number; received: number }) => void;
|
|
60
|
-
autoReconnect?: boolean;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// 4008 = application-defined code (browser rejects 1008 "Policy Violation")
|
|
64
|
-
const CONNECT_FAILED_CLOSE_CODE = 4008;
|
|
65
|
-
|
|
66
|
-
export class GatewayRequestError extends Error {
|
|
67
|
-
readonly code: string;
|
|
68
|
-
readonly details?: unknown;
|
|
69
|
-
|
|
70
|
-
constructor(error: GatewayErrorInfo) {
|
|
71
|
-
super(error.message || error.code);
|
|
72
|
-
this.name = "GatewayRequestError";
|
|
73
|
-
this.code = error.code;
|
|
74
|
-
this.details = error.details;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export class GatewayBrowserClient {
|
|
79
|
-
private ws: WebSocket | null = null;
|
|
80
|
-
private pending = new Map<string, Pending>();
|
|
81
|
-
private closed = false;
|
|
82
|
-
private lastSeq: number | null = null;
|
|
83
|
-
private connectNonce: string | null = null;
|
|
84
|
-
private connectSent = false;
|
|
85
|
-
private connectTimer: number | null = null;
|
|
86
|
-
private backoffMs = 800;
|
|
87
|
-
private connectError: GatewayErrorInfo | null = null;
|
|
88
|
-
private pendingTokenResetRetry = false;
|
|
89
|
-
private tokenResetRetryBudgetUsed = false;
|
|
90
|
-
|
|
91
|
-
constructor(private opts: GatewayBrowserClientOptions) {}
|
|
92
|
-
|
|
93
|
-
start() {
|
|
94
|
-
this.closed = false;
|
|
95
|
-
this.connect();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
stop() {
|
|
99
|
-
this.closed = true;
|
|
100
|
-
this.ws?.close();
|
|
101
|
-
this.ws = null;
|
|
102
|
-
this.pendingTokenResetRetry = false;
|
|
103
|
-
this.tokenResetRetryBudgetUsed = false;
|
|
104
|
-
this.flushPending(new Error("gateway client stopped"));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
get connected() {
|
|
108
|
-
return this.ws?.readyState === WebSocket.OPEN;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private connect() {
|
|
112
|
-
if (this.closed) return;
|
|
113
|
-
try {
|
|
114
|
-
this.ws = new WebSocket(this.opts.url);
|
|
115
|
-
this.ws.onopen = () => this.queueConnect();
|
|
116
|
-
this.ws.onmessage = (ev) => this.handleMessage(String(ev.data ?? ""));
|
|
117
|
-
this.ws.onclose = (ev) => {
|
|
118
|
-
const reason = String(ev.reason ?? "");
|
|
119
|
-
const error = this.connectError;
|
|
120
|
-
this.connectError = null;
|
|
121
|
-
this.ws = null;
|
|
122
|
-
this.flushPending(new Error(`gateway closed (${ev.code}): ${reason}`));
|
|
123
|
-
|
|
124
|
-
if (this.pendingTokenResetRetry) {
|
|
125
|
-
this.pendingTokenResetRetry = false;
|
|
126
|
-
window.setTimeout(() => this.connect(), 0);
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
this.opts.onClose?.({ code: ev.code, reason, error });
|
|
131
|
-
this.scheduleReconnect();
|
|
132
|
-
};
|
|
133
|
-
this.ws.onerror = () => {
|
|
134
|
-
// ignored; close handler will fire
|
|
135
|
-
};
|
|
136
|
-
} catch (err) {
|
|
137
|
-
console.error("[gateway] Failed to create WebSocket instance:", err);
|
|
138
|
-
// Simulate a close event for the UI to handle
|
|
139
|
-
this.opts.onClose?.({
|
|
140
|
-
code: 1006,
|
|
141
|
-
reason: err instanceof Error ? err.message : "Connect failed",
|
|
142
|
-
error: null,
|
|
143
|
-
});
|
|
144
|
-
this.scheduleReconnect();
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private scheduleReconnect() {
|
|
149
|
-
if (this.closed || this.opts.autoReconnect === false) return;
|
|
150
|
-
const delay = this.backoffMs;
|
|
151
|
-
this.backoffMs = Math.min(this.backoffMs * 1.7, 15_000);
|
|
152
|
-
window.setTimeout(() => this.connect(), delay);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
private flushPending(err: Error) {
|
|
156
|
-
for (const [, p] of this.pending) p.reject(err);
|
|
157
|
-
this.pending.clear();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private async sendConnect() {
|
|
161
|
-
if (this.connectSent) return;
|
|
162
|
-
this.connectSent = true;
|
|
163
|
-
if (this.connectTimer !== null) {
|
|
164
|
-
window.clearTimeout(this.connectTimer);
|
|
165
|
-
this.connectTimer = null;
|
|
166
|
-
}
|
|
167
|
-
const hasWebCrypto = typeof crypto !== "undefined" && !!crypto.subtle;
|
|
168
|
-
|
|
169
|
-
const scopes = ["operator.admin", "operator.approvals", "operator.pairing"];
|
|
170
|
-
const role = "operator";
|
|
171
|
-
|
|
172
|
-
// Dynamic import types for correct typing
|
|
173
|
-
let deviceIdentity = this.opts.deviceIdentity ?? null;
|
|
174
|
-
let authToken: string | undefined;
|
|
175
|
-
|
|
176
|
-
// Dynamically import device auth logic only if needed and in secure context
|
|
177
|
-
if (hasWebCrypto && !deviceIdentity) {
|
|
178
|
-
try {
|
|
179
|
-
const { loadOrCreateDeviceIdentity } = await import("./device-identity");
|
|
180
|
-
|
|
181
|
-
deviceIdentity = await loadOrCreateDeviceIdentity();
|
|
182
|
-
} catch (e) {
|
|
183
|
-
console.warn("[gateway] Failed to load device identity libs", e);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (deviceIdentity) {
|
|
188
|
-
authToken = loadDeviceAuthToken({
|
|
189
|
-
deviceId: deviceIdentity.deviceId,
|
|
190
|
-
role,
|
|
191
|
-
})?.token;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const auth =
|
|
195
|
-
authToken || this.opts.token || this.opts.password
|
|
196
|
-
? {
|
|
197
|
-
token: authToken ?? this.opts.token,
|
|
198
|
-
password: this.opts.password,
|
|
199
|
-
}
|
|
200
|
-
: undefined;
|
|
201
|
-
|
|
202
|
-
let device:
|
|
203
|
-
| {
|
|
204
|
-
id: string;
|
|
205
|
-
publicKey: string;
|
|
206
|
-
signature: string;
|
|
207
|
-
signedAt: number;
|
|
208
|
-
nonce: string | undefined;
|
|
209
|
-
}
|
|
210
|
-
| undefined;
|
|
211
|
-
|
|
212
|
-
if (deviceIdentity) {
|
|
213
|
-
const signedAtMs = Date.now();
|
|
214
|
-
const nonce = this.connectNonce ?? "";
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const { buildDeviceAuthPayload } = await import("./device-auth-utils");
|
|
218
|
-
const { signDevicePayload } = await import("./device-identity");
|
|
219
|
-
|
|
220
|
-
const payload = buildDeviceAuthPayload({
|
|
221
|
-
deviceId: deviceIdentity.deviceId,
|
|
222
|
-
clientId: this.opts.clientName ?? GATEWAY_CLIENT_IDS.CONTROL_UI,
|
|
223
|
-
clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.WEBCHAT,
|
|
224
|
-
role,
|
|
225
|
-
scopes,
|
|
226
|
-
signedAtMs,
|
|
227
|
-
token: authToken ?? undefined,
|
|
228
|
-
nonce,
|
|
229
|
-
});
|
|
230
|
-
const signature = await signDevicePayload(deviceIdentity.privateKey, payload);
|
|
231
|
-
device = {
|
|
232
|
-
id: deviceIdentity.deviceId,
|
|
233
|
-
publicKey: deviceIdentity.publicKey,
|
|
234
|
-
signature,
|
|
235
|
-
signedAt: signedAtMs,
|
|
236
|
-
nonce,
|
|
237
|
-
};
|
|
238
|
-
} catch (e) {
|
|
239
|
-
console.error("[gateway] Failed to sign device payload", e);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const params = {
|
|
244
|
-
minProtocol: 3,
|
|
245
|
-
maxProtocol: 3,
|
|
246
|
-
client: {
|
|
247
|
-
id: this.opts.clientName ?? GATEWAY_CLIENT_IDS.CONTROL_UI,
|
|
248
|
-
version: this.opts.clientVersion ?? "dev",
|
|
249
|
-
platform: this.opts.platform ?? navigator.platform ?? "web",
|
|
250
|
-
mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.WEBCHAT,
|
|
251
|
-
instanceId: this.opts.instanceId,
|
|
252
|
-
},
|
|
253
|
-
role,
|
|
254
|
-
scopes,
|
|
255
|
-
device,
|
|
256
|
-
caps: [],
|
|
257
|
-
auth,
|
|
258
|
-
userAgent: navigator.userAgent,
|
|
259
|
-
locale: navigator.language,
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
void this.request<GatewayHelloOk>("connect", params)
|
|
263
|
-
.then(async (hello) => {
|
|
264
|
-
this.connectError = null;
|
|
265
|
-
if (hello?.auth?.deviceToken && deviceIdentity) {
|
|
266
|
-
try {
|
|
267
|
-
storeDeviceAuthToken({
|
|
268
|
-
deviceId: deviceIdentity.deviceId,
|
|
269
|
-
role: hello.auth.role ?? role,
|
|
270
|
-
token: hello.auth.deviceToken,
|
|
271
|
-
scopes: hello.auth.scopes ?? [],
|
|
272
|
-
});
|
|
273
|
-
} catch (e) {
|
|
274
|
-
console.warn("[gateway] Failed to store device token", e);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
this.backoffMs = 800;
|
|
278
|
-
this.tokenResetRetryBudgetUsed = false;
|
|
279
|
-
this.opts.onHello?.(hello);
|
|
280
|
-
})
|
|
281
|
-
.catch((err) => {
|
|
282
|
-
console.warn("[gateway] connect seq failed", err);
|
|
283
|
-
|
|
284
|
-
if (
|
|
285
|
-
!this.tokenResetRetryBudgetUsed &&
|
|
286
|
-
deviceIdentity &&
|
|
287
|
-
authToken &&
|
|
288
|
-
err instanceof GatewayRequestError &&
|
|
289
|
-
err.code === "AUTH_TOKEN_MISMATCH"
|
|
290
|
-
) {
|
|
291
|
-
clearDeviceAuthToken({
|
|
292
|
-
deviceId: deviceIdentity.deviceId,
|
|
293
|
-
role,
|
|
294
|
-
});
|
|
295
|
-
this.pendingTokenResetRetry = true;
|
|
296
|
-
this.tokenResetRetryBudgetUsed = true;
|
|
297
|
-
this.connectError = null;
|
|
298
|
-
this.ws?.close(CONNECT_FAILED_CLOSE_CODE, "retry after clearing stale device token");
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
this.connectError =
|
|
303
|
-
err instanceof GatewayRequestError
|
|
304
|
-
? { code: err.code, message: err.message, details: err.details }
|
|
305
|
-
: { code: "CONNECT_FAILED", message: err instanceof Error ? err.message : "connect failed" };
|
|
306
|
-
// Do not close here, let the interval handle it or the error event?
|
|
307
|
-
// Original code closed it.
|
|
308
|
-
this.ws?.close(CONNECT_FAILED_CLOSE_CODE, "connect failed");
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
private handleMessage(raw: string) {
|
|
313
|
-
let parsed: unknown;
|
|
314
|
-
try {
|
|
315
|
-
parsed = JSON.parse(raw);
|
|
316
|
-
} catch {
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const frame = parsed as { type?: unknown };
|
|
321
|
-
if (frame.type === "event") {
|
|
322
|
-
const evt = parsed as GatewayEventFrame;
|
|
323
|
-
|
|
324
|
-
if (evt.event === "connect.challenge") {
|
|
325
|
-
const payload = evt.payload as { nonce?: unknown } | undefined;
|
|
326
|
-
const nonce = payload && typeof payload.nonce === "string" ? payload.nonce : null;
|
|
327
|
-
if (nonce) {
|
|
328
|
-
this.connectNonce = nonce;
|
|
329
|
-
void this.sendConnect();
|
|
330
|
-
}
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
const seq = typeof evt.seq === "number" ? evt.seq : null;
|
|
334
|
-
if (seq !== null) {
|
|
335
|
-
if (this.lastSeq !== null && seq > this.lastSeq + 1) {
|
|
336
|
-
this.opts.onGap?.({ expected: this.lastSeq + 1, received: seq });
|
|
337
|
-
}
|
|
338
|
-
this.lastSeq = seq;
|
|
339
|
-
}
|
|
340
|
-
try {
|
|
341
|
-
this.opts.onEvent?.(evt);
|
|
342
|
-
} catch (err) {
|
|
343
|
-
console.error("[gateway] event handler error:", err);
|
|
344
|
-
}
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (frame.type === "res") {
|
|
349
|
-
const res = parsed as GatewayResponseFrame;
|
|
350
|
-
const pending = this.pending.get(res.id);
|
|
351
|
-
if (!pending) return;
|
|
352
|
-
this.pending.delete(res.id);
|
|
353
|
-
if (res.ok) pending.resolve(res.payload);
|
|
354
|
-
else {
|
|
355
|
-
const error = res.error;
|
|
356
|
-
pending.reject(
|
|
357
|
-
error
|
|
358
|
-
? new GatewayRequestError({
|
|
359
|
-
code: error.code,
|
|
360
|
-
message: error.message ?? "request failed",
|
|
361
|
-
details: error.details,
|
|
362
|
-
})
|
|
363
|
-
: new Error("request failed"),
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
request<T = unknown>(method: string, params?: unknown): Promise<T> {
|
|
371
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
372
|
-
return Promise.reject(new Error("gateway not connected"));
|
|
373
|
-
}
|
|
374
|
-
const id = crypto.randomUUID();
|
|
375
|
-
const frame = { type: "req", id, method, params };
|
|
376
|
-
const p = new Promise<T>((resolve, reject) => {
|
|
377
|
-
this.pending.set(id, { resolve: (v) => resolve(v as T), reject });
|
|
378
|
-
});
|
|
379
|
-
this.ws.send(JSON.stringify(frame));
|
|
380
|
-
return p;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
private queueConnect() {
|
|
384
|
-
this.connectNonce = null;
|
|
385
|
-
this.connectSent = false;
|
|
386
|
-
if (this.connectTimer !== null) window.clearTimeout(this.connectTimer);
|
|
387
|
-
|
|
388
|
-
this.connectTimer = window.setTimeout(() => {
|
|
389
|
-
void this.sendConnect();
|
|
390
|
-
}, 750);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
1
|
+
import { GATEWAY_CLIENT_IDS, GATEWAY_CLIENT_MODES, type GatewayClientId, type GatewayClientMode } from "./client-info";
|
|
2
|
+
import { clearDeviceAuthToken, loadDeviceAuthToken, storeDeviceAuthToken } from "./device-auth";
|
|
3
|
+
import type { DeviceIdentity } from "./device-identity";
|
|
4
|
+
|
|
5
|
+
export type GatewayEventFrame = {
|
|
6
|
+
type: "event";
|
|
7
|
+
event: string;
|
|
8
|
+
payload?: unknown;
|
|
9
|
+
seq?: number;
|
|
10
|
+
stateVersion?: { presence: number; health: number };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type GatewayResponseFrame = {
|
|
14
|
+
type: "res";
|
|
15
|
+
id: string;
|
|
16
|
+
ok: boolean;
|
|
17
|
+
payload?: unknown;
|
|
18
|
+
error?: { code: string; message: string; details?: unknown };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type GatewayErrorInfo = {
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
details?: unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type GatewayHelloOk = {
|
|
28
|
+
type: "hello-ok";
|
|
29
|
+
protocol: number;
|
|
30
|
+
features?: { methods?: string[]; events?: string[] };
|
|
31
|
+
snapshot?: unknown;
|
|
32
|
+
auth?: {
|
|
33
|
+
deviceToken?: string;
|
|
34
|
+
role?: string;
|
|
35
|
+
scopes?: string[];
|
|
36
|
+
issuedAtMs?: number;
|
|
37
|
+
};
|
|
38
|
+
policy?: { tickIntervalMs?: number };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type Pending = {
|
|
42
|
+
resolve: (value: unknown) => void;
|
|
43
|
+
reject: (err: unknown) => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type GatewayBrowserClientOptions = {
|
|
47
|
+
url: string;
|
|
48
|
+
token?: string;
|
|
49
|
+
password?: string;
|
|
50
|
+
deviceIdentity?: DeviceIdentity | null;
|
|
51
|
+
clientName?: GatewayClientId;
|
|
52
|
+
clientVersion?: string;
|
|
53
|
+
platform?: string;
|
|
54
|
+
mode?: GatewayClientMode;
|
|
55
|
+
instanceId?: string;
|
|
56
|
+
onHello?: (hello: GatewayHelloOk) => void;
|
|
57
|
+
onEvent?: (evt: GatewayEventFrame) => void;
|
|
58
|
+
onClose?: (info: { code: number; reason: string; error?: GatewayErrorInfo | null }) => void;
|
|
59
|
+
onGap?: (info: { expected: number; received: number }) => void;
|
|
60
|
+
autoReconnect?: boolean;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// 4008 = application-defined code (browser rejects 1008 "Policy Violation")
|
|
64
|
+
const CONNECT_FAILED_CLOSE_CODE = 4008;
|
|
65
|
+
|
|
66
|
+
export class GatewayRequestError extends Error {
|
|
67
|
+
readonly code: string;
|
|
68
|
+
readonly details?: unknown;
|
|
69
|
+
|
|
70
|
+
constructor(error: GatewayErrorInfo) {
|
|
71
|
+
super(error.message || error.code);
|
|
72
|
+
this.name = "GatewayRequestError";
|
|
73
|
+
this.code = error.code;
|
|
74
|
+
this.details = error.details;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export class GatewayBrowserClient {
|
|
79
|
+
private ws: WebSocket | null = null;
|
|
80
|
+
private pending = new Map<string, Pending>();
|
|
81
|
+
private closed = false;
|
|
82
|
+
private lastSeq: number | null = null;
|
|
83
|
+
private connectNonce: string | null = null;
|
|
84
|
+
private connectSent = false;
|
|
85
|
+
private connectTimer: number | null = null;
|
|
86
|
+
private backoffMs = 800;
|
|
87
|
+
private connectError: GatewayErrorInfo | null = null;
|
|
88
|
+
private pendingTokenResetRetry = false;
|
|
89
|
+
private tokenResetRetryBudgetUsed = false;
|
|
90
|
+
|
|
91
|
+
constructor(private opts: GatewayBrowserClientOptions) {}
|
|
92
|
+
|
|
93
|
+
start() {
|
|
94
|
+
this.closed = false;
|
|
95
|
+
this.connect();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
stop() {
|
|
99
|
+
this.closed = true;
|
|
100
|
+
this.ws?.close();
|
|
101
|
+
this.ws = null;
|
|
102
|
+
this.pendingTokenResetRetry = false;
|
|
103
|
+
this.tokenResetRetryBudgetUsed = false;
|
|
104
|
+
this.flushPending(new Error("gateway client stopped"));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get connected() {
|
|
108
|
+
return this.ws?.readyState === WebSocket.OPEN;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private connect() {
|
|
112
|
+
if (this.closed) return;
|
|
113
|
+
try {
|
|
114
|
+
this.ws = new WebSocket(this.opts.url);
|
|
115
|
+
this.ws.onopen = () => this.queueConnect();
|
|
116
|
+
this.ws.onmessage = (ev) => this.handleMessage(String(ev.data ?? ""));
|
|
117
|
+
this.ws.onclose = (ev) => {
|
|
118
|
+
const reason = String(ev.reason ?? "");
|
|
119
|
+
const error = this.connectError;
|
|
120
|
+
this.connectError = null;
|
|
121
|
+
this.ws = null;
|
|
122
|
+
this.flushPending(new Error(`gateway closed (${ev.code}): ${reason}`));
|
|
123
|
+
|
|
124
|
+
if (this.pendingTokenResetRetry) {
|
|
125
|
+
this.pendingTokenResetRetry = false;
|
|
126
|
+
window.setTimeout(() => this.connect(), 0);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.opts.onClose?.({ code: ev.code, reason, error });
|
|
131
|
+
this.scheduleReconnect();
|
|
132
|
+
};
|
|
133
|
+
this.ws.onerror = () => {
|
|
134
|
+
// ignored; close handler will fire
|
|
135
|
+
};
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error("[gateway] Failed to create WebSocket instance:", err);
|
|
138
|
+
// Simulate a close event for the UI to handle
|
|
139
|
+
this.opts.onClose?.({
|
|
140
|
+
code: 1006,
|
|
141
|
+
reason: err instanceof Error ? err.message : "Connect failed",
|
|
142
|
+
error: null,
|
|
143
|
+
});
|
|
144
|
+
this.scheduleReconnect();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private scheduleReconnect() {
|
|
149
|
+
if (this.closed || this.opts.autoReconnect === false) return;
|
|
150
|
+
const delay = this.backoffMs;
|
|
151
|
+
this.backoffMs = Math.min(this.backoffMs * 1.7, 15_000);
|
|
152
|
+
window.setTimeout(() => this.connect(), delay);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private flushPending(err: Error) {
|
|
156
|
+
for (const [, p] of this.pending) p.reject(err);
|
|
157
|
+
this.pending.clear();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private async sendConnect() {
|
|
161
|
+
if (this.connectSent) return;
|
|
162
|
+
this.connectSent = true;
|
|
163
|
+
if (this.connectTimer !== null) {
|
|
164
|
+
window.clearTimeout(this.connectTimer);
|
|
165
|
+
this.connectTimer = null;
|
|
166
|
+
}
|
|
167
|
+
const hasWebCrypto = typeof crypto !== "undefined" && !!crypto.subtle;
|
|
168
|
+
|
|
169
|
+
const scopes = ["operator.admin", "operator.approvals", "operator.pairing"];
|
|
170
|
+
const role = "operator";
|
|
171
|
+
|
|
172
|
+
// Dynamic import types for correct typing
|
|
173
|
+
let deviceIdentity = this.opts.deviceIdentity ?? null;
|
|
174
|
+
let authToken: string | undefined;
|
|
175
|
+
|
|
176
|
+
// Dynamically import device auth logic only if needed and in secure context
|
|
177
|
+
if (hasWebCrypto && !deviceIdentity) {
|
|
178
|
+
try {
|
|
179
|
+
const { loadOrCreateDeviceIdentity } = await import("./device-identity");
|
|
180
|
+
|
|
181
|
+
deviceIdentity = await loadOrCreateDeviceIdentity();
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.warn("[gateway] Failed to load device identity libs", e);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (deviceIdentity) {
|
|
188
|
+
authToken = loadDeviceAuthToken({
|
|
189
|
+
deviceId: deviceIdentity.deviceId,
|
|
190
|
+
role,
|
|
191
|
+
})?.token;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const auth =
|
|
195
|
+
authToken || this.opts.token || this.opts.password
|
|
196
|
+
? {
|
|
197
|
+
token: authToken ?? this.opts.token,
|
|
198
|
+
password: this.opts.password,
|
|
199
|
+
}
|
|
200
|
+
: undefined;
|
|
201
|
+
|
|
202
|
+
let device:
|
|
203
|
+
| {
|
|
204
|
+
id: string;
|
|
205
|
+
publicKey: string;
|
|
206
|
+
signature: string;
|
|
207
|
+
signedAt: number;
|
|
208
|
+
nonce: string | undefined;
|
|
209
|
+
}
|
|
210
|
+
| undefined;
|
|
211
|
+
|
|
212
|
+
if (deviceIdentity) {
|
|
213
|
+
const signedAtMs = Date.now();
|
|
214
|
+
const nonce = this.connectNonce ?? "";
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const { buildDeviceAuthPayload } = await import("./device-auth-utils");
|
|
218
|
+
const { signDevicePayload } = await import("./device-identity");
|
|
219
|
+
|
|
220
|
+
const payload = buildDeviceAuthPayload({
|
|
221
|
+
deviceId: deviceIdentity.deviceId,
|
|
222
|
+
clientId: this.opts.clientName ?? GATEWAY_CLIENT_IDS.CONTROL_UI,
|
|
223
|
+
clientMode: this.opts.mode ?? GATEWAY_CLIENT_MODES.WEBCHAT,
|
|
224
|
+
role,
|
|
225
|
+
scopes,
|
|
226
|
+
signedAtMs,
|
|
227
|
+
token: authToken ?? undefined,
|
|
228
|
+
nonce,
|
|
229
|
+
});
|
|
230
|
+
const signature = await signDevicePayload(deviceIdentity.privateKey, payload);
|
|
231
|
+
device = {
|
|
232
|
+
id: deviceIdentity.deviceId,
|
|
233
|
+
publicKey: deviceIdentity.publicKey,
|
|
234
|
+
signature,
|
|
235
|
+
signedAt: signedAtMs,
|
|
236
|
+
nonce,
|
|
237
|
+
};
|
|
238
|
+
} catch (e) {
|
|
239
|
+
console.error("[gateway] Failed to sign device payload", e);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const params = {
|
|
244
|
+
minProtocol: 3,
|
|
245
|
+
maxProtocol: 3,
|
|
246
|
+
client: {
|
|
247
|
+
id: this.opts.clientName ?? GATEWAY_CLIENT_IDS.CONTROL_UI,
|
|
248
|
+
version: this.opts.clientVersion ?? "dev",
|
|
249
|
+
platform: this.opts.platform ?? navigator.platform ?? "web",
|
|
250
|
+
mode: this.opts.mode ?? GATEWAY_CLIENT_MODES.WEBCHAT,
|
|
251
|
+
instanceId: this.opts.instanceId,
|
|
252
|
+
},
|
|
253
|
+
role,
|
|
254
|
+
scopes,
|
|
255
|
+
device,
|
|
256
|
+
caps: [],
|
|
257
|
+
auth,
|
|
258
|
+
userAgent: navigator.userAgent,
|
|
259
|
+
locale: navigator.language,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
void this.request<GatewayHelloOk>("connect", params)
|
|
263
|
+
.then(async (hello) => {
|
|
264
|
+
this.connectError = null;
|
|
265
|
+
if (hello?.auth?.deviceToken && deviceIdentity) {
|
|
266
|
+
try {
|
|
267
|
+
storeDeviceAuthToken({
|
|
268
|
+
deviceId: deviceIdentity.deviceId,
|
|
269
|
+
role: hello.auth.role ?? role,
|
|
270
|
+
token: hello.auth.deviceToken,
|
|
271
|
+
scopes: hello.auth.scopes ?? [],
|
|
272
|
+
});
|
|
273
|
+
} catch (e) {
|
|
274
|
+
console.warn("[gateway] Failed to store device token", e);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
this.backoffMs = 800;
|
|
278
|
+
this.tokenResetRetryBudgetUsed = false;
|
|
279
|
+
this.opts.onHello?.(hello);
|
|
280
|
+
})
|
|
281
|
+
.catch((err) => {
|
|
282
|
+
console.warn("[gateway] connect seq failed", err);
|
|
283
|
+
|
|
284
|
+
if (
|
|
285
|
+
!this.tokenResetRetryBudgetUsed &&
|
|
286
|
+
deviceIdentity &&
|
|
287
|
+
authToken &&
|
|
288
|
+
err instanceof GatewayRequestError &&
|
|
289
|
+
err.code === "AUTH_TOKEN_MISMATCH"
|
|
290
|
+
) {
|
|
291
|
+
clearDeviceAuthToken({
|
|
292
|
+
deviceId: deviceIdentity.deviceId,
|
|
293
|
+
role,
|
|
294
|
+
});
|
|
295
|
+
this.pendingTokenResetRetry = true;
|
|
296
|
+
this.tokenResetRetryBudgetUsed = true;
|
|
297
|
+
this.connectError = null;
|
|
298
|
+
this.ws?.close(CONNECT_FAILED_CLOSE_CODE, "retry after clearing stale device token");
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
this.connectError =
|
|
303
|
+
err instanceof GatewayRequestError
|
|
304
|
+
? { code: err.code, message: err.message, details: err.details }
|
|
305
|
+
: { code: "CONNECT_FAILED", message: err instanceof Error ? err.message : "connect failed" };
|
|
306
|
+
// Do not close here, let the interval handle it or the error event?
|
|
307
|
+
// Original code closed it.
|
|
308
|
+
this.ws?.close(CONNECT_FAILED_CLOSE_CODE, "connect failed");
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private handleMessage(raw: string) {
|
|
313
|
+
let parsed: unknown;
|
|
314
|
+
try {
|
|
315
|
+
parsed = JSON.parse(raw);
|
|
316
|
+
} catch {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const frame = parsed as { type?: unknown };
|
|
321
|
+
if (frame.type === "event") {
|
|
322
|
+
const evt = parsed as GatewayEventFrame;
|
|
323
|
+
|
|
324
|
+
if (evt.event === "connect.challenge") {
|
|
325
|
+
const payload = evt.payload as { nonce?: unknown } | undefined;
|
|
326
|
+
const nonce = payload && typeof payload.nonce === "string" ? payload.nonce : null;
|
|
327
|
+
if (nonce) {
|
|
328
|
+
this.connectNonce = nonce;
|
|
329
|
+
void this.sendConnect();
|
|
330
|
+
}
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const seq = typeof evt.seq === "number" ? evt.seq : null;
|
|
334
|
+
if (seq !== null) {
|
|
335
|
+
if (this.lastSeq !== null && seq > this.lastSeq + 1) {
|
|
336
|
+
this.opts.onGap?.({ expected: this.lastSeq + 1, received: seq });
|
|
337
|
+
}
|
|
338
|
+
this.lastSeq = seq;
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
this.opts.onEvent?.(evt);
|
|
342
|
+
} catch (err) {
|
|
343
|
+
console.error("[gateway] event handler error:", err);
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (frame.type === "res") {
|
|
349
|
+
const res = parsed as GatewayResponseFrame;
|
|
350
|
+
const pending = this.pending.get(res.id);
|
|
351
|
+
if (!pending) return;
|
|
352
|
+
this.pending.delete(res.id);
|
|
353
|
+
if (res.ok) pending.resolve(res.payload);
|
|
354
|
+
else {
|
|
355
|
+
const error = res.error;
|
|
356
|
+
pending.reject(
|
|
357
|
+
error
|
|
358
|
+
? new GatewayRequestError({
|
|
359
|
+
code: error.code,
|
|
360
|
+
message: error.message ?? "request failed",
|
|
361
|
+
details: error.details,
|
|
362
|
+
})
|
|
363
|
+
: new Error("request failed"),
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
request<T = unknown>(method: string, params?: unknown): Promise<T> {
|
|
371
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
372
|
+
return Promise.reject(new Error("gateway not connected"));
|
|
373
|
+
}
|
|
374
|
+
const id = crypto.randomUUID();
|
|
375
|
+
const frame = { type: "req", id, method, params };
|
|
376
|
+
const p = new Promise<T>((resolve, reject) => {
|
|
377
|
+
this.pending.set(id, { resolve: (v) => resolve(v as T), reject });
|
|
378
|
+
});
|
|
379
|
+
this.ws.send(JSON.stringify(frame));
|
|
380
|
+
return p;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private queueConnect() {
|
|
384
|
+
this.connectNonce = null;
|
|
385
|
+
this.connectSent = false;
|
|
386
|
+
if (this.connectTimer !== null) window.clearTimeout(this.connectTimer);
|
|
387
|
+
|
|
388
|
+
this.connectTimer = window.setTimeout(() => {
|
|
389
|
+
void this.sendConnect();
|
|
390
|
+
}, 750);
|
|
391
|
+
}
|
|
392
|
+
}
|