edge-core-js 2.47.0 → 2.48.0

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/lib/util/nym.js CHANGED
@@ -1,27 +1,37 @@
1
1
  import {
2
2
  createMixFetch,
3
- disconnectMixTunnel,
3
+ disconnectMixFetch,
4
4
 
5
- } from '@nymproject/mix-fetch'
6
5
 
7
6
 
7
+ } from '@nymproject/mix-fetch'
8
8
 
9
- /** The fetch-bound function `createMixFetch` resolves to. */
10
9
 
11
10
 
12
11
  /**
13
- * Configuration options for the NYM mixFetch tunnel.
12
+ * Configuration options for the NYM mixFetch client.
14
13
  */
15
14
  export const mixFetchOptions = {
16
15
  clientId: 'edge-core-js-2026-03-10',
16
+ preferredGateway: '5rXcNe2a44vXisK3uqLHCzpzvEwcnsijDMU7hg4fcYk8', // with WSS
17
+ preferredNetworkRequester:
18
+ '5x6q9UfVHs5AohKMUqeivj7a556kVVy7QwoKige8xHxh.6CFoB3kJaDbYz6oafPJxNxNjzahpT2NtgtytcSyN9EvF@5rXcNe2a44vXisK3uqLHCzpzvEwcnsijDMU7hg4fcYk8',
17
19
  forceTls: true, // force WSS
18
- // Mixnet round trips are slow, so give the tunnel handshake plenty of time.
19
- // v1 tuned a 5 min `requestTimeoutMs`; v2 exposes no per-request timeout, but
20
- // the tunnel setup is where mixnet latency bites, so restore that 5 min
21
- // budget here to avoid premature failures during the handshake.
22
- connectTimeoutMs: 300000
20
+ mixFetchOverride: {
21
+ requestTimeoutMs: 300000
22
+ }
23
23
  }
24
24
 
25
+ /**
26
+ * Budget for `createMixFetch` itself (client start + gateway handshake).
27
+ *
28
+ * A healthy setup with the pinned gateway completes in under 10s measured.
29
+ * Without a bound here the whole app blocks on the first mixnet request for
30
+ * as long as a dead gateway keeps us waiting, which reads to the user as a
31
+ * freeze.
32
+ */
33
+ const SETUP_TIMEOUT_MS = 60000
34
+
25
35
  // MixFetch initialization state
26
36
  let mixFetchInitPromise = null
27
37
 
