@oneaddress/setup 1.3.3 → 1.4.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/dist/index.js +182 -66
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -856,7 +856,7 @@ var _R = ["\u2588\u2588\u2588\u2588\u2588\u2588 ", "\u2588\u2588 \u2588\u2588"
|
|
|
856
856
|
var _S = [" \u2588\u2588\u2588\u2588\u2588\u2588", "\u2588\u2588 ", "\u2588\u2588 ", " \u2588\u2588\u2588\u2588\u2588 ", " \u2588\u2588", " \u2588\u2588", "\u2588\u2588\u2588\u2588\u2588\u2588 "];
|
|
857
857
|
var ONE_ROWS = Array.from({ length: 7 }, (_3, i) => [_O[i], _N[i], _E[i]].join(" "));
|
|
858
858
|
var ADDR_ROWS = Array.from({ length: 7 }, (_3, i) => [_A[i], _D2[i], _D2[i], _R[i], _E[i], _S[i], _S[i]].join(" "));
|
|
859
|
-
var WIZARD_VERSION = true ? "1.
|
|
859
|
+
var WIZARD_VERSION = true ? "1.4.0" : "?";
|
|
860
860
|
function printCompactHeader() {
|
|
861
861
|
const INNER = 42;
|
|
862
862
|
const TOP = fn("\u250C") + dm("\u2500".repeat(INNER)) + fn("\u2510");
|
|
@@ -1204,6 +1204,7 @@ if (nodeMajor < 22 || (nodeMajor === 22 && nodeMinor < 5)) {
|
|
|
1204
1204
|
|
|
1205
1205
|
import 'dotenv/config';
|
|
1206
1206
|
import express, { Request, Response } from 'express';
|
|
1207
|
+
import { createPrivateKey } from 'node:crypto';
|
|
1207
1208
|
import {
|
|
1208
1209
|
verifySignature,
|
|
1209
1210
|
decryptAddress,
|
|
@@ -1229,10 +1230,39 @@ if (!WEBHOOK_SECRET || !PARTNER_PRIVATE_KEY || !PARTNER_ID) {
|
|
|
1229
1230
|
process.exit(1);
|
|
1230
1231
|
}
|
|
1231
1232
|
|
|
1232
|
-
//
|
|
1233
|
-
//
|
|
1234
|
-
//
|
|
1235
|
-
//
|
|
1233
|
+
// Startup self-check. Two faults were historically INVISIBLE until a live
|
|
1234
|
+
// dispatch, and both read afterwards as "partner key mismatch" deep inside a
|
|
1235
|
+
// try/catch, which is a day of reading webhook logs. Assert them here so the
|
|
1236
|
+
// receiver refuses to start and names the fix instead.
|
|
1237
|
+
// 1. An @oneaddress/partner-sdk too old to export decryptSession. A missing
|
|
1238
|
+
// CJS export is undefined, not a load error, so tsx never complains and it
|
|
1239
|
+
// fails only when the first payload arrives.
|
|
1240
|
+
// 2. A private key that does not parse (empty, truncated, or header stripped).
|
|
1241
|
+
if (typeof decryptSession !== 'function') {
|
|
1242
|
+
console.error('[startup] @oneaddress/partner-sdk does not export decryptSession. Your installed SDK is too old for D5 dispatches.');
|
|
1243
|
+
console.error('[startup] Fix: npm install "@oneaddress/partner-sdk@^1.8.0", then restart.');
|
|
1244
|
+
process.exit(1);
|
|
1245
|
+
}
|
|
1246
|
+
// Only PEM keys are parseable this way; a post-quantum key is a base64 secret,
|
|
1247
|
+
// so skip the check unless the value looks like a PEM.
|
|
1248
|
+
if (PARTNER_PRIVATE_KEY.includes('BEGIN')) {
|
|
1249
|
+
try {
|
|
1250
|
+
createPrivateKey(PARTNER_PRIVATE_KEY);
|
|
1251
|
+
} catch {
|
|
1252
|
+
console.error('[startup] PARTNER_PRIVATE_KEY_PEM does not parse as a private key.');
|
|
1253
|
+
console.error('[startup] Paste the FULL PEM, including the -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- lines.');
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// In-memory dedup cache. Records a dispatch id only once it has been fully
|
|
1259
|
+
// HANDLED, so a dispatch that failed to decrypt is NOT remembered and a later
|
|
1260
|
+
// retry re-runs it rather than being dismissed as a duplicate. Bounded so a
|
|
1261
|
+
// partner running with the default scaffold can't be DoS'd by an attacker
|
|
1262
|
+
// who's already past HMAC verify (i.e. has stolen the webhook secret) by
|
|
1263
|
+
// sending unique dispatch IDs until RAM is exhausted. For production use a
|
|
1264
|
+
// distributed store (Redis), and prefer the completion-claim model the other
|
|
1265
|
+
// language scaffolds use.
|
|
1236
1266
|
const SEEN_DISPATCHES_MAX = 10_000;
|
|
1237
1267
|
const seenDispatches = new Set<string>();
|
|
1238
1268
|
function rememberDispatch(id: string): void {
|
|
@@ -1296,7 +1326,12 @@ app.post('/webhook', async (req: Request, res: Response) => {
|
|
|
1296
1326
|
if (dispatch && seenDispatches.has(dispatch)) {
|
|
1297
1327
|
return res.status(200).json({ ok: true, duplicate: true });
|
|
1298
1328
|
}
|
|
1299
|
-
|
|
1329
|
+
// A dispatch is remembered ONLY after it has been fully handled (see the
|
|
1330
|
+
// success returns below), never here. Remembering before the work would mark
|
|
1331
|
+
// a dispatch that then FAILS to decrypt (422 below) as "seen", so OneAddress's
|
|
1332
|
+
// retry after you fix the key would be dismissed as a duplicate and the update
|
|
1333
|
+
// lost. The connection-verification probe (address.test) is never remembered
|
|
1334
|
+
// at all, so re-running go-live always re-tests instead of short-circuiting.
|
|
1300
1335
|
|
|
1301
1336
|
const event = body.event as string;
|
|
1302
1337
|
|
|
@@ -1387,6 +1422,7 @@ app.post('/webhook', async (req: Request, res: Response) => {
|
|
|
1387
1422
|
if (event === 'address.updated') {
|
|
1388
1423
|
console.log(\`[webhook] address.updated for \${ctx.accountNumber || ctx.name}\`);
|
|
1389
1424
|
await saveAddress(ctx, address);
|
|
1425
|
+
if (dispatch) rememberDispatch(dispatch); // remember only after it is stored
|
|
1390
1426
|
return res.status(200).json({ ok: true });
|
|
1391
1427
|
}
|
|
1392
1428
|
|
|
@@ -1423,6 +1459,7 @@ app.post('/webhook', async (req: Request, res: Response) => {
|
|
|
1423
1459
|
token: callbackToken,
|
|
1424
1460
|
}),
|
|
1425
1461
|
});
|
|
1462
|
+
if (dispatch) rememberDispatch(dispatch); // remember only after the callback posted
|
|
1426
1463
|
return res.status(200).json({ ok: true });
|
|
1427
1464
|
}
|
|
1428
1465
|
|
|
@@ -1435,7 +1472,17 @@ app.post('/webhook', async (req: Request, res: Response) => {
|
|
|
1435
1472
|
// as "answered but could not decrypt", which is not what happened.
|
|
1436
1473
|
if (event === 'address.test' || event === 'address.test-dispatch') {
|
|
1437
1474
|
const index = typeof body.index === 'number' ? body.index : undefined;
|
|
1475
|
+
// Print the decrypted record as a verification line for the connection-
|
|
1476
|
+
// verification flow. The portal's verify page asks you to copy these lines
|
|
1477
|
+
// (shaped "N | name | address") out of this console and paste them at the
|
|
1478
|
+
// verify URL in your instruction email; decorative lines around them are
|
|
1479
|
+
// ignored, so pasting the whole block is fine. Returning { verification:true }
|
|
1480
|
+
// is what the go-live and receiver-verification decrypt proofs look for.
|
|
1481
|
+
const a = address as { street?: unknown; suburb?: unknown; state?: unknown; postcode?: unknown };
|
|
1482
|
+
const oneLine = [a.street, [a.suburb, a.state, a.postcode].filter(Boolean).join(' ')]
|
|
1483
|
+
.filter(Boolean).join(', ');
|
|
1438
1484
|
console.log(\`[webhook] \${event} verification probe decrypted OK\`);
|
|
1485
|
+
console.log(\`[verification] \${index ?? '?'} | \${ctx.name} | \${oneLine}\`);
|
|
1439
1486
|
return res.status(200).json({ verification: true, index });
|
|
1440
1487
|
}
|
|
1441
1488
|
|
|
@@ -1905,10 +1952,15 @@ async def webhook(request: Request) -> Response:
|
|
|
1905
1952
|
session_share = body.get("session_key_share")
|
|
1906
1953
|
legacy_enc = body.get("address_encrypted")
|
|
1907
1954
|
|
|
1955
|
+
# Decrypted display name, captured for the connection-verification probe
|
|
1956
|
+
# below. Identity comes ONLY from decryption, never a cleartext wire field.
|
|
1957
|
+
verified_name = ""
|
|
1958
|
+
|
|
1908
1959
|
if isinstance(session_envelope, str) and isinstance(session_share, dict):
|
|
1909
1960
|
try:
|
|
1910
1961
|
data = _decrypt_session(session_share, session_envelope, PRIVATE_KEY, PARTNER_ID)
|
|
1911
1962
|
address = data.get("new_address", {})
|
|
1963
|
+
verified_name = data.get("verified_name", "") or ""
|
|
1912
1964
|
except Exception as e:
|
|
1913
1965
|
print(f"[webhook] D5 decryption failed \u2014 check OA_PRIVATE_KEY_PEM matches key_id "
|
|
1914
1966
|
f"{session_share.get('key_id', '?')}: {e}")
|
|
@@ -1917,6 +1969,7 @@ async def webhook(request: Request) -> Response:
|
|
|
1917
1969
|
elif isinstance(legacy_enc, dict):
|
|
1918
1970
|
try:
|
|
1919
1971
|
address = _decrypt_address(legacy_enc, PRIVATE_KEY, PARTNER_ID)
|
|
1972
|
+
verified_name = address.get("fullName", "") or ""
|
|
1920
1973
|
except Exception as e:
|
|
1921
1974
|
print(f"[webhook] Decryption error (check OA_PRIVATE_KEY_PEM): {e}")
|
|
1922
1975
|
return Response(content='{"ok":false,"error":"decryption failed - partner key mismatch"}',
|
|
@@ -1977,8 +2030,18 @@ async def webhook(request: Request) -> Response:
|
|
|
1977
2030
|
# OneAddress connection-verification probe. Reaching here means the D5
|
|
1978
2031
|
# decrypt above succeeded (a wrong key returned 422), which is exactly
|
|
1979
2032
|
# what the check proves, so answer verification=true. Anything else reads
|
|
1980
|
-
# as "answered but could not decrypt", which is not what happened.
|
|
2033
|
+
# as "answered but could not decrypt", which is not what happened. We also
|
|
2034
|
+
# print the decrypted record as a "N | name | address" line: the portal's
|
|
2035
|
+
# verify page asks you to copy these lines from this console and paste
|
|
2036
|
+
# them at the verify URL in your instruction email. Decorative lines are
|
|
2037
|
+
# ignored, so pasting the whole block is fine.
|
|
2038
|
+
idx = body.get("index")
|
|
2039
|
+
_locality = " ".join(str(x) for x in [address.get("suburb", ""),
|
|
2040
|
+
address.get("state", ""),
|
|
2041
|
+
address.get("postcode", "")] if x)
|
|
2042
|
+
one_line = ", ".join(str(p) for p in [address.get("street", ""), _locality] if p)
|
|
1981
2043
|
print(f"[webhook] {event} verification probe decrypted OK")
|
|
2044
|
+
print(f"[verification] {idx if idx is not None else '?'} | {verified_name} | {one_line}")
|
|
1982
2045
|
return Response(content=json.dumps({"verification": True, "index": body.get("index")}),
|
|
1983
2046
|
media_type="application/json")
|
|
1984
2047
|
|
|
@@ -2542,10 +2605,22 @@ public class OneAddressWebhookController {
|
|
|
2542
2605
|
} else if ("address.test".equals(event) || "address.test-dispatch".equals(event)) {
|
|
2543
2606
|
// OneAddress connection-verification probe. Reaching here means the
|
|
2544
2607
|
// D5 decrypt above succeeded (a wrong key returned 422), which is
|
|
2545
|
-
// what the check proves, so answer verification=true.
|
|
2608
|
+
// what the check proves, so answer verification=true. We also print
|
|
2609
|
+
// the decrypted record as a "N | name | address" line: the portal's
|
|
2610
|
+
// verify page asks you to copy these lines from this console and
|
|
2611
|
+
// paste them at the verify URL in your instruction email.
|
|
2546
2612
|
if (dispatchId != null && !dispatchId.isBlank()) store.markProcessed(dispatchId, "verification:ok");
|
|
2547
2613
|
Object idx = body.get("index");
|
|
2614
|
+
java.util.function.Function<String, String> fld = k -> {
|
|
2615
|
+
Object v = address.get(k);
|
|
2616
|
+
return v == null ? "" : String.valueOf(v);
|
|
2617
|
+
};
|
|
2618
|
+
String oaLoc = java.util.stream.Stream.of(fld.apply("suburb"), fld.apply("state"), fld.apply("postcode"))
|
|
2619
|
+
.filter(s -> !s.isBlank()).collect(java.util.stream.Collectors.joining(" "));
|
|
2620
|
+
String oaLine = java.util.stream.Stream.of(fld.apply("street"), oaLoc)
|
|
2621
|
+
.filter(s -> !s.isBlank()).collect(java.util.stream.Collectors.joining(", "));
|
|
2548
2622
|
log.info("[OneAddress] {} verification probe decrypted OK", event);
|
|
2623
|
+
log.info("[verification] {} | {} | {}", idx == null ? "?" : idx, verifiedName, oaLine);
|
|
2549
2624
|
return ResponseEntity.ok("{\\"verification\\":true,\\"index\\":" + (idx == null ? "null" : idx.toString()) + "}");
|
|
2550
2625
|
} else {
|
|
2551
2626
|
log.info("[OneAddress] skipping unknown event: {}", event);
|
|
@@ -3404,10 +3479,20 @@ app.MapPost("/webhooks/oneaddress", async (HttpRequest request) =>
|
|
|
3404
3479
|
{
|
|
3405
3480
|
// OneAddress connection-verification probe. Reaching here means the D5
|
|
3406
3481
|
// decrypt above succeeded (a wrong key returned 422), which is what the
|
|
3407
|
-
// check proves, so answer verification=true.
|
|
3482
|
+
// check proves, so answer verification=true. We also print the decrypted
|
|
3483
|
+
// record as a "N | name | address" line: the portal's verify page asks
|
|
3484
|
+
// you to copy these lines from this console and paste them at the verify
|
|
3485
|
+
// URL in your instruction email.
|
|
3408
3486
|
int? index = body.TryGetProperty("index", out var idxEl) && idxEl.ValueKind == JsonValueKind.Number
|
|
3409
3487
|
? idxEl.GetInt32() : (int?)null;
|
|
3488
|
+
static string OaStr(JsonElement a, string k) =>
|
|
3489
|
+
a.TryGetProperty(k, out var el) && el.ValueKind == JsonValueKind.String ? (el.GetString() ?? "") : "";
|
|
3490
|
+
var oaLoc = string.Join(" ", new[] { OaStr(address, "suburb"), OaStr(address, "state"), OaStr(address, "postcode") }
|
|
3491
|
+
.Where(s => !string.IsNullOrWhiteSpace(s)));
|
|
3492
|
+
var oaLine = string.Join(", ", new[] { OaStr(address, "street"), oaLoc }
|
|
3493
|
+
.Where(s => !string.IsNullOrWhiteSpace(s)));
|
|
3410
3494
|
app.Logger.LogInformation("[OneAddress] {EventType} verification probe decrypted OK", eventType);
|
|
3495
|
+
app.Logger.LogInformation("[verification] {Line}", (index?.ToString() ?? "?") + " | " + verifiedName + " | " + oaLine);
|
|
3411
3496
|
return Results.Json(new { verification = true, index });
|
|
3412
3497
|
}
|
|
3413
3498
|
|
|
@@ -4252,6 +4337,9 @@ func main() {
|
|
|
4252
4337
|
// OneAddress connection-verification probe. Reaching this case means the
|
|
4253
4338
|
// decrypt above succeeded (a wrong key returned 422), which is what the
|
|
4254
4339
|
// check proves, so answer verification=true rather than the ok:true below.
|
|
4340
|
+
// We also print the decrypted record as a "N | name | address" line: the
|
|
4341
|
+
// portal's verify page asks you to copy these lines from this console and
|
|
4342
|
+
// paste them at the verify URL in your instruction email.
|
|
4255
4343
|
log.Printf("[webhook] %s verification probe decrypted OK", event)
|
|
4256
4344
|
if dispatch != "" {
|
|
4257
4345
|
_ = store.MarkProcessed(dispatch, "verification:ok")
|
|
@@ -4260,6 +4348,23 @@ func main() {
|
|
|
4260
4348
|
if v, ok := parsed["index"]; ok {
|
|
4261
4349
|
idx = v
|
|
4262
4350
|
}
|
|
4351
|
+
var locParts []string
|
|
4352
|
+
for _, s := range []string{str(address, "suburb"), str(address, "state"), str(address, "postcode")} {
|
|
4353
|
+
if s != "" {
|
|
4354
|
+
locParts = append(locParts, s)
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
var lineParts []string
|
|
4358
|
+
for _, s := range []string{str(address, "street"), strings.Join(locParts, " ")} {
|
|
4359
|
+
if s != "" {
|
|
4360
|
+
lineParts = append(lineParts, s)
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
idxStr := "?"
|
|
4364
|
+
if idx != nil {
|
|
4365
|
+
idxStr = fmt.Sprintf("%v", idx)
|
|
4366
|
+
}
|
|
4367
|
+
log.Printf("[verification] %s | %s | %s", idxStr, verifiedName, strings.Join(lineParts, ", "))
|
|
4263
4368
|
jsonResp(w, 200, map[string]any{"verification": true, "index": idx})
|
|
4264
4369
|
return
|
|
4265
4370
|
|
|
@@ -5105,8 +5210,16 @@ Route::post('/webhook', function (Request $request): Response {
|
|
|
5105
5210
|
if ($eventType === 'address.test' || $eventType === 'address.test-dispatch') {
|
|
5106
5211
|
// OneAddress connection-verification probe. Reaching here means the D5
|
|
5107
5212
|
// decrypt above succeeded (a wrong key returned 422), which is what the
|
|
5108
|
-
// check proves, so answer verification=true.
|
|
5213
|
+
// check proves, so answer verification=true. We also print the decrypted
|
|
5214
|
+
// record as a "N | name | address" line: the portal's verify page asks
|
|
5215
|
+
// you to copy these lines from this console and paste them at the verify
|
|
5216
|
+
// URL in your instruction email.
|
|
5217
|
+
$oaAddr = is_array($address) ? $address : [];
|
|
5218
|
+
$oaKeep = static fn($s) => $s !== null && $s !== '';
|
|
5219
|
+
$oaLoc = implode(' ', array_filter([$oaAddr['suburb'] ?? '', $oaAddr['state'] ?? '', $oaAddr['postcode'] ?? ''], $oaKeep));
|
|
5220
|
+
$oaLine = implode(', ', array_filter([$oaAddr['street'] ?? '', $oaLoc], $oaKeep));
|
|
5109
5221
|
\\Log::info('[OneAddress] verification probe decrypted OK', ['event' => $eventType]);
|
|
5222
|
+
\\Log::info('[verification] ' . ($body['index'] ?? '?') . ' | ' . $verifiedName . ' | ' . $oaLine);
|
|
5110
5223
|
return response()->json(['verification' => true, 'index' => $body['index'] ?? null]);
|
|
5111
5224
|
}
|
|
5112
5225
|
|
|
@@ -5296,7 +5409,7 @@ async function scaffold(platform, outputDir, partnerId, webhookSecret, webhookUr
|
|
|
5296
5409
|
|
|
5297
5410
|
// src/register.ts
|
|
5298
5411
|
var import_node_crypto = require("crypto");
|
|
5299
|
-
var PKG_VERSION = true ? "1.
|
|
5412
|
+
var PKG_VERSION = true ? "1.4.0" : "dev";
|
|
5300
5413
|
var REGISTER_URL = "https://partners.oneaddress.io/api/partner/installs";
|
|
5301
5414
|
function hmacSha256(secret, message) {
|
|
5302
5415
|
return (0, import_node_crypto.createHmac)("sha256", secret).update(message).digest("hex");
|
|
@@ -6299,6 +6412,7 @@ async function main() {
|
|
|
6299
6412
|
let webhookReady = true;
|
|
6300
6413
|
let tunnelBase = null;
|
|
6301
6414
|
let existingUrlReason = "";
|
|
6415
|
+
let keepExistingUrl = false;
|
|
6302
6416
|
const existingUrlResult = await getExistingWebhookUrl(pid, secret, (r2) => {
|
|
6303
6417
|
existingUrlReason = r2;
|
|
6304
6418
|
});
|
|
@@ -6314,9 +6428,9 @@ Continuing will overwrite it.`
|
|
|
6314
6428
|
});
|
|
6315
6429
|
assertNotCancelled(continueAnyway);
|
|
6316
6430
|
if (!continueAnyway) {
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6431
|
+
keepExistingUrl = true;
|
|
6432
|
+
webhookUrl = existingUrlResult;
|
|
6433
|
+
M2.info(`Keeping your existing webhook URL: ${webhookUrl}`);
|
|
6320
6434
|
}
|
|
6321
6435
|
} else if (existingUrlResult === void 0) {
|
|
6322
6436
|
M2.warn(
|
|
@@ -6333,70 +6447,72 @@ Continuing will overwrite it.`
|
|
|
6333
6447
|
process.exit(0);
|
|
6334
6448
|
}
|
|
6335
6449
|
}
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
assertNotCancelled(hasPublicUrl);
|
|
6341
|
-
if (hasPublicUrl) {
|
|
6342
|
-
const providedUrl = await he({
|
|
6343
|
-
message: "Your public HTTPS webhook URL",
|
|
6344
|
-
placeholder: "https://my-api.example.com/webhook",
|
|
6345
|
-
validate: (v2) => {
|
|
6346
|
-
if (!v2.trim()) return "URL is required";
|
|
6347
|
-
if (!v2.trim().startsWith("https://"))
|
|
6348
|
-
return "Must start with https:// \u2014 OneAddress enforces HTTPS for outbound webhooks";
|
|
6349
|
-
}
|
|
6450
|
+
if (!keepExistingUrl) {
|
|
6451
|
+
const hasPublicUrl = await ye({
|
|
6452
|
+
message: "Do you have a public HTTPS URL where this server is reachable?",
|
|
6453
|
+
initialValue: false
|
|
6350
6454
|
});
|
|
6351
|
-
assertNotCancelled(
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
);
|
|
6366
|
-
const
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
|
|
6371
|
-
stopTunnel();
|
|
6372
|
-
tunnel = await startTunnel(3001);
|
|
6455
|
+
assertNotCancelled(hasPublicUrl);
|
|
6456
|
+
if (hasPublicUrl) {
|
|
6457
|
+
const providedUrl = await he({
|
|
6458
|
+
message: "Your public HTTPS webhook URL",
|
|
6459
|
+
placeholder: "https://my-api.example.com/webhook",
|
|
6460
|
+
validate: (v2) => {
|
|
6461
|
+
if (!v2.trim()) return "URL is required";
|
|
6462
|
+
if (!v2.trim().startsWith("https://"))
|
|
6463
|
+
return "Must start with https:// \u2014 OneAddress enforces HTTPS for outbound webhooks";
|
|
6464
|
+
}
|
|
6465
|
+
});
|
|
6466
|
+
assertNotCancelled(providedUrl);
|
|
6467
|
+
webhookUrl = providedUrl.trim();
|
|
6468
|
+
} else {
|
|
6469
|
+
M2.info("\nSetting up a free HTTPS tunnel via Cloudflare Tunnel\u2026");
|
|
6470
|
+
const s4 = Y2();
|
|
6471
|
+
s4.start("Starting Cloudflare Tunnel");
|
|
6472
|
+
onCleanup(stopTunnel);
|
|
6473
|
+
try {
|
|
6474
|
+
let tunnel = await startTunnel(3001);
|
|
6373
6475
|
webhookUrl = `${tunnel.url}/webhook`;
|
|
6374
6476
|
tunnelBase = tunnel.url;
|
|
6375
|
-
|
|
6376
|
-
s4c.start(`Waiting for the new tunnel to route: ${tunnel.url}`);
|
|
6377
|
-
webhookReady = await waitForTunnelReady(tunnel.url, { budgetMs: 45e3 });
|
|
6378
|
-
s4c.stop(webhookReady ? "Tunnel is routing" : "Tunnel still not routing");
|
|
6379
|
-
} else {
|
|
6380
|
-
s4b.stop("Tunnel is routing");
|
|
6381
|
-
}
|
|
6382
|
-
if (!webhookReady) {
|
|
6477
|
+
s4.stop(`Tunnel active: ${tunnel.url}`);
|
|
6383
6478
|
M2.warn(
|
|
6384
|
-
|
|
6479
|
+
"IMPORTANT: This tunnel URL is temporary. It lives only while this wizard\nkeeps running, and every run gets a new address. Before going to production,\ndeploy to a permanent server and update the webhook URL in the portal."
|
|
6480
|
+
);
|
|
6481
|
+
const s4b = Y2();
|
|
6482
|
+
s4b.start("Waiting for the tunnel to start routing");
|
|
6483
|
+
webhookReady = await waitForTunnelReady(tunnel.url);
|
|
6484
|
+
if (!webhookReady) {
|
|
6485
|
+
s4b.stop("First tunnel is slow to route, trying a fresh one");
|
|
6486
|
+
stopTunnel();
|
|
6487
|
+
tunnel = await startTunnel(3001);
|
|
6488
|
+
webhookUrl = `${tunnel.url}/webhook`;
|
|
6489
|
+
tunnelBase = tunnel.url;
|
|
6490
|
+
const s4c = Y2();
|
|
6491
|
+
s4c.start(`Waiting for the new tunnel to route: ${tunnel.url}`);
|
|
6492
|
+
webhookReady = await waitForTunnelReady(tunnel.url, { budgetMs: 45e3 });
|
|
6493
|
+
s4c.stop(webhookReady ? "Tunnel is routing" : "Tunnel still not routing");
|
|
6494
|
+
} else {
|
|
6495
|
+
s4b.stop("Tunnel is routing");
|
|
6496
|
+
}
|
|
6497
|
+
if (!webhookReady) {
|
|
6498
|
+
M2.warn(
|
|
6499
|
+
`The tunnel edge is still not routing after a retry. This is almost always
|
|
6385
6500
|
one of two things, and neither is your server (it is running on :3001):
|
|
6386
6501
|
1. Open ${tunnel.url}/health in a browser. If it never loads, your network
|
|
6387
6502
|
is blocking cloudflared and no wait will fix it, so try a different network.
|
|
6388
6503
|
2. The tunnel only lives while this wizard runs, so keep this window open.`
|
|
6504
|
+
);
|
|
6505
|
+
}
|
|
6506
|
+
} catch (err) {
|
|
6507
|
+
s4.stop("Tunnel setup failed");
|
|
6508
|
+
M2.warn(`Could not start tunnel: ${err instanceof Error ? err.message : String(err)}`);
|
|
6509
|
+
M2.warn(
|
|
6510
|
+
"To get a public URL manually, try: npx cloudflared tunnel --url http://localhost:3001\nOr deploy to any server and set the URL in partners.oneaddress.io \u2192 My Profile."
|
|
6389
6511
|
);
|
|
6390
6512
|
}
|
|
6391
|
-
} catch (err) {
|
|
6392
|
-
s4.stop("Tunnel setup failed");
|
|
6393
|
-
M2.warn(`Could not start tunnel: ${err instanceof Error ? err.message : String(err)}`);
|
|
6394
|
-
M2.warn(
|
|
6395
|
-
"To get a public URL manually, try: npx cloudflared tunnel --url http://localhost:3001\nOr deploy to any server and set the URL in partners.oneaddress.io \u2192 My Profile."
|
|
6396
|
-
);
|
|
6397
6513
|
}
|
|
6398
6514
|
}
|
|
6399
|
-
if (webhookUrl) {
|
|
6515
|
+
if (webhookUrl && !keepExistingUrl) {
|
|
6400
6516
|
const s5 = Y2();
|
|
6401
6517
|
s5.start("Registering webhook URL in the Partner Portal");
|
|
6402
6518
|
const reg = await registerWebhookUrl(pid, secret, webhookUrl);
|