connectbase-client 0.13.0 → 0.14.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.
Files changed (2) hide show
  1. package/dist/cli.js +89 -4
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -26,6 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/cli.ts
27
27
  var fs = __toESM(require("fs"));
28
28
  var path = __toESM(require("path"));
29
+ var os = __toESM(require("os"));
29
30
  var crypto = __toESM(require("crypto"));
30
31
  var https = __toESM(require("https"));
31
32
  var http = __toESM(require("http"));
@@ -355,11 +356,11 @@ function getProjectRoot() {
355
356
  var CONSOLE_URL = DEFAULT_BASE_URL.replace("api.", "");
356
357
  function openBrowser(url) {
357
358
  const { exec } = require("child_process");
358
- const platform = process.platform;
359
+ const platform2 = process.platform;
359
360
  let command;
360
- if (platform === "darwin") {
361
+ if (platform2 === "darwin") {
361
362
  command = `open "${url}"`;
362
- } else if (platform === "win32") {
363
+ } else if (platform2 === "win32") {
363
364
  command = `start "" "${url}"`;
364
365
  } else {
365
366
  command = `xdg-open "${url}"`;
@@ -1146,6 +1147,68 @@ ${colors.blue}?${colors.reset} \uC571 \uC120\uD0DD (\uBC88\uD638): `);
1146
1147
  success(`\uC571 \uC0DD\uC131 \uC644\uB8CC: ${createData.app_name}`);
1147
1148
  return createData.app_id;
1148
1149
  }
1150
+ function getTunnelLockDir() {
1151
+ const platform2 = os.platform();
1152
+ if (platform2 === "darwin") {
1153
+ return path.join(os.homedir(), "Library", "Application Support", "connectbase", "tunnel-locks");
1154
+ } else if (platform2 === "win32") {
1155
+ return path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "connectbase", "tunnel-locks");
1156
+ }
1157
+ const stateHome = process.env.XDG_STATE_HOME || path.join(os.homedir(), ".local", "state");
1158
+ return path.join(stateHome, "connectbase", "tunnel-locks");
1159
+ }
1160
+ function isProcessAlive(pid) {
1161
+ try {
1162
+ process.kill(pid, 0);
1163
+ return true;
1164
+ } catch (e) {
1165
+ return e.code === "EPERM";
1166
+ }
1167
+ }
1168
+ function acquireTunnelLock(appID, port, force) {
1169
+ const lockDir = getTunnelLockDir();
1170
+ fs.mkdirSync(lockDir, { recursive: true });
1171
+ const lockPath = path.join(lockDir, `${appID}-${port}.lock`);
1172
+ const lockData = {
1173
+ pid: process.pid,
1174
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1175
+ appID,
1176
+ port,
1177
+ host: os.hostname(),
1178
+ version: VERSION
1179
+ };
1180
+ if (!force) {
1181
+ try {
1182
+ const fd = fs.openSync(lockPath, "wx");
1183
+ fs.writeSync(fd, JSON.stringify(lockData, null, 2));
1184
+ fs.closeSync(fd);
1185
+ return lockPath;
1186
+ } catch (e) {
1187
+ if (e.code !== "EEXIST") {
1188
+ warn(`lockfile \uC0DD\uC131 \uC2E4\uD328: ${e.message}`);
1189
+ return null;
1190
+ }
1191
+ }
1192
+ try {
1193
+ const existing = JSON.parse(fs.readFileSync(lockPath, "utf-8"));
1194
+ if (isProcessAlive(existing.pid)) {
1195
+ error(`\uC774\uBBF8 \uC2E4\uD589 \uC911\uC778 \uD130\uB110\uC774 \uC788\uC2B5\uB2C8\uB2E4: PID ${existing.pid}, \uC2DC\uC791 ${existing.startedAt}, \uD638\uC2A4\uD2B8 ${existing.host}`);
1196
+ info("\uC911\uBCF5 \uC2E4\uD589\uC744 \uBB34\uC2DC\uD558\uB824\uBA74 --force \uC635\uC158\uC744 \uC0AC\uC6A9\uD558\uC138\uC694");
1197
+ process.exit(2);
1198
+ }
1199
+ warn(`stale lockfile \uBC1C\uACAC (PID ${existing.pid} \uC885\uB8CC\uB428), \uB36E\uC5B4\uC501\uB2C8\uB2E4`);
1200
+ } catch {
1201
+ }
1202
+ }
1203
+ fs.writeFileSync(lockPath, JSON.stringify(lockData, null, 2));
1204
+ return lockPath;
1205
+ }
1206
+ function releaseTunnelLock(lockPath) {
1207
+ try {
1208
+ fs.unlinkSync(lockPath);
1209
+ } catch {
1210
+ }
1211
+ }
1149
1212
  async function startTunnel(port, config, tunnelOpts) {
1150
1213
  let tunnelKey = config.secretKey || (config.publicKey?.startsWith("cb_sk_") ? config.publicKey : "");
1151
1214
  if (!tunnelKey) {
@@ -1198,6 +1261,7 @@ async function startTunnel(port, config, tunnelOpts) {
1198
1261
  }
1199
1262
  } catch {
1200
1263
  }
1264
+ const lockPath = acquireTunnelLock(appId, port, tunnelOpts?.force ?? false);
1201
1265
  const tunnelServerUrl = getTunnelServerUrl(config.baseUrl);
1202
1266
  const parsedUrl = new URL(tunnelServerUrl);
1203
1267
  const isHttps = parsedUrl.protocol === "https:";
@@ -1212,8 +1276,17 @@ async function startTunnel(port, config, tunnelOpts) {
1212
1276
  const maxReconnectAttempts = 10;
1213
1277
  let shouldReconnect = true;
1214
1278
  let socket = null;
1279
+ let lockReleased = false;
1280
+ const releaseLock = () => {
1281
+ if (lockPath && !lockReleased) {
1282
+ lockReleased = true;
1283
+ releaseTunnelLock(lockPath);
1284
+ }
1285
+ };
1286
+ process.on("exit", releaseLock);
1215
1287
  const cleanup = () => {
1216
1288
  shouldReconnect = false;
1289
+ releaseLock();
1217
1290
  if (socket) {
1218
1291
  try {
1219
1292
  socket.write(createWsCloseFrame(1e3));
@@ -1344,7 +1417,13 @@ ${colors.dim}Ctrl+C\uB85C \uC885\uB8CC${colors.reset}
1344
1417
  forwardRequest(msg, sock, localPort);
1345
1418
  break;
1346
1419
  case "tunnel_error":
1347
- error(`\uD130\uB110 \uC5D0\uB7EC: ${msg.message}`);
1420
+ if (msg.code === "replaced") {
1421
+ error(`\u26A0 \uB2E4\uB978 \uC138\uC158\uC774 \uAC19\uC740 \uC571+\uD3EC\uD2B8(${appId}:${localPort})\uB85C \uC5F0\uACB0\uB418\uC5B4 \uC774 \uC138\uC158\uC774 \uC885\uB8CC\uB429\uB2C8\uB2E4. \uB3D9\uC77C \uBA38\uC2E0\uC5D0\uC11C \uB450 \uBC88 \uC2E4\uD589\uD558\uC9C0 \uB9C8\uC138\uC694.`);
1422
+ shouldReconnect = false;
1423
+ setTimeout(() => process.exit(1), 500);
1424
+ } else {
1425
+ error(`\uD130\uB110 \uC5D0\uB7EC: ${msg.error || msg.message || "\uC54C \uC218 \uC5C6\uB294 \uC5D0\uB7EC"}`);
1426
+ }
1348
1427
  break;
1349
1428
  case "ping":
1350
1429
  sock.write(createWsTextFrame(JSON.stringify({ type: "pong" })));
@@ -1497,6 +1576,7 @@ ${colors.yellow}\uC635\uC158:${colors.reset}
1497
1576
  -u, --base-url <url> \uC11C\uBC84 URL (\uAE30\uBCF8: ${DEFAULT_BASE_URL})
1498
1577
  -t, --timeout <sec> \uD130\uB110 \uC694\uCCAD \uD0C0\uC784\uC544\uC6C3 (\uCD08, tunnel \uC804\uC6A9)
1499
1578
  --max-body <MB> \uD130\uB110 \uCD5C\uB300 \uBC14\uB514 \uD06C\uAE30 (MB, tunnel \uC804\uC6A9)
1579
+ --force \uD130\uB110 lockfile \uBB34\uC2DC (\uC911\uBCF5 \uC2E4\uD589 \uAC15\uC81C, tunnel \uC804\uC6A9)
1500
1580
  -d, --dev Dev \uD658\uACBD\uC5D0 \uBC30\uD3EC (deploy \uC804\uC6A9)
1501
1581
  (docs, mcp\uB294 \uBAA8\uB178\uB808\uD3EC\uB97C \uC790\uB3D9 \uAC10\uC9C0\uD558\uC5EC \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8\uC5D0 \uC0DD\uC131)
1502
1582
  -h, --help \uB3C4\uC6C0\uB9D0 \uD45C\uC2DC
@@ -1562,6 +1642,8 @@ function parseArgs(args) {
1562
1642
  result.options.maxBody = args[++i];
1563
1643
  } else if (arg === "-a" || arg === "--app") {
1564
1644
  result.options.appId = args[++i];
1645
+ } else if (arg === "--force") {
1646
+ result.options.force = "true";
1565
1647
  } else if (arg === "-d" || arg === "--dev") {
1566
1648
  result.options.dev = "true";
1567
1649
  } else if (arg === "-h" || arg === "--help") {
@@ -1645,6 +1727,9 @@ async function main() {
1645
1727
  if (parsed.options.appId) {
1646
1728
  tunnelOpts.appId = parsed.options.appId;
1647
1729
  }
1730
+ if (parsed.options.force === "true") {
1731
+ tunnelOpts.force = true;
1732
+ }
1648
1733
  await startTunnel(port, config, tunnelOpts);
1649
1734
  } else {
1650
1735
  error(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC5B4: ${parsed.command}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "connectbase-client",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Connect Base JavaScript/TypeScript SDK for browser and Node.js",
5
5
  "repository": {
6
6
  "type": "git",