@wishknish/knishio-client-ts 0.7.7 → 0.7.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wishknish/knishio-client-ts",
3
- "version": "0.7.7",
3
+ "version": "0.7.8",
4
4
  "productName": "Knish.IO TypeScript SDK Client",
5
5
  "description": "TypeScript implementation of the Knish.IO SDK to consume Knish.IO GraphQL APIs with enhanced type safety.",
6
6
  "license": "GPL-3.0-or-later",
@@ -84,6 +84,7 @@ import QueryUserActivity from '@/query/QueryUserActivity'
84
84
  import QueryToken from '@/query/QueryToken'
85
85
  import QueryMetaTypeViaAtom from '@/query/QueryMetaTypeViaAtom'
86
86
  import QueryMetaTypeViaMolecule from '@/query/QueryMetaTypeViaMolecule'
87
+ import QueryEmbeddingStatus from '@/query/QueryEmbeddingStatus'
87
88
 
88
89
  // Mutation imports
89
90
  import MutationCreateToken from '@/mutation/MutationCreateToken'
@@ -102,6 +103,10 @@ import MutationRequestAuthorizationGuest from '@/mutation/MutationRequestAuthori
102
103
  import MutationLinkIdentifier from '@/mutation/MutationLinkIdentifier'
103
104
  import MutationPeering from '@/mutation/MutationPeering'
104
105
  import MutationAppendRequest from '@/mutation/MutationAppendRequest'
106
+ import MutationProposeMolecule from '@/mutation/MutationProposeMolecule'
107
+
108
+ // Response imports
109
+ import ResponseProposeMolecule from '@/response/ResponseProposeMolecule'
105
110
 
106
111
  // Subscribe imports
107
112
  import CreateMoleculeSubscribe from '@/subscribe/CreateMoleculeSubscribe'
@@ -116,7 +121,10 @@ import {
116
121
  TransferBalanceException,
117
122
  WalletShadowException,
118
123
  StackableUnitAmountException,
119
- AuthorizationRejectedException
124
+ AuthorizationRejectedException,
125
+ MolecularHashMismatchException,
126
+ SignatureMismatchException,
127
+ AtomIndexException
120
128
  } from '@/exception'
121
129
 
122
130
  // Type imports
@@ -150,6 +158,13 @@ export default class KnishIOClient {
150
158
  private $__remainderWallet: Wallet | null = null
151
159
  private lastMoleculeQuery: Mutation | null = null
152
160
  private abortControllers: Map<string, AbortController> = new Map()
161
+ private $__capabilityCache: Record<string, boolean> = {}
162
+ // Promise-chain mutex serializing MutationProposeMolecule submissions on this
163
+ // client. Without it, concurrent createMolecule() calls both query the same
164
+ // ContinuID position and both sign with it — the second is rejected with
165
+ // OTS position reuse. Auth-token flow (MutationRequestAuthorization) inherits
166
+ // from MutationProposeMolecule so it's covered by the same lock.
167
+ private $__moleculeChain: Promise<unknown> = Promise.resolve()
153
168
 
154
169
  /**
155
170
  * Enhanced constructor with standardized configuration validation (Phase 2 Enhancement)
@@ -320,6 +335,7 @@ export default class KnishIOClient {
320
335
  this.$__authToken = null
321
336
  this.$__remainderWallet = null
322
337
  this.lastMoleculeQuery = null
338
+ this.$__capabilityCache = {}
323
339
  }
324
340
 
325
341
  /**
@@ -344,6 +360,20 @@ export default class KnishIOClient {
344
360
  }
345
361
  }
346
362
 
363
+ /**
364
+ * Sets the WebSocket (subscription) endpoint for this session.
365
+ *
366
+ * F-8a (cross-SDK parity, 2026-06-03): `setUri` only updates the HTTP endpoint; the
367
+ * subscription socket is built once from the `socket.socketUri` passed at
368
+ * construction. This lets a caller re-point the socket when the endpoint changes
369
+ * (mirrors the JS SDK's `setSocketUri`).
370
+ */
371
+ setSocketUri(socketUri: string): void {
372
+ if (this.$__client && 'setSocketUri' in this.$__client) {
373
+ (this.$__client as any).setSocketUri(socketUri)
374
+ }
375
+ }
376
+
347
377
  /**
348
378
  * Gets the Knish.IO server URIs
349
379
  */
@@ -517,6 +547,24 @@ export default class KnishIOClient {
517
547
  return mutation
518
548
  }
519
549
 
550
+ /**
551
+ * Serializes the given async work behind a per-client promise chain. Used to
552
+ * guarantee that at most one MutationProposeMolecule submission runs at a
553
+ * time on this client — query position, sign, submit, observe response must
554
+ * complete before the next one starts, or two callers race for the same OTS
555
+ * position and the second gets rejected.
556
+ *
557
+ * `.then(fn, fn)` runs fn whether the previous holder resolved or rejected;
558
+ * the queue's `.catch` swallows the rejection so a single failure doesn't
559
+ * poison every subsequent caller, while the rejection still propagates to
560
+ * the caller whose fn threw.
561
+ */
562
+ private withMoleculeLock<T>(fn: () => Promise<T>): Promise<T> {
563
+ const result = (this.$__moleculeChain as Promise<unknown>).then(fn, fn) as Promise<T>
564
+ this.$__moleculeChain = result.catch(() => undefined)
565
+ return result
566
+ }
567
+
520
568
  /**
521
569
  * Executes a query or mutation
522
570
  */
@@ -532,10 +580,42 @@ export default class KnishIOClient {
532
580
  })