@@ -32,22 +42,44 @@ let mixFetchInitPromise = null
32
42
  export async function initMixFetch(log) {
33
43
  if (mixFetchInitPromise == null) {
34
44
  log('Initializing mixFetch...')
35
- mixFetchInitPromise = createMixFetch(mixFetchOptions)
36
- .then(mixFetch => {
45
+ const pending = createMixFetch(mixFetchOptions)
46
+ // The timeout below can abandon this setup while it is still in flight.
47
+ // Deliberately do NOT tear it down on late completion: `createMixFetch`
48
+ // resolves to a healthy global singleton, and disconnecting it (a
49
+ // process-wide operation) would race a newer init that has taken over.
50
+ // A late completion just repopulates `__mixFetchGlobal`, which the next
51
+ // init reuses. Swallow a late rejection so it is not unhandled.
52
+ pending.catch(() => {})
53
+ let timer
54
+ const timeout = new Promise((resolve, reject) => {
55
+ timer = setTimeout(() => {
56
+ reject(
57
+ new Error(`mixFetch setup timed out after ${SETUP_TIMEOUT_MS}ms`)
58
+ )
59
+ }, SETUP_TIMEOUT_MS)
60
+ })
61
+ mixFetchInitPromise = Promise.race([pending, timeout])
62
+ .then(mixFetchModule => {
37
63
  log('mixFetch initialized successfully')
38
- return mixFetch
64
+ return mixFetchModule
39
65
  })
40
66
  .catch(async error => {
41
- // Tear down any partially-established tunnel left by the failed init
42
- // so the next createMixFetch call starts fresh instead of reusing a
67
+ // Clean up stale global state left by the failed init so the
68
+ // next createMixFetch call starts fresh instead of reusing a
43
69
  // broken singleton.
44
70
  try {
45
- await disconnectMixTunnel()
71
+ await disconnectMixFetch()
46
72
  } catch (e) {}
73
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
74
+ delete (window ).__mixFetchGlobal
47
75
  mixFetchInitPromise = null
48
76
  log.error('mixFetch initialization failed:', error)
49
77
  throw error
50
78
  })
79
+ .finally(() => {
80
+ clearTimeout(timer)
81
+ })
51
82
  }
52
- return await mixFetchInitPromise
83
+ const mixFetchModule = await mixFetchInitPromise
84
+ return mixFetchModule.mixFetch
53
85
  }
@@ -1,11 +1,13 @@
1
1
  /**
2
- * We only accept *.edge.app or localhost as valid domain names.
2
+ * We only accept *.edge.app, localhost, or (for http/ws only) private LAN IPv4.
3
+ * https/wss still require localhost or *.edge(test)?.app; private IPs are not
4
+ * accepted on secure schemes.
3
5
  */
4
6
  export function validateServer(server) {
5
7
  const url = new URL(server)
6
8
 
7
9
  if (url.protocol === 'http:' || url.protocol === 'ws:') {
8
- if (url.hostname === 'localhost') return
10
+ if (isPrivateHost(url.hostname)) return
9
11
  }
10
12
  if (url.protocol === 'https:' || url.protocol === 'wss:') {
11
13
  if (url.hostname === 'localhost') return
@@ -13,6 +15,34 @@ export function validateServer(server) {
13
15
  }
14
16
 
15
17
  throw new Error(
16
- `Only *.edge.app or localhost are valid login domain names, not ${url.hostname}`
18
+ `Only *.edge.app, localhost, or private LAN addresses (http/ws) are valid login domain names, not ${url.hostname}`
17
19
  )
18
20
  }
21
+
22
+ function isPrivateHost(hostname) {
23
+ if (hostname === 'localhost') return true
24
+ const octets = parseIpv4(hostname)
25
+ if (octets == null) return false
26
+ const [a, b] = octets
27
+ if (a === 127) return true
28
+ if (a === 10) return true
29
+ if (a === 192 && b === 168) return true
30
+ if (a === 172 && b >= 16 && b <= 31) return true
31
+ return false
32
+ }
33
+
34
+ function parseIpv4(hostname) {
35
+ const parts = hostname.split('.')
36
+ if (parts.length !== 4) return null
37
+ const octets = []
38
+ for (const part of parts) {
39
+ if (!/^\d{1,3}$/.test(part)) return null
40
+ const n = Number(part)
41
+ if (!Number.isInteger(n) || n < 0 || n > 255) return null
42
+ // Reject leading zeros like 010.0.0.1 which are not canonical dotted-quad
43
+ // when they reach this helper (URL parsing may already rewrite some forms).
44
+ if (part.length > 1 && part.startsWith('0')) return null
45
+ octets.push(n)
46
+ }
47
+ return octets
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edge-core-js",
3
- "version": "2.47.0",
3
+ "version": "2.48.0",
4
4
  "description": "Edge account & wallet management library",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -78,7 +78,7 @@
78
78
  "*.{js,jsx,ts,tsx}": "eslint"
79
79
  },
80
80
  "dependencies": {
81
- "@nymproject/mix-fetch": "^2.0.0",
81
+ "@nymproject/mix-fetch": "^1.4.4",
82
82
  "aes-js": "^3.1.0",
83
83
  "base-x": "^4.0.1",
84
84
  "biggystring": "^4.2.3",
@@ -2172,6 +2172,13 @@ export interface EdgeContext {
2172
2172
  readonly changeLogSettings: (
2173
2173
  settings: Partial<EdgeLogSettings>
2174
2174
  ) => Promise<void>
2175
+
2176
+ /**
2177
+ * Supplies the latest device attestation token for login-server requests.
2178
+ * Pass `undefined` or `''` to clear the header. Only subsequent login-server
2179
+ * requests pick up the new value.
2180
+ */
2181
+ readonly setAttestationToken: (token: string | undefined) => Promise<void>
2175
2182
  }
2176
2183
 
2177
2184
  // ---------------------------------------------------------------------
@@ -2194,8 +2201,8 @@ export interface EdgeFakeContextOptions {
2194
2201
  logSettings?: Partial<EdgeLogSettings>
2195
2202
  plugins?: EdgeCorePluginsInit
2196
2203
 
2197
- // Allows core plugins to access the real network except for the
2198
- // login and sync servers, which remain emulated:
2204
+ // Allows core plugins to access the real network except for login, info,
2205
+ // and sync servers, which remain emulated:
2199
2206
  allowNetworkAccess?: boolean
2200
2207
 
2201
2208
  // Fake device options: