blue-js-sdk 2.0.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/CHANGELOG.md +446 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/ai-path/ADMIN-ELEVATION.md +116 -0
- package/ai-path/AI-MANIFESTO.md +185 -0
- package/ai-path/BREAKING.md +74 -0
- package/ai-path/CHECKLIST.md +619 -0
- package/ai-path/CONNECTION-STEPS.md +724 -0
- package/ai-path/DECISION-TREE.md +378 -0
- package/ai-path/DEPENDENCIES.md +459 -0
- package/ai-path/E2E-FLOW.md +1555 -0
- package/ai-path/FAILURES.md +403 -0
- package/ai-path/GUIDE.md +1217 -0
- package/ai-path/README.md +558 -0
- package/ai-path/SPLIT-TUNNEL.md +266 -0
- package/ai-path/cli.js +535 -0
- package/ai-path/connect.js +884 -0
- package/ai-path/discover.js +178 -0
- package/ai-path/environment.js +266 -0
- package/ai-path/errors.js +86 -0
- package/ai-path/examples/autonomous-agent.mjs +220 -0
- package/ai-path/examples/multi-region.mjs +174 -0
- package/ai-path/examples/one-shot.mjs +31 -0
- package/ai-path/index.js +60 -0
- package/ai-path/pricing.js +136 -0
- package/ai-path/recommend.js +413 -0
- package/ai-path/run-admin.vbs +25 -0
- package/ai-path/setup.js +291 -0
- package/ai-path/wallet.js +137 -0
- package/app-helpers.js +363 -0
- package/app-settings.js +95 -0
- package/app-types.js +267 -0
- package/audit.js +847 -0
- package/batch.js +293 -0
- package/bin/setup.js +376 -0
- package/chain/authz.js +109 -0
- package/chain/broadcast.js +472 -0
- package/chain/client.js +160 -0
- package/chain/fee-grants.js +305 -0
- package/chain/index.js +891 -0
- package/chain/lcd.js +313 -0
- package/chain/queries.js +547 -0
- package/chain/rpc.js +408 -0
- package/chain/wallet.js +141 -0
- package/cli/config.js +143 -0
- package/cli/index.js +463 -0
- package/cli/output.js +182 -0
- package/cli.js +491 -0
- package/client/index.js +251 -0
- package/client.js +271 -0
- package/config/index.js +255 -0
- package/connection/connect.js +849 -0
- package/connection/disconnect.js +180 -0
- package/connection/discovery.js +321 -0
- package/connection/index.js +76 -0
- package/connection/proxy.js +148 -0
- package/connection/resilience.js +428 -0
- package/connection/security.js +232 -0
- package/connection/state.js +369 -0
- package/connection/tunnel.js +691 -0
- package/consumer.js +132 -0
- package/cosmjs-setup.js +1884 -0
- package/defaults.js +366 -0
- package/disk-cache.js +107 -0
- package/dist/client.d.ts +108 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +400 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/errors/index.js +112 -0
- package/errors.js +218 -0
- package/examples/README.md +64 -0
- package/examples/connect-direct.mjs +106 -0
- package/examples/connect-plan.mjs +125 -0
- package/examples/error-handling.mjs +109 -0
- package/examples/query-nodes.mjs +94 -0
- package/examples/wallet-basics.mjs +61 -0
- package/generated/amino/amino.ts +9 -0
- package/generated/cosmos/base/v1beta1/coin.ts +365 -0
- package/generated/cosmos_proto/cosmos.ts +323 -0
- package/generated/gogoproto/gogo.ts +9 -0
- package/generated/google/protobuf/descriptor.ts +7601 -0
- package/generated/google/protobuf/duration.ts +208 -0
- package/generated/google/protobuf/timestamp.ts +238 -0
- package/generated/sentinel/lease/v1/events.ts +924 -0
- package/generated/sentinel/lease/v1/lease.ts +292 -0
- package/generated/sentinel/lease/v1/msg.ts +949 -0
- package/generated/sentinel/lease/v1/params.ts +164 -0
- package/generated/sentinel/node/v3/events.ts +881 -0
- package/generated/sentinel/node/v3/msg.ts +1002 -0
- package/generated/sentinel/node/v3/node.ts +263 -0
- package/generated/sentinel/node/v3/params.ts +183 -0
- package/generated/sentinel/plan/v3/events.ts +675 -0
- package/generated/sentinel/plan/v3/msg.ts +1191 -0
- package/generated/sentinel/plan/v3/plan.ts +283 -0
- package/generated/sentinel/provider/v2/events.ts +171 -0
- package/generated/sentinel/provider/v2/msg.ts +480 -0
- package/generated/sentinel/provider/v2/params.ts +131 -0
- package/generated/sentinel/provider/v2/provider.ts +246 -0
- package/generated/sentinel/session/v3/events.ts +480 -0
- package/generated/sentinel/session/v3/msg.ts +616 -0
- package/generated/sentinel/session/v3/params.ts +260 -0
- package/generated/sentinel/session/v3/proof.ts +180 -0
- package/generated/sentinel/session/v3/session.ts +384 -0
- package/generated/sentinel/subscription/v3/events.ts +1181 -0
- package/generated/sentinel/subscription/v3/msg.ts +1305 -0
- package/generated/sentinel/subscription/v3/params.ts +167 -0
- package/generated/sentinel/subscription/v3/subscription.ts +315 -0
- package/generated/sentinel/types/v1/bandwidth.ts +124 -0
- package/generated/sentinel/types/v1/price.ts +149 -0
- package/generated/sentinel/types/v1/renewal.ts +87 -0
- package/generated/sentinel/types/v1/status.ts +54 -0
- package/generated/typeRegistry.ts +27 -0
- package/index.js +486 -0
- package/node-connect.js +3015 -0
- package/operator.js +134 -0
- package/package.json +113 -0
- package/plan-operations.js +199 -0
- package/preflight.js +352 -0
- package/pricing/index.js +262 -0
- package/proto/amino/amino.proto +84 -0
- package/proto/cosmos/base/v1beta1/coin.proto +61 -0
- package/proto/cosmos_proto/cosmos.proto +112 -0
- package/proto/gogoproto/gogo.proto +145 -0
- package/proto/google/api/annotations.proto +31 -0
- package/proto/google/api/http.proto +370 -0
- package/proto/google/protobuf/any.proto +106 -0
- package/proto/google/protobuf/duration.proto +115 -0
- package/proto/google/protobuf/timestamp.proto +145 -0
- package/proto/sentinel/lease/v1/events.proto +52 -0
- package/proto/sentinel/lease/v1/genesis.proto +15 -0
- package/proto/sentinel/lease/v1/lease.proto +25 -0
- package/proto/sentinel/lease/v1/msg.proto +62 -0
- package/proto/sentinel/lease/v1/params.proto +17 -0
- package/proto/sentinel/node/v3/events.proto +50 -0
- package/proto/sentinel/node/v3/genesis.proto +15 -0
- package/proto/sentinel/node/v3/msg.proto +63 -0
- package/proto/sentinel/node/v3/node.proto +27 -0
- package/proto/sentinel/node/v3/params.proto +21 -0
- package/proto/sentinel/node/v3/querier.proto +63 -0
- package/proto/sentinel/plan/v3/events.proto +41 -0
- package/proto/sentinel/plan/v3/genesis.proto +21 -0
- package/proto/sentinel/plan/v3/msg.proto +83 -0
- package/proto/sentinel/plan/v3/plan.proto +32 -0
- package/proto/sentinel/plan/v3/querier.proto +53 -0
- package/proto/sentinel/provider/v2/events.proto +16 -0
- package/proto/sentinel/provider/v2/genesis.proto +15 -0
- package/proto/sentinel/provider/v2/msg.proto +35 -0
- package/proto/sentinel/provider/v2/params.proto +17 -0
- package/proto/sentinel/provider/v2/provider.proto +24 -0
- package/proto/sentinel/provider/v3/genesis.proto +15 -0
- package/proto/sentinel/provider/v3/params.proto +13 -0
- package/proto/sentinel/session/v3/events.proto +30 -0
- package/proto/sentinel/session/v3/genesis.proto +15 -0
- package/proto/sentinel/session/v3/msg.proto +50 -0
- package/proto/sentinel/session/v3/params.proto +25 -0
- package/proto/sentinel/session/v3/proof.proto +25 -0
- package/proto/sentinel/session/v3/querier.proto +100 -0
- package/proto/sentinel/session/v3/session.proto +50 -0
- package/proto/sentinel/subscription/v2/allocation.proto +21 -0
- package/proto/sentinel/subscription/v2/payout.proto +22 -0
- package/proto/sentinel/subscription/v3/events.proto +65 -0
- package/proto/sentinel/subscription/v3/genesis.proto +17 -0
- package/proto/sentinel/subscription/v3/msg.proto +83 -0
- package/proto/sentinel/subscription/v3/params.proto +21 -0
- package/proto/sentinel/subscription/v3/subscription.proto +33 -0
- package/proto/sentinel/types/v1/bandwidth.proto +19 -0
- package/proto/sentinel/types/v1/price.proto +21 -0
- package/proto/sentinel/types/v1/renewal.proto +21 -0
- package/proto/sentinel/types/v1/status.proto +16 -0
- package/protocol/encoding.js +341 -0
- package/protocol/events.js +361 -0
- package/protocol/handshake.js +297 -0
- package/protocol/index.js +15 -0
- package/protocol/messages.js +346 -0
- package/protocol/plans.js +199 -0
- package/protocol/v2ray.js +268 -0
- package/protocol/v3.js +723 -0
- package/protocol/wireguard.js +125 -0
- package/security/index.js +132 -0
- package/session-manager.js +329 -0
- package/session-tracker.js +80 -0
- package/setup.js +376 -0
- package/speedtest/index.js +528 -0
- package/speedtest.js +567 -0
- package/src/client.ts +502 -0
- package/src/index.ts +20 -0
- package/state/index.js +347 -0
- package/state.js +516 -0
- package/test-all-chain-ops.js +493 -0
- package/test-all-logic.js +199 -0
- package/test-all-msg-types.js +292 -0
- package/test-every-connection.js +208 -0
- package/test-feegrant-connect.js +98 -0
- package/test-logic.js +148 -0
- package/test-mainnet.js +176 -0
- package/test-plan-lifecycle.js +335 -0
- package/tls-trust.js +132 -0
- package/tsconfig.build.json +20 -0
- package/tsconfig.json +34 -0
- package/types/chain.d.ts +746 -0
- package/types/connection.d.ts +425 -0
- package/types/errors.d.ts +174 -0
- package/types/index.d.ts +1380 -0
- package/types/nodes.d.ts +187 -0
- package/types/pricing.d.ts +156 -0
- package/types/protocol.d.ts +332 -0
- package/types/session.d.ts +236 -0
- package/types/settings.d.ts +192 -0
- package/v3protocol.js +1053 -0
- package/wallet/index.js +153 -0
- package/wireguard.js +307 -0
package/batch.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel dVPN SDK — Batch Session Operations
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ TESTING / AUDITING TOOL ONLY — NOT FOR CONSUMER APPS ⚠️
|
|
5
|
+
*
|
|
6
|
+
* Batch session creation: start multiple node sessions in a single transaction.
|
|
7
|
+
* This is designed for network audit tools that test hundreds of nodes sequentially.
|
|
8
|
+
*
|
|
9
|
+
* WHY THIS EXISTS:
|
|
10
|
+
* The Node Tester audits 1,000+ nodes. Paying individually = 1,000 TXs = 200 P2P in gas.
|
|
11
|
+
* Batching 5 per TX = 200 TXs = 40 P2P in gas. 5x cheaper.
|
|
12
|
+
*
|
|
13
|
+
* WHY CONSUMER APPS SHOULD NOT USE THIS:
|
|
14
|
+
* - Consumer apps connect to ONE node at a time. Use `connect()` or `connectDirect()`.
|
|
15
|
+
* - Batch payment creates sessions for 5 nodes simultaneously — wasteful for single-node use.
|
|
16
|
+
* - If any node in the batch fails, the entire TX fails (atomic).
|
|
17
|
+
* - Session management complexity (credential caching, poisoning, dedup) is audit-level concern.
|
|
18
|
+
*
|
|
19
|
+
* FOR CONSUMER APPS: use `connect(mnemonic, options)` from the SDK. One function, one node, done.
|
|
20
|
+
*
|
|
21
|
+
* Usage (audit tools only):
|
|
22
|
+
* import { batchStartSessions } from './batch.js';
|
|
23
|
+
* const results = await batchStartSessions(client, account, nodes, 1, 'udvpn');
|
|
24
|
+
* // results = Map<nodeAddr, BigInt sessionId>
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { ChainError, ErrorCodes } from './errors.js';
|
|
28
|
+
import { sleep } from './defaults.js';
|
|
29
|
+
import {
|
|
30
|
+
broadcast,
|
|
31
|
+
extractAllSessionIds,
|
|
32
|
+
findExistingSession,
|
|
33
|
+
lcdPaginatedSafe,
|
|
34
|
+
MSG_TYPES,
|
|
35
|
+
} from './cosmjs-setup.js';
|
|
36
|
+
|
|
37
|
+
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/** Default batch size limit (chain-tested safe maximum) */
|
|
40
|
+
const DEFAULT_BATCH_SIZE = 5;
|
|
41
|
+
|
|
42
|
+
/** Default gas per message */
|
|
43
|
+
const GAS_PER_MSG = 800000;
|
|
44
|
+
|
|
45
|
+
/** Default fee per message (udvpn) */
|
|
46
|
+
const FEE_PER_MSG = 200000;
|
|
47
|
+
|
|
48
|
+
/** Wait time before polling chain for session confirmation (ms) */
|
|
49
|
+
const POST_TX_POLL_DELAY = 2000;
|
|
50
|
+
|
|
51
|
+
/** Max time to wait for sessions to appear on-chain (ms) */
|
|
52
|
+
const DEFAULT_POLL_TIMEOUT = 20000;
|
|
53
|
+
|
|
54
|
+
// ─── Batch Start Sessions ────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Start sessions for multiple nodes in a single transaction.
|
|
58
|
+
* Builds N MsgStartSessionRequest messages, broadcasts as one TX,
|
|
59
|
+
* and extracts session IDs from the TX events.
|
|
60
|
+
*
|
|
61
|
+
* Handles:
|
|
62
|
+
* - Duplicate payment detection (via sessionManager if provided)
|
|
63
|
+
* - Session ID extraction from base64-encoded TX events
|
|
64
|
+
* - Fallback: on-chain lookup if extraction fails for some sessions
|
|
65
|
+
* - Cost tracking via optional onCost callback
|
|
66
|
+
*
|
|
67
|
+
* @param {SigningStargateClient} client - CosmJS signing client
|
|
68
|
+
* @param {{ address: string }} account - Signer account with .address
|
|
69
|
+
* @param {Array<{ address: string, gigabyte_prices: Array<{ denom: string, base_value: string, quote_value: string }> }>} nodes - Nodes to start sessions on
|
|
70
|
+
* @param {number} gigabytes - GB to allocate per session (typically 1)
|
|
71
|
+
* @param {string} denom - Token denomination (typically 'udvpn')
|
|
72
|
+
* @param {object} [options]
|
|
73
|
+
* @param {import('./session-manager.js').SessionManager} [options.sessionManager] - SessionManager for dedup/caching
|
|
74
|
+
* @param {number} [options.batchSize=5] - Max messages per TX
|
|
75
|
+
* @param {Function} [options.logger] - Optional logger function (msg) => void
|
|
76
|
+
* @param {Function} [options.onCost] - Called with { udvpn: number } after each TX (for cost tracking)
|
|
77
|
+
* @param {string} [options.lcdUrl] - LCD URL for fallback session lookups
|
|
78
|
+
* @returns {Promise<Map<string, bigint>>} Map of nodeAddr -> sessionId for all started sessions
|
|
79
|
+
*/
|
|
80
|
+
export async function batchStartSessions(client, account, nodes, gigabytes, denom, options = {}) {
|
|
81
|
+
const {
|
|
82
|
+
sessionManager,
|
|
83
|
+
batchSize = DEFAULT_BATCH_SIZE,
|
|
84
|
+
logger,
|
|
85
|
+
onCost,
|
|
86
|
+
lcdUrl,
|
|
87
|
+
} = options;
|
|
88
|
+
|
|
89
|
+
const log = logger || (() => {});
|
|
90
|
+
const allResults = new Map();
|
|
91
|
+
|
|
92
|
+
// Split into batches
|
|
93
|
+
const batches = [];
|
|
94
|
+
for (let i = 0; i < nodes.length; i += batchSize) {
|
|
95
|
+
batches.push(nodes.slice(i, i + batchSize));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const batch of batches) {
|
|
99
|
+
const batchResult = await _processBatch(
|
|
100
|
+
client, account, batch, gigabytes, denom,
|
|
101
|
+
{ sessionManager, log, onCost, lcdUrl },
|
|
102
|
+
);
|
|
103
|
+
for (const [addr, sid] of batchResult) {
|
|
104
|
+
allResults.set(addr, sid);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return allResults;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ─── Internal: Process a Single Batch ────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Process a single batch of nodes: build messages, broadcast, extract IDs.
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
async function _processBatch(client, account, batch, gigabytes, denom, ctx) {
|
|
118
|
+
const { sessionManager, log, onCost, lcdUrl } = ctx;
|
|
119
|
+
const result = new Map();
|
|
120
|
+
|
|
121
|
+
// Filter out already-paid nodes
|
|
122
|
+
const toPayBatch = [];
|
|
123
|
+
for (const node of batch) {
|
|
124
|
+
if (sessionManager?.isPaid(node.address)) {
|
|
125
|
+
log(`Skip ${node.address.slice(0, 20)}... - already paid this run`);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const priceEntry = (node.gigabyte_prices || []).find(p => p.denom === denom);
|
|
130
|
+
if (!priceEntry) {
|
|
131
|
+
log(`Skip ${node.address.slice(0, 20)}... - no ${denom} pricing`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
toPayBatch.push({ node, priceEntry });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (toPayBatch.length === 0) return result;
|
|
139
|
+
|
|
140
|
+
// Build messages
|
|
141
|
+
const messages = toPayBatch.map(({ node, priceEntry }) => ({
|
|
142
|
+
typeUrl: MSG_TYPES.START_SESSION,
|
|
143
|
+
value: {
|
|
144
|
+
from: account.address,
|
|
145
|
+
node_address: node.address,
|
|
146
|
+
gigabytes,
|
|
147
|
+
hours: 0,
|
|
148
|
+
max_price: {
|
|
149
|
+
denom: priceEntry.denom,
|
|
150
|
+
base_value: priceEntry.base_value,
|
|
151
|
+
quote_value: priceEntry.quote_value,
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
// Calculate fee
|
|
157
|
+
const n = toPayBatch.length;
|
|
158
|
+
const fee = {
|
|
159
|
+
amount: [{ denom, amount: String(FEE_PER_MSG * n) }],
|
|
160
|
+
gas: String(GAS_PER_MSG * n),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Broadcast
|
|
164
|
+
let txResult;
|
|
165
|
+
try {
|
|
166
|
+
txResult = await broadcast(client, account.address, messages, fee);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
throw new ChainError(
|
|
169
|
+
ErrorCodes.BROADCAST_FAILED,
|
|
170
|
+
`Batch session TX failed (${n} nodes): ${err.message}`,
|
|
171
|
+
{ nodeCount: n, original: err.message },
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Extract session IDs from TX events
|
|
176
|
+
const ids = extractAllSessionIds(txResult);
|
|
177
|
+
|
|
178
|
+
const unmatchedNodes = [];
|
|
179
|
+
toPayBatch.forEach(({ node }, i) => {
|
|
180
|
+
// Mark as paid regardless
|
|
181
|
+
if (sessionManager) sessionManager.markPaid(node.address);
|
|
182
|
+
|
|
183
|
+
if (ids[i]) {
|
|
184
|
+
result.set(node.address, ids[i]);
|
|
185
|
+
if (sessionManager) sessionManager.addToSessionMap(node.address, ids[i]);
|
|
186
|
+
} else {
|
|
187
|
+
unmatchedNodes.push(node.address);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Fallback: look up missing session IDs on-chain
|
|
192
|
+
if (unmatchedNodes.length > 0) {
|
|
193
|
+
log(`${unmatchedNodes.length} session IDs not extracted from TX, looking up on chain...`);
|
|
194
|
+
await sleep(POST_TX_POLL_DELAY);
|
|
195
|
+
|
|
196
|
+
if (sessionManager) sessionManager.invalidateSessionMap();
|
|
197
|
+
|
|
198
|
+
for (const addr of unmatchedNodes) {
|
|
199
|
+
try {
|
|
200
|
+
const sid = sessionManager
|
|
201
|
+
? await sessionManager.findExistingSession(addr)
|
|
202
|
+
: await findExistingSession(lcdUrl, account.address, addr);
|
|
203
|
+
if (sid) {
|
|
204
|
+
result.set(addr, sid);
|
|
205
|
+
} else {
|
|
206
|
+
log(`Could not find session for ${addr.slice(0, 20)}... - may need individual payment`);
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
log(`Fallback lookup failed for ${addr.slice(0, 20)}...: ${err.message}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Log TX hash
|
|
215
|
+
log(`Batch TX (${n} msgs): ${txResult.transactionHash.slice(0, 16)}...`);
|
|
216
|
+
|
|
217
|
+
// Report cost
|
|
218
|
+
if (onCost) {
|
|
219
|
+
let sessionCost = 0;
|
|
220
|
+
for (const { node } of toPayBatch) {
|
|
221
|
+
const priceEntry = (node.gigabyte_prices || []).find(p => p.denom === denom);
|
|
222
|
+
if (priceEntry) {
|
|
223
|
+
sessionCost += Math.round(parseFloat(priceEntry.quote_value) || 0) * gigabytes;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const gasCost = FEE_PER_MSG * n;
|
|
227
|
+
onCost({ udvpn: sessionCost + gasCost, sessionCost, gasCost, txHash: txResult.transactionHash });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// ─── Wait for Batch Sessions ─────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Poll until all node sessions appear on chain, or timeout.
|
|
237
|
+
* Useful after batch payment to ensure sessions are confirmed before handshake.
|
|
238
|
+
*
|
|
239
|
+
* @param {string[]} nodeAddrs - Node addresses to wait for
|
|
240
|
+
* @param {string} walletAddr - Wallet address
|
|
241
|
+
* @param {string} [lcdUrl] - LCD endpoint URL
|
|
242
|
+
* @param {object} [options]
|
|
243
|
+
* @param {number} [options.maxWaitMs=20000] - Maximum wait time in ms
|
|
244
|
+
* @param {number} [options.pollIntervalMs=2000] - Poll interval in ms
|
|
245
|
+
* @returns {Promise<{ confirmed: string[], pending: string[] }>}
|
|
246
|
+
*/
|
|
247
|
+
export async function waitForBatchSessions(nodeAddrs, walletAddr, lcdUrl, options = {}) {
|
|
248
|
+
const maxWaitMs = options.maxWaitMs ?? DEFAULT_POLL_TIMEOUT;
|
|
249
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2000;
|
|
250
|
+
const baseLcd = lcdUrl || 'https://lcd.sentinel.co';
|
|
251
|
+
|
|
252
|
+
if (nodeAddrs.length === 0) return { confirmed: [], pending: [] };
|
|
253
|
+
|
|
254
|
+
const pending = new Set(nodeAddrs);
|
|
255
|
+
const deadline = Date.now() + maxWaitMs;
|
|
256
|
+
|
|
257
|
+
while (pending.size > 0 && Date.now() < deadline) {
|
|
258
|
+
await sleep(pollIntervalMs);
|
|
259
|
+
try {
|
|
260
|
+
const result = await lcdPaginatedSafe(
|
|
261
|
+
baseLcd,
|
|
262
|
+
`/sentinel/session/v3/sessions?address=${walletAddr}&status=1`,
|
|
263
|
+
'sessions',
|
|
264
|
+
);
|
|
265
|
+
for (const s of (result.items || [])) {
|
|
266
|
+
const bs = s.base_session || s;
|
|
267
|
+
const n = bs.node_address || bs.node;
|
|
268
|
+
if (pending.has(n)) pending.delete(n);
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
// Transient LCD error — will retry on next poll
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const confirmed = nodeAddrs.filter(a => !pending.has(a));
|
|
276
|
+
return { confirmed, pending: [...pending] };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Wait for a single session to appear on chain.
|
|
281
|
+
* Convenience wrapper around waitForBatchSessions.
|
|
282
|
+
*
|
|
283
|
+
* @param {string} nodeAddr - Node address
|
|
284
|
+
* @param {string} walletAddr - Wallet address
|
|
285
|
+
* @param {string} [lcdUrl] - LCD endpoint URL
|
|
286
|
+
* @param {object} [options]
|
|
287
|
+
* @param {number} [options.maxWaitMs=20000] - Maximum wait time in ms
|
|
288
|
+
* @returns {Promise<boolean>} True if session confirmed, false if timed out
|
|
289
|
+
*/
|
|
290
|
+
export async function waitForSessionActive(nodeAddr, walletAddr, lcdUrl, options = {}) {
|
|
291
|
+
const { confirmed } = await waitForBatchSessions([nodeAddr], walletAddr, lcdUrl, options);
|
|
292
|
+
return confirmed.includes(nodeAddr);
|
|
293
|
+
}
|
package/bin/setup.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sentinel dVPN SDK — Setup Script
|
|
4
|
+
*
|
|
5
|
+
* Downloads required binaries (V2Ray, WireGuard) for the current platform.
|
|
6
|
+
* Run: node setup.js
|
|
7
|
+
*
|
|
8
|
+
* What it does:
|
|
9
|
+
* 1. Checks if V2Ray v5.2.1 exists in bin/ — downloads if missing
|
|
10
|
+
* 2. Checks if WireGuard is installed — prints install instructions if missing
|
|
11
|
+
* 3. Verifies all SDK dependencies are installed (npm install)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, createWriteStream, unlinkSync, readFileSync } from 'fs';
|
|
15
|
+
import { execSync, execFileSync } from 'child_process';
|
|
16
|
+
import { createHash } from 'crypto';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import { fileURLToPath } from 'url';
|
|
19
|
+
import https from 'https';
|
|
20
|
+
import http from 'http';
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const BIN_DIR = __dirname;
|
|
25
|
+
|
|
26
|
+
import { V2RAY_VERSION } from '../defaults.js';
|
|
27
|
+
const V2RAY_URLS = {
|
|
28
|
+
'win32-x64': `https://github.com/v2fly/v2ray-core/releases/download/v${V2RAY_VERSION}/v2ray-windows-64.zip`,
|
|
29
|
+
'win32-ia32': `https://github.com/v2fly/v2ray-core/releases/download/v${V2RAY_VERSION}/v2ray-windows-32.zip`,
|
|
30
|
+
'linux-x64': `https://github.com/v2fly/v2ray-core/releases/download/v${V2RAY_VERSION}/v2ray-linux-64.zip`,
|
|
31
|
+
'linux-arm64':`https://github.com/v2fly/v2ray-core/releases/download/v${V2RAY_VERSION}/v2ray-linux-arm64-v8a.zip`,
|
|
32
|
+
'darwin-x64': `https://github.com/v2fly/v2ray-core/releases/download/v${V2RAY_VERSION}/v2ray-macos-64.zip`,
|
|
33
|
+
'darwin-arm64':`https://github.com/v2fly/v2ray-core/releases/download/v${V2RAY_VERSION}/v2ray-macos-arm64-v8a.zip`,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const V2RAY_BINARY = process.platform === 'win32' ? 'v2ray.exe' : 'v2ray';
|
|
37
|
+
const V2RAY_FILES = [V2RAY_BINARY, 'geoip.dat', 'geosite.dat'];
|
|
38
|
+
|
|
39
|
+
// SHA256 checksums for V2Ray v5.2.1 release binaries (from official .dgst files)
|
|
40
|
+
const V2RAY_SHA256 = {
|
|
41
|
+
'win32-x64': 'd9791f911b603437a34219488b0111ae9913f38abe22c0103abce330537dabd6',
|
|
42
|
+
'win32-ia32': 'dc9f37dbeb32221e62b9a52b79f1842a217f049675872b334e1e5fd96121d0d2',
|
|
43
|
+
'linux-x64': '56eb8d4727b058d10f8ff830bb0121381386b0695171767f38ba410f2613fc9a',
|
|
44
|
+
'linux-arm64': '63958429e93f24f10f34a64701f70b4f42dfa0bc8120e1c0a426c6161bd2a3c9',
|
|
45
|
+
'darwin-x64': 'edbb0b94c05570d39a4549186927369853542649eb6b703dd432bda300c5d51a',
|
|
46
|
+
'darwin-arm64':'e18c17a79c4585d963395ae6ddafffb18c5d22777f7ac5938c1b40563db88d56',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// ─── WireGuard Download URLs (verified 2026-03-26) ─────────────────────────
|
|
50
|
+
// Direct MSI downloads — no browser, no bootstrapper, silent-installable
|
|
51
|
+
const WG_VERSION = '0.5.3';
|
|
52
|
+
const WG_URLS = {
|
|
53
|
+
'win32-x64': `https://download.wireguard.com/windows-client/wireguard-amd64-${WG_VERSION}.msi`,
|
|
54
|
+
'win32-arm64': `https://download.wireguard.com/windows-client/wireguard-arm64-${WG_VERSION}.msi`,
|
|
55
|
+
'win32-ia32': `https://download.wireguard.com/windows-client/wireguard-x86-${WG_VERSION}.msi`,
|
|
56
|
+
};
|
|
57
|
+
const WG_INSTALL_URL = 'https://download.wireguard.com/windows-client/wireguard-installer.exe';
|
|
58
|
+
|
|
59
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
function log(msg) { console.log(`[setup] ${msg}`); }
|
|
62
|
+
function warn(msg) { console.log(`[setup] ⚠ ${msg}`); }
|
|
63
|
+
function ok(msg) { console.log(`[setup] ✓ ${msg}`); }
|
|
64
|
+
|
|
65
|
+
function download(url, dest) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const follow = (url, redirects = 0) => {
|
|
68
|
+
if (redirects > 5) return reject(new Error('Too many redirects'));
|
|
69
|
+
const mod = url.startsWith('https') ? https : http;
|
|
70
|
+
mod.get(url, { headers: { 'User-Agent': 'sentinel-sdk-setup' } }, (res) => {
|
|
71
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
72
|
+
return follow(res.headers.location, redirects + 1);
|
|
73
|
+
}
|
|
74
|
+
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
75
|
+
const file = createWriteStream(dest);
|
|
76
|
+
res.pipe(file);
|
|
77
|
+
file.on('finish', () => file.close(resolve));
|
|
78
|
+
file.on('error', reject);
|
|
79
|
+
}).on('error', reject);
|
|
80
|
+
};
|
|
81
|
+
follow(url);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function unzip(zipPath, destDir) {
|
|
86
|
+
// Use PowerShell on Windows, unzip on others
|
|
87
|
+
if (process.platform === 'win32') {
|
|
88
|
+
execFileSync('powershell', ['-NoProfile', '-Command', `Expand-Archive -Force -Path '${zipPath}' -DestinationPath '${destDir}'`], { stdio: 'pipe' });
|
|
89
|
+
} else {
|
|
90
|
+
execFileSync('unzip', ['-o', zipPath, '-d', destDir], { stdio: 'pipe' });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── V2Ray ──────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
async function setupV2Ray() {
|
|
97
|
+
const v2rayPath = path.join(BIN_DIR, V2RAY_BINARY);
|
|
98
|
+
|
|
99
|
+
// Check if already exists
|
|
100
|
+
if (existsSync(v2rayPath)) {
|
|
101
|
+
ok(`V2Ray found at ${v2rayPath}`);
|
|
102
|
+
// Verify companion files
|
|
103
|
+
for (const f of V2RAY_FILES) {
|
|
104
|
+
if (!existsSync(path.join(BIN_DIR, f))) {
|
|
105
|
+
warn(`Missing ${f} in bin/ — re-downloading`);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (V2RAY_FILES.every(f => existsSync(path.join(BIN_DIR, f)))) return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Check system-wide
|
|
113
|
+
const systemPaths = [
|
|
114
|
+
'C:\\Program Files\\V2Ray\\v2ray.exe',
|
|
115
|
+
'C:\\Program Files (x86)\\V2Ray\\v2ray.exe',
|
|
116
|
+
'/usr/local/bin/v2ray',
|
|
117
|
+
'/usr/bin/v2ray',
|
|
118
|
+
];
|
|
119
|
+
for (const p of systemPaths) {
|
|
120
|
+
if (existsSync(p)) {
|
|
121
|
+
ok(`V2Ray found at ${p} (system install)`);
|
|
122
|
+
log('Tip: copy v2ray.exe + geoip.dat + geosite.dat to bin/ for portability');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Download
|
|
128
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
129
|
+
const url = V2RAY_URLS[platform];
|
|
130
|
+
if (!url) {
|
|
131
|
+
warn(`No V2Ray binary available for ${platform}`);
|
|
132
|
+
warn(`Download manually from: https://github.com/v2fly/v2ray-core/releases/tag/v${V2RAY_VERSION}`);
|
|
133
|
+
warn(`Place v2ray${process.platform === 'win32' ? '.exe' : ''}, geoip.dat, geosite.dat in bin/`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
mkdirSync(BIN_DIR, { recursive: true });
|
|
138
|
+
const zipPath = path.join(BIN_DIR, 'v2ray.zip');
|
|
139
|
+
|
|
140
|
+
log(`Downloading V2Ray v${V2RAY_VERSION} for ${platform}...`);
|
|
141
|
+
log(`URL: ${url}`);
|
|
142
|
+
await download(url, zipPath);
|
|
143
|
+
|
|
144
|
+
// SHA256 verification
|
|
145
|
+
const expectedHash = V2RAY_SHA256[platform];
|
|
146
|
+
if (expectedHash) {
|
|
147
|
+
const fileData = readFileSync(zipPath);
|
|
148
|
+
const actualHash = createHash('sha256').update(fileData).digest('hex');
|
|
149
|
+
if (actualHash !== expectedHash) {
|
|
150
|
+
unlinkSync(zipPath);
|
|
151
|
+
throw new Error(
|
|
152
|
+
`V2Ray SHA256 mismatch! Expected ${expectedHash}, got ${actualHash}. ` +
|
|
153
|
+
`Download may be corrupt or tampered. Delete bin/ and retry.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
ok('SHA256 checksum verified');
|
|
157
|
+
} else {
|
|
158
|
+
warn(`No SHA256 checksum for ${platform} — skipping verification`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
log('Extracting...');
|
|
162
|
+
unzip(zipPath, BIN_DIR);
|
|
163
|
+
unlinkSync(zipPath);
|
|
164
|
+
|
|
165
|
+
// Verify extraction + version check
|
|
166
|
+
if (existsSync(v2rayPath)) {
|
|
167
|
+
if (process.platform !== 'win32') {
|
|
168
|
+
execFileSync('chmod', ['+x', v2rayPath], { stdio: 'pipe' });
|
|
169
|
+
}
|
|
170
|
+
// Verify the binary runs and reports correct version
|
|
171
|
+
try {
|
|
172
|
+
const versionOut = execFileSync(v2rayPath, ['version'], { encoding: 'utf8', timeout: 10000, stdio: 'pipe' });
|
|
173
|
+
if (versionOut.includes(V2RAY_VERSION)) {
|
|
174
|
+
ok(`V2Ray v${V2RAY_VERSION} installed and verified`);
|
|
175
|
+
} else {
|
|
176
|
+
warn(`V2Ray binary reports unexpected version: ${versionOut.trim().split('\n')[0]}`);
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
warn('V2Ray installed but version check failed (binary may not be executable on this platform)');
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
warn('V2Ray extraction failed — check bin/ directory');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── WireGuard ──────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
function findWireGuard() {
|
|
189
|
+
// Windows: check standard install paths
|
|
190
|
+
if (process.platform === 'win32') {
|
|
191
|
+
const paths = [
|
|
192
|
+
'C:\\Program Files\\WireGuard\\wireguard.exe',
|
|
193
|
+
'C:\\Program Files (x86)\\WireGuard\\wireguard.exe',
|
|
194
|
+
];
|
|
195
|
+
for (const p of paths) {
|
|
196
|
+
if (existsSync(p)) return p;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// macOS/Linux: check wg-quick
|
|
201
|
+
if (process.platform !== 'win32') {
|
|
202
|
+
const paths = ['/usr/bin/wg', '/usr/local/bin/wg', '/opt/homebrew/bin/wg'];
|
|
203
|
+
for (const p of paths) {
|
|
204
|
+
if (existsSync(p)) return p;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check system PATH
|
|
209
|
+
try {
|
|
210
|
+
const cmd = process.platform === 'win32' ? 'where wireguard.exe' : 'which wg';
|
|
211
|
+
const result = execSync(cmd, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
212
|
+
if (result) return result.split('\n')[0];
|
|
213
|
+
} catch { /* not in PATH */ }
|
|
214
|
+
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isAdmin() {
|
|
219
|
+
if (process.platform === 'win32') {
|
|
220
|
+
try {
|
|
221
|
+
execSync('net session', { stdio: 'pipe' });
|
|
222
|
+
return true;
|
|
223
|
+
} catch {
|
|
224
|
+
try {
|
|
225
|
+
execSync('fsutil dirty query C:', { stdio: 'pipe' });
|
|
226
|
+
return true;
|
|
227
|
+
} catch { return false; }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return process.getuid?.() === 0;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function setupWireGuard() {
|
|
234
|
+
const existing = findWireGuard();
|
|
235
|
+
if (existing) {
|
|
236
|
+
ok(`WireGuard found at ${existing}`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Windows: auto-download MSI and silent install ───
|
|
241
|
+
if (process.platform === 'win32') {
|
|
242
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
243
|
+
const msiUrl = WG_URLS[platform];
|
|
244
|
+
|
|
245
|
+
if (!msiUrl) {
|
|
246
|
+
warn(`No WireGuard MSI for ${platform}`);
|
|
247
|
+
log(`Download manually from: ${WG_INSTALL_URL}`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!isAdmin()) {
|
|
252
|
+
warn('WireGuard not installed — admin privileges required for automatic install');
|
|
253
|
+
log('Run setup as Administrator, or install manually:');
|
|
254
|
+
log(` Download: ${msiUrl}`);
|
|
255
|
+
log(` Install: msiexec /i wireguard-amd64-${WG_VERSION}.msi /qn /norestart`);
|
|
256
|
+
log('');
|
|
257
|
+
log('WireGuard is optional — V2Ray nodes (~70% of network) work without it');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const msiPath = path.join(BIN_DIR, `wireguard-${WG_VERSION}.msi`);
|
|
262
|
+
mkdirSync(BIN_DIR, { recursive: true });
|
|
263
|
+
|
|
264
|
+
log(`Downloading WireGuard v${WG_VERSION} for ${platform}...`);
|
|
265
|
+
log(`URL: ${msiUrl}`);
|
|
266
|
+
await download(msiUrl, msiPath);
|
|
267
|
+
|
|
268
|
+
log('Installing WireGuard (silent)...');
|
|
269
|
+
try {
|
|
270
|
+
execSync(`msiexec /i "${msiPath}" /qn /norestart`, {
|
|
271
|
+
stdio: 'pipe',
|
|
272
|
+
timeout: 60000,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Verify installation
|
|
276
|
+
const installed = findWireGuard();
|
|
277
|
+
if (installed) {
|
|
278
|
+
ok(`WireGuard v${WG_VERSION} installed at ${installed}`);
|
|
279
|
+
} else {
|
|
280
|
+
warn('WireGuard MSI completed but binary not found — may need reboot');
|
|
281
|
+
}
|
|
282
|
+
} catch (err) {
|
|
283
|
+
warn(`WireGuard MSI install failed: ${err.message}`);
|
|
284
|
+
log('Try installing manually:');
|
|
285
|
+
log(` msiexec /i "${msiPath}" /qn /norestart`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Clean up MSI
|
|
289
|
+
try { unlinkSync(msiPath); } catch { /* ignore */ }
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ─── macOS: use Homebrew ───
|
|
294
|
+
if (process.platform === 'darwin') {
|
|
295
|
+
try {
|
|
296
|
+
execSync('which brew', { stdio: 'pipe' });
|
|
297
|
+
log('Installing WireGuard via Homebrew...');
|
|
298
|
+
execSync('brew install wireguard-tools', { stdio: 'inherit', timeout: 120000 });
|
|
299
|
+
ok('WireGuard installed via Homebrew');
|
|
300
|
+
return;
|
|
301
|
+
} catch {
|
|
302
|
+
warn('WireGuard not found and Homebrew not available');
|
|
303
|
+
log('Install Homebrew: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"');
|
|
304
|
+
log('Then: brew install wireguard-tools');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ─── Linux: use package manager ───
|
|
310
|
+
if (process.platform === 'linux') {
|
|
311
|
+
const managers = [
|
|
312
|
+
{ cmd: 'apt-get', install: 'sudo apt-get install -y wireguard-tools' },
|
|
313
|
+
{ cmd: 'dnf', install: 'sudo dnf install -y wireguard-tools' },
|
|
314
|
+
{ cmd: 'pacman', install: 'sudo pacman -S --noconfirm wireguard-tools' },
|
|
315
|
+
{ cmd: 'apk', install: 'sudo apk add wireguard-tools' },
|
|
316
|
+
];
|
|
317
|
+
|
|
318
|
+
for (const mgr of managers) {
|
|
319
|
+
try {
|
|
320
|
+
execSync(`which ${mgr.cmd}`, { stdio: 'pipe' });
|
|
321
|
+
if (isAdmin()) {
|
|
322
|
+
log(`Installing WireGuard via ${mgr.cmd}...`);
|
|
323
|
+
execSync(mgr.install, { stdio: 'inherit', timeout: 120000 });
|
|
324
|
+
ok('WireGuard installed');
|
|
325
|
+
} else {
|
|
326
|
+
warn('WireGuard not installed — root required');
|
|
327
|
+
log(`Install: ${mgr.install}`);
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
} catch { /* try next manager */ }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
warn('WireGuard not found and no supported package manager detected');
|
|
334
|
+
log('Install wireguard-tools for your distribution');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ─── npm dependencies ───────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
function checkDeps() {
|
|
341
|
+
if (!existsSync(path.join(__dirname, 'node_modules'))) {
|
|
342
|
+
log('Installing npm dependencies...');
|
|
343
|
+
execSync('npm install', { cwd: __dirname, stdio: 'inherit' });
|
|
344
|
+
ok('Dependencies installed');
|
|
345
|
+
} else {
|
|
346
|
+
ok('npm dependencies present');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
async function main() {
|
|
353
|
+
console.log('');
|
|
354
|
+
console.log(' Sentinel dVPN SDK — Setup');
|
|
355
|
+
console.log(' ─────────────────────────');
|
|
356
|
+
console.log('');
|
|
357
|
+
|
|
358
|
+
checkDeps();
|
|
359
|
+
await setupV2Ray();
|
|
360
|
+
await setupWireGuard();
|
|
361
|
+
|
|
362
|
+
console.log('');
|
|
363
|
+
console.log(' Setup complete! Quick start:');
|
|
364
|
+
console.log('');
|
|
365
|
+
console.log(' import { connect, listNodes, registerCleanupHandlers } from \'./index.js\';');
|
|
366
|
+
console.log('');
|
|
367
|
+
console.log(' registerCleanupHandlers();');
|
|
368
|
+
console.log(' const nodes = await listNodes();');
|
|
369
|
+
console.log(' const conn = await connect({ mnemonic: \'...\', nodeAddress: nodes[0].address });');
|
|
370
|
+
console.log('');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
main().catch(err => {
|
|
374
|
+
console.error(`[setup] Fatal: ${err.message}`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
});
|