533
581
  }
534
582
 
583
+ if (query instanceof MutationProposeMolecule) {
584
+ return await this.withMoleculeLock(async () => {
585
+ const response = await query.execute({ variables: variables || {} })
586
+ this.handlePositionDrift(response)
587
+ return response
588
+ })
589
+ }
590
+
535
591
  // Execute the query
536
592
  return await query.execute({ variables: variables || {} })
537
593
  }
538
594
 
595
+ /**
596
+ * When a ProposeMolecule submission is rejected for a position-related
597
+ * reason (OTS reuse, ContinuID chain violation, molecular hash mismatch),
598
+ * the cached remainder wallet and lastMoleculeQuery are stale — the
599
+ * validator's chain has advanced past what we know. Clear them so the next
600
+ * createMolecule call re-queries queryContinuId for the authoritative
601
+ * position. Other failure modes (network, malformed meta, bad signature
602
+ * bytes) don't imply drift; leave cached state alone.
603
+ */
604
+ private handlePositionDrift(response: Response | null): void {
605
+ if (!(response instanceof ResponseProposeMolecule)) return
606
+ const exc = response.toException()
607
+ if (!exc) return
608
+ if (
609
+ exc instanceof MolecularHashMismatchException ||
610
+ exc instanceof AtomIndexException ||
611
+ (exc instanceof SignatureMismatchException && exc.code === 'OTS_VERIFICATION_FAILED')
612
+ ) {
613
+ this.$__remainderWallet = null
614
+ this.lastMoleculeQuery = null
615
+ this.log('warn', `KnishIOClient::executeQuery() - position drift detected (${exc.name}/${exc.code}); cleared cached remainder wallet`)
616
+ }
617
+ }
618
+
539
619
  /**
540
620
  * Sets the secret for this session
541
621
  */
@@ -1060,6 +1140,76 @@ export default class KnishIOClient {
1060
1140
  return response
1061
1141
  }
1062
1142
 
