@wishknish/knishio-client-ts 0.9.5 → 0.9.7
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/index.cjs +847 -240
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -29
- package/dist/index.d.ts +154 -29
- package/dist/index.iife.js +1340 -452
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +840 -241
- package/dist/index.js.map +1 -1
- package/package.json +18 -21
- package/src/KnishIOClient.ts +100 -10
- package/src/core/AtomMeta.ts +12 -4
- package/src/core/Molecule.ts +61 -0
- package/src/core/Wallet.ts +7 -5
- package/src/exception/SecretStorageException.ts +97 -0
- package/src/exception/index.ts +1 -0
- package/src/index.ts +28 -1
- package/src/libraries/GraphQLClient.ts +21 -7
- package/src/libraries/UrqlClientWrapper.ts +8 -3
- package/src/libraries/crypto.ts +4 -6
- package/src/libraries/secureMemory.ts +121 -0
- package/src/mutation/MutationReplenishToken.ts +28 -0
- package/src/schemas/index.ts +3 -2
- package/src/storage/MemorySecretStorageProvider.ts +156 -0
- package/src/storage/WebCryptoSecretStorageProvider.ts +407 -0
- package/src/storage/index.ts +87 -0
- package/src/types/assertions.ts +2 -2
- package/src/types/index.ts +1 -1
- package/src/types/storage.ts +129 -0
- package/src/validation/schemas.ts +9 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wishknish/knishio-client-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
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",
|
|
@@ -58,32 +58,30 @@
|
|
|
58
58
|
"fips-203"
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@noble/post-quantum": "^0.
|
|
62
|
-
"@
|
|
63
|
-
"
|
|
64
|
-
"graphql": "^
|
|
65
|
-
"
|
|
66
|
-
"isomorphic-fetch": "^3.0.0",
|
|
67
|
-
"jssha": "^3.3.1",
|
|
61
|
+
"@noble/post-quantum": "^0.7.0",
|
|
62
|
+
"@urql/core": "^6.0.3",
|
|
63
|
+
"graphql": "^16.14.2",
|
|
64
|
+
"graphql-ws": "^6.2.1",
|
|
65
|
+
"jssha": "^3.3.2",
|
|
68
66
|
"tslib": "^2.8.1",
|
|
69
|
-
"wonka": "^6.3.
|
|
67
|
+
"wonka": "^6.3.6",
|
|
70
68
|
"zod": "^4.5.4"
|
|
71
69
|
},
|
|
72
70
|
"devDependencies": {
|
|
73
|
-
"@types/isomorphic-fetch": "^0.0.39",
|
|
74
71
|
"@types/jssha": "^3.0.0",
|
|
75
|
-
"@types/node": "^
|
|
76
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
77
|
-
"@typescript-eslint/parser": "^8.
|
|
78
|
-
"@vitest/coverage-v8": "^4.1.
|
|
79
|
-
"eslint": "^
|
|
80
|
-
"prettier": "^3",
|
|
72
|
+
"@types/node": "^26",
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
|
74
|
+
"@typescript-eslint/parser": "^8.67.0",
|
|
75
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
76
|
+
"eslint": "^10",
|
|
77
|
+
"prettier": "^3.9.6",
|
|
81
78
|
"tsup": "^8.5.1",
|
|
82
|
-
"tsx": "^4.23.
|
|
79
|
+
"tsx": "^4.23.12",
|
|
83
80
|
"typescript": "^5",
|
|
84
|
-
"
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
"vitest": "^4.1.11"
|
|
82
|
+
},
|
|
83
|
+
"resolutions": {
|
|
84
|
+
"wonka": "6.3.6"
|
|
87
85
|
},
|
|
88
86
|
"browserslist": [
|
|
89
87
|
"> 1%",
|
|
@@ -103,7 +101,6 @@
|
|
|
103
101
|
"test:watch": "vitest",
|
|
104
102
|
"test:coverage": "vitest run --coverage",
|
|
105
103
|
"test:ui": "vitest --ui",
|
|
106
|
-
"dev": "vite",
|
|
107
104
|
"prepublishOnly": "npm run build",
|
|
108
105
|
"selftest": "npm run build && node self-test.cjs"
|
|
109
106
|
},
|
package/src/KnishIOClient.ts
CHANGED
|
@@ -104,6 +104,7 @@ import MutationLinkIdentifier from '@/mutation/MutationLinkIdentifier'
|
|
|
104
104
|
import MutationPeering from '@/mutation/MutationPeering'
|
|
105
105
|
import MutationAppendRequest from '@/mutation/MutationAppendRequest'
|
|
106
106
|
import MutationProposeMolecule from '@/mutation/MutationProposeMolecule'
|
|
107
|
+
import MutationReplenishToken from '@/mutation/MutationReplenishToken'
|
|
107
108
|
|
|
108
109
|
// Response imports
|
|
109
110
|
import ResponseProposeMolecule from '@/response/ResponseProposeMolecule'
|
|
@@ -127,6 +128,8 @@ import {
|
|
|
127
128
|
SignatureMismatchException,
|
|
128
129
|
AtomIndexException
|
|
129
130
|
} from '@/exception'
|
|
131
|
+
import type { ISecretStorageProvider } from '@/types/storage'
|
|
132
|
+
import MemorySecretStorageProvider from '@/storage/MemorySecretStorageProvider'
|
|
130
133
|
|
|
131
134
|
// Type imports
|
|
132
135
|
import type {
|
|
@@ -149,6 +152,7 @@ import type {
|
|
|
149
152
|
export default class KnishIOClient {
|
|
150
153
|
private $__secret: string = ''
|
|
151
154
|
private $__bundle: string = ''
|
|
155
|
+
private $__secretStorage: ISecretStorageProvider | null = null
|
|
152
156
|
private $__cellSlug: string | null = null
|
|
153
157
|
private $__encrypt: boolean = false
|
|
154
158
|
private $__uris: string[] = []
|
|
@@ -184,6 +188,7 @@ export default class KnishIOClient {
|
|
|
184
188
|
serverSdkVersion?: number
|
|
185
189
|
logging?: boolean
|
|
186
190
|
defaultRequestPolicy?: RequestPolicy | null
|
|
191
|
+
secretStorage?: ISecretStorageProvider | null
|
|
187
192
|
}) {
|
|
188
193
|
// Phase 2 Enhancement: Use standardized configuration validation
|
|
189
194
|
const standardValidationResult = ConfigValidator.validateClientConfig(config)
|
|
@@ -235,6 +240,10 @@ export default class KnishIOClient {
|
|
|
235
240
|
logging,
|
|
236
241
|
defaultRequestPolicy
|
|
237
242
|
})
|
|
243
|
+
|
|
244
|
+
if (config.secretStorage) {
|
|
245
|
+
this.$__secretStorage = config.secretStorage
|
|
246
|
+
}
|
|
238
247
|
}
|
|
239
248
|
|
|
240
249
|
/**
|
|
@@ -364,6 +373,7 @@ export default class KnishIOClient {
|
|
|
364
373
|
reset(): void {
|
|
365
374
|
this.$__secret = ''
|
|
366
375
|
this.$__bundle = ''
|
|
376
|
+
this.$__secretStorage = null
|
|
367
377
|
this.$__encrypt = false
|
|
368
378
|
this.$__cellSlug = null
|
|
369
379
|
this.$__authToken = null
|
|
@@ -433,7 +443,7 @@ export default class KnishIOClient {
|
|
|
433
443
|
* Returns whether a secret is stored for this session
|
|
434
444
|
*/
|
|
435
445
|
hasSecret(): boolean {
|
|
436
|
-
return !!this.$__secret && this.$__secret.length > 0
|
|
446
|
+
return (!!this.$__secret && this.$__secret.length > 0) || (!!this.$__secretStorage && !!this.$__bundle && this.$__bundle.length > 0)
|
|
437
447
|
}
|
|
438
448
|
|
|
439
449
|
/**
|
|
@@ -446,6 +456,36 @@ export default class KnishIOClient {
|
|
|
446
456
|
return this.$__secret
|
|
447
457
|
}
|
|
448
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Sets the secret storage provider and optionally sets the bundle hash
|
|
461
|
+
*/
|
|
462
|
+
setSecretStorage(storage: ISecretStorageProvider, bundleHash?: string): void {
|
|
463
|
+
this.$__secretStorage = storage
|
|
464
|
+
if (bundleHash) {
|
|
465
|
+
this.$__bundle = bundleHash
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Returns current secret storage provider
|
|
471
|
+
*/
|
|
472
|
+
getSecretStorage(): ISecretStorageProvider | null {
|
|
473
|
+
return this.$__secretStorage
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Asynchronously retrieves the secret from storage or returns in-memory secret
|
|
478
|
+
*/
|
|
479
|
+
async retrieveSecret(options?: { passphrase?: string }): Promise<string | null> {
|
|
480
|
+
if (this.$__secret && this.$__secret.length > 0) {
|
|
481
|
+
return this.$__secret
|
|
482
|
+
}
|
|
483
|
+
if (this.$__secretStorage && this.$__bundle && this.$__bundle.length > 0) {
|
|
484
|
+
return await this.$__secretStorage.retrieveSecret(this.$__bundle, options)
|
|
485
|
+
}
|
|
486
|
+
return null
|
|
487
|
+
}
|
|
488
|
+
|
|
449
489
|
/**
|
|
450
490
|
* Returns whether a bundle hash is being stored for this session
|
|
451
491
|
*/
|
|
@@ -494,6 +534,14 @@ export default class KnishIOClient {
|
|
|
494
534
|
} = {}): Promise<Molecule> {
|
|
495
535
|
this.log('info', 'KnishIOClient::createMolecule() - Creating a new molecule...')
|
|
496
536
|
|
|
537
|
+
if (!secret) {
|
|
538
|
+
if (this.$__secret && this.$__secret.length > 0) {
|
|
539
|
+
secret = this.getSecret()
|
|
540
|
+
} else if (this.$__secretStorage && this.$__bundle && this.$__bundle.length > 0) {
|
|
541
|
+
secret = await this.$__secretStorage.retrieveSecret(this.$__bundle)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
497
545
|
secret = secret || this.getSecret()
|
|
498
546
|
bundle = bundle || this.getBundle()
|
|
499
547
|
|
|
@@ -546,6 +594,7 @@ export default class KnishIOClient {
|
|
|
546
594
|
|
|
547
595
|
return new Molecule({
|
|
548
596
|
secret,
|
|
597
|
+
bundle,
|
|
549
598
|
sourceWallet,
|
|
550
599
|
remainderWallet: this.getRemainderWallet()!,
|
|
551
600
|
cellSlug: this.getCellSlug(),
|
|
@@ -611,8 +660,9 @@ export default class KnishIOClient {
|
|
|
611
660
|
// Guard with $__authInProcess to prevent recursive auth refresh
|
|
612
661
|
if (this.$__authToken && this.$__authToken.isExpired() && !this.$__authInProcess) {
|
|
613
662
|
this.log('info', 'KnishIOClient::executeQuery() - Access token is expired. Getting new one...')
|
|
663
|
+
const authSecret = this.$__secret || (await this.retrieveSecret()) || ''
|
|
614
664
|
await this.requestAuthToken({
|
|
615
|
-
secret:
|
|
665
|
+
secret: authSecret,
|
|
616
666
|
cellSlug: this.$__cellSlug,
|
|
617
667
|
encrypt: this.$__encrypt
|
|
618
668
|
})
|
|
@@ -672,6 +722,13 @@ export default class KnishIOClient {
|
|
|
672
722
|
setSecret(secret: string): void {
|
|
673
723
|
this.$__secret = secret
|
|
674
724
|
this.$__bundle = generateBundleHash(secret)
|
|
725
|
+
if (!this.$__secretStorage) {
|
|
726
|
+
const memStorage = new MemorySecretStorageProvider()
|
|
727
|
+
memStorage.storeSecret(this.$__bundle, secret)
|
|
728
|
+
this.$__secretStorage = memStorage
|
|
729
|
+
} else {
|
|
730
|
+
this.$__secretStorage.storeSecret(this.$__bundle, secret)
|
|
731
|
+
}
|
|
675
732
|
}
|
|
676
733
|
|
|
677
734
|
/**
|
|
@@ -923,6 +980,11 @@ export default class KnishIOClient {
|
|
|
923
980
|
secret = generateSecret(seed)
|
|
924
981
|
}
|
|
925
982
|
|
|
983
|
+
// Retrieve secret from storage provider if available
|
|
984
|
+
if (secret === null && this.$__secretStorage && this.$__bundle) {
|
|
985
|
+
secret = await this.$__secretStorage.retrieveSecret(this.$__bundle)
|
|
986
|
+
}
|
|
987
|
+
|
|
926
988
|
// Set cell slug if provided
|
|
927
989
|
if (cellSlug) {
|
|
928
990
|
this.setCellSlug(cellSlug)
|
|
@@ -1836,27 +1898,55 @@ export default class KnishIOClient {
|
|
|
1836
1898
|
}
|
|
1837
1899
|
|
|
1838
1900
|
/**
|
|
1839
|
-
* Replenish
|
|
1901
|
+
* Replenish a non-finite token supply.
|
|
1902
|
+
* Matches JS SDK KnishIOClient.replenishToken (KnishIOClient.js:2195-2231).
|
|
1840
1903
|
*/
|
|
1841
1904
|
async replenishToken({
|
|
1842
1905
|
token,
|
|
1843
1906
|
amount = null,
|
|
1844
1907
|
units = null,
|
|
1845
|
-
sourceWallet
|
|
1908
|
+
sourceWallet = null
|
|
1846
1909
|
}: {
|
|
1847
1910
|
token: TokenSlug | string
|
|
1848
1911
|
amount?: number | string | null
|
|
1849
|
-
units?: string
|
|
1912
|
+
units?: Array<[string, string, Record<string, any>?]> | null
|
|
1850
1913
|
sourceWallet?: Wallet | null
|
|
1851
1914
|
}): Promise<Response> {
|
|
1852
1915
|
this.log('info', `KnishIOClient::replenishToken() - Replenishing ${amount || 'units'} of ${token}...`)
|
|
1853
1916
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1917
|
+
if (!sourceWallet) {
|
|
1918
|
+
sourceWallet = (await this.queryBalance({ token }))?.payload() as Wallet | null
|
|
1919
|
+
}
|
|
1920
|
+
if (!sourceWallet) {
|
|
1921
|
+
throw new TransferBalanceException('Source wallet is missing or invalid.')
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1924
|
+
// Remainder wallet (same token, so the V-isotope pair conserves)
|
|
1925
|
+
const remainderWallet = sourceWallet.createRemainder(this.getSecret())
|
|
1926
|
+
|
|
1927
|
+
const molecule = await this.createMolecule({
|
|
1928
|
+
sourceWallet,
|
|
1929
|
+
remainderWallet
|
|
1859
1930
|
})
|
|
1931
|
+
molecule.replenishToken({
|
|
1932
|
+
amount: Number(amount ?? 0),
|
|
1933
|
+
units: units ?? []
|
|
1934
|
+
})
|
|
1935
|
+
molecule.sign({ bundle: this.getBundle() })
|
|
1936
|
+
molecule.check()
|
|
1937
|
+
|
|
1938
|
+
const mutation = await this.createMoleculeMutation({
|
|
1939
|
+
mutationClass: MutationReplenishToken,
|
|
1940
|
+
molecule
|
|
1941
|
+
})
|
|
1942
|
+
|
|
1943
|
+
const response = await this.executeQuery(mutation)
|
|
1944
|
+
|
|
1945
|
+
if (!response) {
|
|
1946
|
+
throw new CodeException('Token replenishment failed')
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
return response
|
|
1860
1950
|
}
|
|
1861
1951
|
|
|
1862
1952
|
/**
|
package/src/core/AtomMeta.ts
CHANGED
|
@@ -47,6 +47,7 @@ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
|
|
|
47
47
|
*/
|
|
48
48
|
|
|
49
49
|
import Meta, { type NormalizedMeta } from './Meta'
|
|
50
|
+
import PolicyMeta from './PolicyMeta'
|
|
50
51
|
|
|
51
52
|
// Configuration constants matching JS SDK
|
|
52
53
|
const USE_META_CONTEXT = false
|
|
@@ -172,12 +173,19 @@ export default class AtomMeta {
|
|
|
172
173
|
* @return This instance for chaining
|
|
173
174
|
*/
|
|
174
175
|
addPolicy(policy: Record<string, any>): AtomMeta {
|
|
175
|
-
//
|
|
176
|
-
//
|
|
176
|
+
// Normalise through PolicyMeta before serialising. PolicyMeta.fillDefault adds a default
|
|
177
|
+
// entry for every meta key not already covered by the caller's policy, so the stored string
|
|
178
|
+
// carries materially more than the input — and this string is hashed into the atom, so a
|
|
179
|
+
// raw JSON.stringify(policy) diverges from every other SDK.
|
|
180
|
+
// Matches JS SDK AtomMeta.addPolicy (AtomMeta.js:170-175), including passing
|
|
181
|
+
// Object.keys(this.meta): `meta` is a NormalizedMeta[] in both SDKs, so these are array
|
|
182
|
+
// indices rather than meta key names. Faithful to the JS behaviour — do not "correct" it.
|
|
183
|
+
const policyMeta = new PolicyMeta(policy, Object.keys(this.meta))
|
|
184
|
+
|
|
177
185
|
this.merge({
|
|
178
|
-
policy:
|
|
186
|
+
policy: policyMeta.toJson()
|
|
179
187
|
})
|
|
180
|
-
|
|
188
|
+
|
|
181
189
|
return this
|
|
182
190
|
}
|
|
183
191
|
|
package/src/core/Molecule.ts
CHANGED
|
@@ -755,6 +755,67 @@ export default class Molecule {
|
|
|
755
755
|
return this
|
|
756
756
|
}
|
|
757
757
|
|
|
758
|
+
/**
|
|
759
|
+
* Replenishes non-finite token supplies.
|
|
760
|
+
* Matches JS SDK Molecule.replenishToken (Molecule.js:521-566) exactly.
|
|
761
|
+
*
|
|
762
|
+
* Two orderings here are load-bearing for the molecular hash and must not be reordered:
|
|
763
|
+
* the remainder balance is computed BEFORE the source balance is overwritten, and the
|
|
764
|
+
* source V-atom is added BEFORE the remainder V-atom.
|
|
765
|
+
*/
|
|
766
|
+
replenishToken({
|
|
767
|
+
amount,
|
|
768
|
+
units = []
|
|
769
|
+
}: {
|
|
770
|
+
amount: number
|
|
771
|
+
units?: Array<[string, string, Record<string, any>?]>
|
|
772
|
+
}): Molecule {
|
|
773
|
+
if (amount < 0) {
|
|
774
|
+
throw new NegativeAmountException('Molecule::replenishToken() - Amount to replenish must be positive!')
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
if (!this.sourceWallet || !this.remainderWallet) {
|
|
778
|
+
throw new Error('Source and remainder wallets required for token replenishment')
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (units.length) {
|
|
782
|
+
// Prepare token units to formatted style
|
|
783
|
+
const formatted = Wallet.getTokenUnits(units)
|
|
784
|
+
|
|
785
|
+
// Merge token units with source wallet & new items
|
|
786
|
+
this.remainderWallet.tokenUnits = this.sourceWallet.tokenUnits
|
|
787
|
+
for (const unit of formatted) {
|
|
788
|
+
this.remainderWallet.tokenUnits.push(unit)
|
|
789
|
+
}
|
|
790
|
+
this.remainderWallet.balance = String(this.remainderWallet.tokenUnits.length)
|
|
791
|
+
|
|
792
|
+
// Override first atom's token units to replenish values
|
|
793
|
+
this.sourceWallet.tokenUnits = formatted
|
|
794
|
+
this.sourceWallet.balance = String(this.sourceWallet.tokenUnits.length)
|
|
795
|
+
} else {
|
|
796
|
+
// Update wallet's balances
|
|
797
|
+
this.remainderWallet.balance = String(Number(this.sourceWallet.balance) + amount)
|
|
798
|
+
this.sourceWallet.balance = String(amount)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Initializing a new Atom to remove tokens from source
|
|
802
|
+
this.addAtom(Atom.create({
|
|
803
|
+
isotope: 'V',
|
|
804
|
+
wallet: this.sourceWallet,
|
|
805
|
+
value: Number(this.sourceWallet.balance)
|
|
806
|
+
}))
|
|
807
|
+
|
|
808
|
+
this.addAtom(Atom.create({
|
|
809
|
+
isotope: 'V',
|
|
810
|
+
wallet: this.remainderWallet,
|
|
811
|
+
value: Number(this.remainderWallet.balance),
|
|
812
|
+
metaType: 'walletBundle',
|
|
813
|
+
metaId: this.remainderWallet.bundle!
|
|
814
|
+
}))
|
|
815
|
+
|
|
816
|
+
return this
|
|
817
|
+
}
|
|
818
|
+
|
|
758
819
|
/**
|
|
759
820
|
* Initialize authorization request
|
|
760
821
|
* Creates U-isotope (authorization) atom for requesting auth token
|
package/src/core/Wallet.ts
CHANGED
|
@@ -51,6 +51,7 @@ import { randomString, chunkSubstr, isHex } from '@/libraries/strings'
|
|
|
51
51
|
import { generateBundleHash, generateSecret, shake256, generateBatchId } from '@/libraries/crypto'
|
|
52
52
|
import WalletCredentialException from '@/exception/WalletCredentialException'
|
|
53
53
|
import { isBundleHash } from '@/types'
|
|
54
|
+
import TokenUnit from '@/core/TokenUnit'
|
|
54
55
|
// Post-quantum cryptography for ML-KEM768 key encapsulation
|
|
55
56
|
import { ml_kem768 } from '@noble/post-quantum/ml-kem.js'
|
|
56
57
|
|
|
@@ -271,12 +272,13 @@ export default class Wallet {
|
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
/**
|
|
274
|
-
*
|
|
275
|
-
*
|
|
275
|
+
* Map raw token-unit tuples to TokenUnit instances.
|
|
276
|
+
* Matches JS SDK Wallet.getTokenUnits (Wallet.js:190-196). The serialised shape reaches hashed
|
|
277
|
+
* atom meta via AtomMeta.setAtomWallet -> JSON.stringify(getTokenUnitsData()), so returning raw
|
|
278
|
+
* tuples here would diverge from every other SDK.
|
|
276
279
|
*/
|
|
277
|
-
static getTokenUnits(unitsData: any
|
|
278
|
-
|
|
279
|
-
return unitsData
|
|
280
|
+
static getTokenUnits(unitsData: Array<[string, string, Record<string, any>?]>): TokenUnit[] {
|
|
281
|
+
return unitsData.map(unitData => TokenUnit.createFromDB(unitData))
|
|
280
282
|
}
|
|
281
283
|
|
|
282
284
|
/**
|
|
@@ -0,0 +1,97 @@
|
|
|
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 BaseException, { type BaseExceptionOptions } from './BaseException'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Exception thrown when a secret storage or hardware envelope encryption operation fails
|
|
53
|
+
*/
|
|
54
|
+
export default class SecretStorageException extends BaseException {
|
|
55
|
+
constructor(message = 'Secret storage operation failed', options: BaseExceptionOptions = {}) {
|
|
56
|
+
super('WALLET_CREDENTIAL_ERROR', message, {
|
|
57
|
+
code: 'SECRET_STORAGE_ERROR',
|
|
58
|
+
...options
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Secret not found for the requested bundle hash
|
|
64
|
+
*/
|
|
65
|
+
static notFound(bundleHash: string): SecretStorageException {
|
|
66
|
+
return new SecretStorageException(`Secret not found for bundle: ${bundleHash}`, {
|
|
67
|
+
code: 'SECRET_NOT_FOUND',
|
|
68
|
+
details: { bundleHash }
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Decryption failed (wrong passphrase or corrupted payload)
|
|
74
|
+
*/
|
|
75
|
+
static decryptionFailed(reason?: string): SecretStorageException {
|
|
76
|
+
return new SecretStorageException(
|
|
77
|
+
`Failed to decrypt master secret: ${reason || 'Invalid passphrase or corrupted ciphertext'}`,
|
|
78
|
+
{
|
|
79
|
+
code: 'DECRYPTION_FAILED',
|
|
80
|
+
details: { reason }
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Provider is unavailable in current platform
|
|
87
|
+
*/
|
|
88
|
+
static unavailable(provider: string, reason?: string): SecretStorageException {
|
|
89
|
+
return new SecretStorageException(
|
|
90
|
+
`Secret storage provider '${provider}' is unavailable: ${reason || 'Hardware or API not accessible'}`,
|
|
91
|
+
{
|
|
92
|
+
code: 'STORAGE_UNAVAILABLE',
|
|
93
|
+
details: { provider, reason }
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
package/src/exception/index.ts
CHANGED
|
@@ -62,6 +62,7 @@ export { default as MolecularHashMismatchException } from './MolecularHashMismat
|
|
|
62
62
|
export { default as SignatureMismatchException } from './SignatureMismatchException'
|
|
63
63
|
export { default as TransferBalanceException } from './TransferBalanceException'
|
|
64
64
|
export { default as WalletCredentialException } from './WalletCredentialException'
|
|
65
|
+
export { default as SecretStorageException } from './SecretStorageException'
|
|
65
66
|
export { default as InvalidResponseException } from './InvalidResponseException'
|
|
66
67
|
|
|
67
68
|
// Additional exceptions (now implemented)
|
package/src/index.ts
CHANGED
|
@@ -261,12 +261,39 @@ export {
|
|
|
261
261
|
SignatureMismatchException,
|
|
262
262
|
TransferBalanceException,
|
|
263
263
|
WalletCredentialException,
|
|
264
|
+
SecretStorageException,
|
|
264
265
|
InvalidResponseException,
|
|
265
266
|
ExceptionFactory,
|
|
266
267
|
EXCEPTION_TYPES,
|
|
267
268
|
EXCEPTION_CODES
|
|
268
269
|
} from './exception'
|
|
269
270
|
|
|
271
|
+
// =============================================================================
|
|
272
|
+
// SECRET STORAGE & HARDWARE ENVELOPE ENCRYPTION
|
|
273
|
+
// =============================================================================
|
|
274
|
+
|
|
275
|
+
export {
|
|
276
|
+
MemorySecretStorageProvider,
|
|
277
|
+
WebCryptoSecretStorageProvider,
|
|
278
|
+
MemoryStorageBackend,
|
|
279
|
+
createDefaultSecretStorage,
|
|
280
|
+
type IStorageBackend,
|
|
281
|
+
type CreateSecretStorageOptions
|
|
282
|
+
} from './storage'
|
|
283
|
+
|
|
284
|
+
export {
|
|
285
|
+
zeroizeBytes,
|
|
286
|
+
withSecureBytes,
|
|
287
|
+
withSecureString,
|
|
288
|
+
constantTimeCompare
|
|
289
|
+
} from './libraries/secureMemory'
|
|
290
|
+
|
|
291
|
+
export type {
|
|
292
|
+
SecretStorageMetadata,
|
|
293
|
+
EncryptedSecretPayload,
|
|
294
|
+
ISecretStorageProvider
|
|
295
|
+
} from './types/storage'
|
|
296
|
+
|
|
270
297
|
// =============================================================================
|
|
271
298
|
// TYPE DEFINITIONS
|
|
272
299
|
// =============================================================================
|
|
@@ -362,7 +389,7 @@ export {
|
|
|
362
389
|
// MUST equal package.json's "version". The CI `version consistency` job enforces this via
|
|
363
390
|
// .github/scripts/check-version.sh, which parses this exact line — keep the literal form
|
|
364
391
|
// `export const SDK_VERSION = '<semver>'` intact so the gate can read it.
|
|
365
|
-
export const SDK_VERSION = '0.9.
|
|
392
|
+
export const SDK_VERSION = '0.9.7'
|
|
366
393
|
export const SDK_NAME = 'KnishIO-Client-TS'
|
|
367
394
|
export const COMPATIBLE_SERVER_VERSIONS = [4, 5]
|
|
368
395
|
|
|
@@ -141,16 +141,30 @@ export default class GraphQLClient implements IGraphQLClient {
|
|
|
141
141
|
return createClient({
|
|
142
142
|
url: serverUri,
|
|
143
143
|
exchanges,
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
|
|
144
|
+
// urql 5 had no default and always POSTed; urql 6 defaults to 'within-url-limit', which
|
|
145
|
+
// URL-encodes short queries and sends NO body. That would silently disable the CipherHash
|
|
146
|
+
// envelope below — cipherFetch's `typeof init.body === 'string'` guard fails on a GET, so
|
|
147
|
+
// the query would leave as plaintext URL parameters with no error. Pin POST explicitly.
|
|
148
|
+
preferGetMethod: false,
|
|
149
|
+
// Always route through our own fetch. Two reasons: (1) PQ-transport Phase E — when
|
|
150
|
+
// encryption is on, cipherFetch wraps the request body in the CipherHash envelope and
|
|
151
|
+
// decrypts the response; (2) the 60s timeout. urql's makeFetchSource unconditionally
|
|
152
|
+
// overwrites init.signal with its own AbortController, so a signal returned from
|
|
153
|
+
// fetchOptions() is discarded and never fired. Combining it here is the only place it
|
|
154
|
+
// survives.
|
|
155
|
+
fetch: ((input: RequestInfo | URL, init?: RequestInit) => {
|
|
156
|
+
const timeoutSignal = AbortSignal.timeout(60000)
|
|
157
|
+
// Keep urql's own signal so teardown still aborts; AbortSignal.any needs Node >= 18.17.
|
|
158
|
+
const signal = init?.signal && typeof AbortSignal.any === 'function'
|
|
159
|
+
? AbortSignal.any([init.signal, timeoutSignal])
|
|
160
|
+
: (init?.signal ?? timeoutSignal)
|
|
161
|
+
const timedInit: RequestInit = { ...init, signal }
|
|
162
|
+
return this.cipherLink ? this.cipherFetch(input, timedInit) : fetch(input, timedInit)
|
|
163
|
+
}) as typeof fetch,
|
|
148
164
|
fetchOptions: () => ({
|
|
149
165
|
headers: {
|
|
150
166
|
'X-Auth-Token': this.$__authToken
|
|
151
|
-
}
|
|
152
|
-
// Add 60 second timeout
|
|
153
|
-
signal: AbortSignal.timeout(60000)
|
|
167
|
+
}
|
|
154
168
|
})
|
|
155
169
|
})
|
|
156
170
|
}
|
|
@@ -155,12 +155,17 @@ class UrqlClientWrapper {
|
|
|
155
155
|
return createClient({
|
|
156
156
|
url: serverUri,
|
|
157
157
|
exchanges,
|
|
158
|
+
// urql 6 defaults preferGetMethod to 'within-url-limit', sending short queries as GET with
|
|
159
|
+
// no body. Keep the urql-5 behaviour (always POST) so this client matches GraphQLClient and
|
|
160
|
+
// cannot silently change the wire format if it is ever put back into use.
|
|
161
|
+
preferGetMethod: false,
|
|
162
|
+
// No `signal` here: urql's makeFetchSource overwrites init.signal with its own
|
|
163
|
+
// AbortController, so a timeout passed through fetchOptions() is silently discarded and
|
|
164
|
+
// never fires. GraphQLClient applies its 60s timeout inside a wrapping `fetch` instead.
|
|
158
165
|
fetchOptions: () => ({
|
|
159
166
|
headers: {
|
|
160
167
|
'X-Auth-Token': this.$__authToken
|
|
161
|
-
}
|
|
162
|
-
// Add 60 second timeout for debugging
|
|
163
|
-
signal: AbortSignal.timeout(60000)
|
|
168
|
+
}
|
|
164
169
|
})
|
|
165
170
|
})
|
|
166
171
|
}
|
package/src/libraries/crypto.ts
CHANGED
|
@@ -659,13 +659,11 @@ export function verifyOTSSignature(
|
|
|
659
659
|
const enumerated = enumerateMolecularHash(base17Hash)
|
|
660
660
|
const normalized = normalizeMolecularHash(enumerated)
|
|
661
661
|
|
|
662
|
-
// Step 2:
|
|
663
|
-
|
|
662
|
+
// Step 2: Fragments must be the uncompressed 2048-character form. Base64-compressed
|
|
663
|
+
// signatures are not supported and are rejected here rather than expanded.
|
|
664
|
+
const ots = otsFragments
|
|
664
665
|
if (ots.length !== 2048) {
|
|
665
|
-
|
|
666
|
-
if (ots.length !== 2048) {
|
|
667
|
-
return false // Invalid signature length
|
|
668
|
-
}
|
|
666
|
+
return false
|
|
669
667
|
}
|
|
670
668
|
|
|
671
669
|
// Step 3: Subdivide OTS into 16 segments of 128 characters each
|