1143
+ /**
1144
+ * Probes the connected server to check whether it supports a named root query field.
1145
+ * Result is cached per URI so the network round-trip happens at most once per URI.
1146
+ *
1147
+ * Uses GraphQL introspection which is universally supported by spec-compliant servers.
1148
+ *
1149
+ * @param fieldName - The root Query field name to check (e.g. 'embeddingStatus')
1150
+ * @returns true if the server schema includes the field, false otherwise
1151
+ */
1152
+ async hasQueryField(fieldName: string): Promise<boolean> {
1153
+ const uri = this.$__client.getUri()
1154
+ const cacheKey = `${uri}::${fieldName}`
1155
+
1156
+ if (typeof this.$__capabilityCache[cacheKey] === 'boolean') {
1157
+ return this.$__capabilityCache[cacheKey]!
1158
+ }
1159
+
1160
+ try {
1161
+ const result = await this.$__client.query({
1162
+ query: '{ __schema { queryType { fields { name } } } }',
1163
+ variables: {}
1164
+ })
1165
+
1166
+ const data = result?.data as Record<string, any> | undefined
1167
+ const fields: Array<{ name: string }> = data?.__schema?.queryType?.fields || []
1168
+ const supported = fields.some((f: { name: string }) => f.name === fieldName)
1169
+ this.$__capabilityCache[cacheKey] = supported
1170
+ return supported
1171
+ } catch (err: any) {
1172
+ this.log('warn', `KnishIOClient::hasQueryField() - Capability probe for '${fieldName}' failed: ${err.message}`)
1173
+ this.$__capabilityCache[cacheKey] = false
1174
+ return false
1175
+ }
1176
+ }
1177
+
1178
+ /**
1179
+ * Queries embedding status for one or more meta instances (DataBraid observability).
1180
+ *
1181
+ * If the connected server does not support the embeddingStatus query,
1182
+ * returns null without throwing an error (graceful degradation).
1183
+ *
1184
+ * Single mode: queryEmbeddingStatus({ metaType: 'product', metaId: 'SKU-001' })
1185
+ * Bulk mode: queryEmbeddingStatus({ instances: [{ metaType: 'product', metaId: 'SKU-001' }, ...] })
1186
+ *
1187
+ * @returns Response with payload(), or null if the server does not support this query
1188
+ */
1189
+ async queryEmbeddingStatus({
1190
+ metaType = null,
1191
+ metaId = null,
1192
+ instances = null
1193
+ }: {
1194
+ metaType?: string | null
1195
+ metaId?: string | null
1196
+ instances?: Array<{ metaType: string; metaId: string }> | null
1197
+ }): Promise<Response | null> {
1198
+ this.log('info', `KnishIOClient::queryEmbeddingStatus() - Checking embedding status for metaType: ${metaType || '(bulk)'}...`)
1199
+
1200
+ const supported = await this.hasQueryField('embeddingStatus')
1201
+
1202
+ if (!supported) {
1203
+ this.log('warn', 'KnishIOClient::queryEmbeddingStatus() - Server does not support embeddingStatus query. Returning null.')
1204
+ return null
1205
+ }
1206
+
1207
+ const query = this.createQuery(QueryEmbeddingStatus)
1208
+ const variables = QueryEmbeddingStatus.createVariables({ metaType, metaId, instances })
1209
+
1210
+ return this.executeQuery(query, variables)
1211
+ }
1212
+
1063
1213
  /**
1064
1214
  * Query cascading meta instances for batchId
1065
1215
  */
@@ -259,14 +259,13 @@ export default class Molecule {
259
259
  const atomMeta = new AtomMeta(meta)
260
260
  atomMeta.addPolicy(policy)
261
261
 
262
- const wallet = Wallet.create({
263
- secret: this.secret!,
264
- bundle: this.sourceWallet!.bundle!,
265
- token: 'USER'
266
- })
267
-
262
+ // F-3 (cross-SDK parity, 2026-06-03): sign the policy (R-isotope) atom from the
263
+ // established ContinuID source wallet, NOT a freshly-derived Wallet.create() at a
264
+ // random position. As the signing atom in createPolicy(), a fresh-wallet position
265
+ // is unknown to the validator → "ContinuID chain validation failed: signing
266
+ // position … not found as wallet". Mirrors createRule(), which uses this.sourceWallet.
268
267
  this.addAtom(Atom.create({
269
- wallet,
268
+ wallet: this.sourceWallet!,
270
269
  isotope: 'R',
271
270
  metaType,
272
271
  metaId,
package/src/index.ts CHANGED
@@ -94,6 +94,9 @@ export { default as QueryAtom } from './query/QueryAtom'
94
94
  export { default as QueryMetaType } from './query/QueryMetaType'
95
95
  export { default as QueryMetaTypeViaAtom } from './query/QueryMetaTypeViaAtom'
96
96
 
97
+ // DataBraid Embedding Observability
98
+ export { default as QueryEmbeddingStatus } from './query/QueryEmbeddingStatus'
99
+
97
100
  // Phase 2 Tier 2 Priority 3: Essential Query Operations
98
101
  export { default as QueryContinuId } from './query/QueryContinuId'
99
102
  export { default as QueryBatch } from './query/QueryBatch'
@@ -134,6 +137,10 @@ export { default as ResponseCreateMeta } from './response/ResponseCreateMeta'
134
137
  export { default as ResponseMetaType } from './response/ResponseMetaType'
135
138
  export { default as ResponseMetaTypeViaAtom } from './response/ResponseMetaTypeViaAtom'
136
139
 
140
+ // DataBraid Embedding Observability
141
+ export { default as ResponseEmbeddingStatus } from './response/ResponseEmbeddingStatus'
142
+ export type { EmbeddingStatusItem } from './response/ResponseEmbeddingStatus'
143
+
137
144
  // Phase 2 Tier 2 Priority 2: Token Operations
138
145
  export { default as ResponseCreateToken } from './response/ResponseCreateToken'
139
146
  export { default as ResponseRequestTokens } from './response/ResponseRequestTokens'
@@ -301,6 +301,21 @@ export default class GraphQLClient implements IGraphQLClient {
301
301
  })
302
302
  }
303
303
 
304
+ /**
305
+ * F-8a (cross-SDK parity, 2026-06-03): re-point the GraphQL-subscription WebSocket
306
+ * without changing the HTTP endpoint. `setUri` only updates `serverUri`, leaving the
307
+ * subscription socket pinned to whatever was passed at construction; this lets a
308
+ * caller re-derive the socket when the endpoint changes.
309
+ */
310
+ setSocketUri(socketUri: string): void {
311
+ this.socketUri = socketUri
312
+ this.$__client = this.createUrqlClient({
313
+ serverUri: this.serverUri,
314
+ socket: { socketUri },
315
+ encrypt: this.cipherLink
316
+ })
317
+ }
318
+
304
319
  socketDisconnect(): void {
305
320
  if (this.socketUri) {
306
321
  this.unsubscribeAll()
@@ -0,0 +1,130 @@
1
+ /*
2
+ (
3
+ (/(
4
+ (//(
5
+ (///(
6
+ (/////(
7
+ (//////( )
8
+ (////////( (/)
9
+ (////////( (///)
10
+ (//////////( (////)
11
+ (//////////( (//////)
12
+ (////////////( (///////)
13
+ (/////////////( (/////////)
14
+ (//////////////( (///////////)
15
+ (///////////////( (/////////////)
16
+ (////////////////( (//////////////)
17
+ ((((((((((((((((((( (((((((((((((((
18
+ ((((((((((((((((((( ((((((((((((((
19
+ ((((((((((((((((((( ((((((((((((((
20
+ (((((((((((((((((((( (((((((((((((
21
+ (((((((((((((((((((( ((((((((((((
22
+ ((((((((((((((((((( ((((((((((((
23
+ ((((((((((((((((((( ((((((((((
24
+ ((((((((((((((((((/ (((((((((
25
+ (((((((((((((((((( ((((((((
26
+ ((((((((((((((((( (((((((
27
+ (((((((((((((((((( (((((
28
+ ################# ##
29
+ ################ #
30
+ ################# ##
31
+ %################ ###
32
+ ###############( ####
33
+ ############### ####
34
+ ############### ######
35
+ %#############( (#######
36
+ %############# #########
37
+ ############( ##########
38
+ ########### #############
39
+ ######### ##############
40
+ %######
41
+
42
+ Powered by Knish.IO: Connecting a Decentralized World
43
+
44
+ Please visit https://github.com/WishKnish/KnishIO-Client-TS for information.
45
+
46
+ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
47
+ */
48
+
49
+ import Query from './Query'
50
+ import ResponseEmbeddingStatus from '../response/ResponseEmbeddingStatus'
51
+ import { gql } from '@urql/core'
52
+ import type { GraphQLClient } from '../types/graphql'
53
+ import type KnishIOClient from '../KnishIOClient'
54
+
55
+ /**
56
+ * QueryEmbeddingStatus - Query for retrieving DataBraid embedding status.
57
+ *
58
+ * Supports both single-instance and bulk modes:
59
+ * - Single: { metaType: 'product', metaId: 'SKU-001' }
60
+ * - Bulk: { instances: [{ metaType: 'product', metaId: 'SKU-001' }, ...] }
61
+ *
62
+ * Returns embedding state (PENDING, STALE, COMPLETE) for each instance,
63
+ * allowing apps to render spinner badges and completion indicators.
64
+ *
65
+ * NOTE: Only available on servers with EMBEDDING_ENABLED=true.
66
+ * Use KnishIOClient.queryEmbeddingStatus() which handles capability
67
+ * detection and returns null for unsupported servers.
68
+ */
69
+ export default class QueryEmbeddingStatus extends Query {
70
+ /**
71
+ * Create new QueryEmbeddingStatus instance
72
+ */
73
+ constructor(graphQLClient: GraphQLClient, knishIOClient: KnishIOClient) {
74
+ super(graphQLClient, knishIOClient)
75
+
76
+ this.$__query = gql`query( $metaType: String, $metaId: String, $instances: [EmbeddingStatusInput!] ) {
77
+ embeddingStatus( metaType: $metaType, metaId: $metaId, instances: $instances ) {
78
+ metaType,
79
+ metaId,
80
+ state,
81
+ totalMetas,
82
+ embeddedCount,
83
+ embeddedAt,
84
+ model
85
+ }
86
+ }`
87
+ }
88
+
89
+ /**
90
+ * Builds a GraphQL-friendly variables object for embedding status queries.
91
+ *
92
+ * Single mode: createVariables({ metaType: 'product', metaId: 'SKU-001' })
93
+ * Bulk mode: createVariables({ instances: [{ metaType: 'product', metaId: 'SKU-001' }, ...] })
94
+ */
95
+ static createVariables({
96
+ metaType = null,
97
+ metaId = null,
98
+ instances = null
99
+ }: {
100
+ metaType?: string | null
101
+ metaId?: string | null
102
+ instances?: Array<{ metaType: string; metaId: string }> | null
103
+ } = {}): Record<string, any> {
104
+ const variables: Record<string, any> = {}
105
+
106
+ if (instances && instances.length > 0) {
107
+ variables.instances = instances
108
+ }
109
+
110
+ if (metaType) {
111
+ variables.metaType = metaType
112
+ }
113
+
114
+ if (metaId) {
115
+ variables.metaId = metaId
116
+ }
117
+
118
+ return variables
119
+ }
120
+
121
+ /**
122
+ * Returns a Response object
123
+ */
124
+ override createResponse(json: any): ResponseEmbeddingStatus {
125
+ return new ResponseEmbeddingStatus({
126
+ query: this,
127
+ json
128
+ })
129
+ }
130
+ }
@@ -0,0 +1,107 @@
1
+ /*
2
+ (
3
+ (/(
4
+ (//(
5
+ (///(
6
+ (/////(
7
+ (//////( )
8
+ (////////( (/)
9
+ (////////( (///)
10
+ (//////////( (////)
11
+ (//////////( (//////)
12
+ (////////////( (///////)
13
+ (/////////////( (/////////)
14
+ (//////////////( (///////////)
15
+ (///////////////( (/////////////)
16
+ (////////////////( (//////////////)
17
+ ((((((((((((((((((( (((((((((((((((
18
+ ((((((((((((((((((( ((((((((((((((
19
+ ((((((((((((((((((( ((((((((((((((
20
+ (((((((((((((((((((( (((((((((((((
21
+ (((((((((((((((((((( ((((((((((((
22
+ ((((((((((((((((((( ((((((((((((
23
+ ((((((((((((((((((( ((((((((((
24
+ ((((((((((((((((((/ (((((((((
25
+ (((((((((((((((((( ((((((((
26
+ ((((((((((((((((( (((((((
27
+ (((((((((((((((((( (((((
28
+ ################# ##
29
+ ################ #
30
+ ################# ##
31
+ %################ ###
32
+ ###############( ####
33
+ ############### ####
34
+ ############### ######
35
+ %#############( (#######
36
+ %############# #########
37
+ ############( ##########
38
+ ########### #############
39
+ ######### ##############
40
+ %######
41
+
42
+ Powered by Knish.IO: Connecting a Decentralized World
43
+
44
+ Please visit https://github.com/WishKnish/KnishIO-Client-TS for information.
45
+
46
+ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
47
+ */
48
+
49
+ import type Query from '../query/Query'
50
+ import Response from './Response'
51
+
52
+ /**
53
+ * EmbeddingStatusItem shape returned by the server.
54
+ */
55
+ export interface EmbeddingStatusItem {
56
+ /** Metadata type (echoed from input) */
57
+ metaType: string
58
+ /** Instance identifier (echoed from input) */
59
+ metaId: string
60
+ /** Embedding state: 'PENDING' | 'STALE' | 'COMPLETE' */
61
+ state: 'PENDING' | 'STALE' | 'COMPLETE'
62
+ /** Total meta rows for this instance */
63
+ totalMetas: number
64
+ /** Rows with current-model embeddings */
65
+ embeddedCount: number
66
+ /** Unix epoch of most recent embedding, or null */
67
+ embeddedAt: number | null
68
+ /** Model name used for embeddings, or null */
69
+ model: string | null
70
+ }
71
+
72
+ /**
73
+ * ResponseEmbeddingStatus - Response for EmbeddingStatus Query
74
+ *
75
+ * Payload is an array of EmbeddingStatusItem objects.
76
+ */
77
+ export default class ResponseEmbeddingStatus extends Response {
78
+ /**
79
+ * Class constructor
80
+ */
81
+ constructor({
82
+ query,
83
+ json
84
+ }: {
85
+ query: Query
86
+ json: any
87
+ }) {
88
+ super({
89
+ query,
90
+ json,
91
+ dataKey: 'data.embeddingStatus'
92
+ })
93
+ }
94
+
95
+ /**
96
+ * Returns the array of embedding status items, or null if empty.
97
+ */
98
+ override payload(): EmbeddingStatusItem[] | null {
99
+ const items = this.data()
100
+
101
+ if (!items || !Array.isArray(items) || items.length === 0) {
102
+ return null
103
+ }
104
+
105
+ return items as EmbeddingStatusItem[]
106
+ }
107
+ }
@@ -50,6 +50,10 @@ import Response from './Response'
50
50
  import Dot from '../libraries/Dot'
51
51
  import Molecule from '../core/Molecule'
52
52
  import type Query from '../query/Query'
53
+ import type BaseException from '../exception/BaseException'
54
+ import MolecularHashMismatchException from '../exception/MolecularHashMismatchException'
55
+ import SignatureMismatchException from '../exception/SignatureMismatchException'
56
+ import AtomIndexException from '../exception/AtomIndexException'
53
57
 
54
58
  /**
55
59
  * Response for proposing new Molecules
@@ -143,6 +147,49 @@ export default class ResponseProposeMolecule extends Response {
143
147
  return Dot.get(this.data(), 'reason', 'Invalid response from server') as string
144
148
  }
145
149
 
150
+ /**
151
+ * Map this rejection to a typed SDK exception when the failure mode is one
152
+ * we know about. Returns null on success or for rejections we don't have a
153
+ * typed class for.
154
+ *
155
+ * This is the single place pattern-matching against validator reason strings
156
+ * lives — consumers (KnishIOClient cache invalidation, callers needing to
157
+ * branch on failure mode) can `instanceof`-switch on the result instead.
158
+ * Future validator versions can rephrase reasons (or populate a structured
159
+ * field in the response payload) without breaking callers.
160
+ */
161
+ toException(): BaseException | null {
162
+ if (this.success()) return null
163
+ const reason = this.reason()
164
+ const lc = reason.toLowerCase()
165
+
166
+ if (/molecularhashmismatch|hash.*mismatch/.test(lc)) {
167
+ return new MolecularHashMismatchException(reason, {
168
+ details: { reason },
169
+ code: 'HASH_MISMATCH'
170
+ })
171
+ }
172
+ if (/ots.*position.*reuse|position.*already.*used|ots.*verification/.test(lc)) {
173
+ return new SignatureMismatchException(reason, {
174
+ details: { reason },
175
+ code: 'OTS_VERIFICATION_FAILED'
176
+ })
177
+ }
178
+ if (/continuid.*chain|previousposition|chain.*violation/.test(lc)) {
179
+ return new AtomIndexException(reason, {
180
+ details: { reason },
181
+ code: 'INDEX_CONFLICT'
182
+ })
183
+ }
184
+ if (/signature.*verification|signature.*invalid/.test(lc)) {
185
+ return new SignatureMismatchException(reason, {
186
+ details: { reason },
187
+ code: 'VERIFICATION_FAILED'
188
+ })
189
+ }
190
+ return null
191
+ }
192
+
146
193
  /**
147
194
  * Returns payload object
148
195
  * Matches JavaScript SDK payload method exactly