socket-function 1.2.37 → 1.2.38

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/package.json +1 -1
  2. package/src/forwardPort.ts +30 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "1.2.37",
3
+ "version": "1.2.38",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "dependencies": {
@@ -89,10 +89,11 @@ export async function listPortMappings(): Promise<PortMapping[]> {
89
89
  // live IGD; see test.ts.)
90
90
  const PERMANENT_LEASE = 0;
91
91
 
92
- // A permanent mapping never expires, so we don't renew. We only watch, on this interval, for
93
- // another application having superseded our forward (or a router reboot having wiped it), and log
94
- // loudly if so. We do NOT re-take it last writer wins, so fighting back would just be a war.
95
- const SUPERSEDE_CHECK_INTERVAL = timeInMinute * 30;
92
+ // A permanent mapping never expires, so we don't renew. We only watch, on this interval, for our
93
+ // forward having disappeared (routers do drop them, e.g. on reboot) or another application having
94
+ // taken it. A mapping that is simply gone is free, so we re-take it; one that someone else holds we
95
+ // leave alone and only log — fighting over it would just be a takeover war neither side wins.
96
+ const MAPPING_CHECK_INTERVAL = timeInMinute * 30;
96
97
 
97
98
  /** Outcome of forwardPort. `owned` is true once we hold the router mapping for the port. When
98
99
  * false, `reason` says why: "declined" = noPortStealing and another host holds the port (the
@@ -137,7 +138,9 @@ export async function forwardPort(config: {
137
138
  return (await listPortMappings()).find(m => m.externalPort === externalPort && m.protocol.toUpperCase() === "TCP");
138
139
  }
139
140
 
140
- async function takeOwnership(): Promise<ForwardPortResult> {
141
+ // allowSteal is false for the periodic re-take, so a mapping that changed hands between our check
142
+ // and this call is still left alone.
143
+ async function takeOwnership(options: { allowSteal: boolean }): Promise<ForwardPortResult> {
141
144
  try {
142
145
  const { internalIP, gatewayIP, controlPort, controlURLs } = await resolveGateway();
143
146
 
@@ -145,8 +148,8 @@ export async function forwardPort(config: {
145
148
  // whether a mapping exists at all (so we only delete when there's something to delete).
146
149
  let existing = await readMapping();
147
150
 
148
- if (existing && existing.internalClient !== internalIP && config.noPortStealing) {
149
- console.log(`Port ${externalPort} is already forwarded to ${existing.internalClient} (not us); not stealing it (noPortStealing).`);
151
+ if (existing && existing.internalClient !== internalIP && !options.allowSteal) {
152
+ console.log(`Port ${externalPort} is already forwarded to ${existing.internalClient} (not us); not stealing it.`);
150
153
  return { owned: false, reason: "declined" };
151
154
  }
152
155
 
@@ -195,32 +198,39 @@ export async function forwardPort(config: {
195
198
  return { owned: false, reason: "error" };
196
199
  }
197
200
 
198
- // Re-read the mapping and warn if we no longer own it. Compares against our CURRENT LAN IP, so
199
- // this also catches the case where our own IP changed out from under the old mapping.
200
- async function warnIfSuperseded() {
201
+ // Re-read the mapping and act on having lost it. Compares against our CURRENT LAN IP, so a mapping
202
+ // left over from an IP change of ours reads as someone else's which is the safe way around, as
203
+ // we can't tell it apart from a genuine takeover and would rather not steal.
204
+ async function checkMapping() {
201
205
  try {
202
206
  let currentIP = await getLocalInternalIP();
203
- let ours = (await listPortMappings()).find(m => m.externalPort === externalPort && m.protocol.toUpperCase() === "TCP");
204
- if (!ours) {
205
- console.error(`Port forward ${externalPort} is gone — the mapping was removed (router reboot, or another host deleted it).`);
206
- } else if (currentIP && ours.internalClient !== currentIP) {
207
- console.error(`Port forward ${externalPort} was superseded — it now forwards to ${ours.internalClient} instead of us (${currentIP}). Another application has taken the port.`);
207
+ let mapping = await readMapping();
208
+ if (!mapping) {
209
+ console.error(`Port forward ${externalPort} is gone — the mapping was removed (router reboot, or another host deleted it). Nothing holds the port, so re-taking it.`);
210
+ let retaken = await takeOwnership({ allowSteal: false });
211
+ if (!retaken.owned) {
212
+ console.error(`Failed to re-take port forward ${externalPort} (${retaken.reason}).`);
213
+ }
214
+ return;
215
+ }
216
+ if (currentIP && mapping.internalClient !== currentIP) {
217
+ console.error(`Port forward ${externalPort} was taken — it now forwards to ${mapping.internalClient} instead of us (${currentIP}). Leaving it alone rather than fighting for it.`);
208
218
  }
209
219
  } catch (e) {
210
- console.error(`Failed to check port forward ${externalPort} for supersession`, (e as Error).stack ?? e);
220
+ console.error(`Failed to check port forward ${externalPort}`, (e as Error).stack ?? e);
211
221
  }
212
222
  }
213
223
 
214
- let result = await takeOwnership();
224
+ let result = await takeOwnership({ allowSteal: !config.noPortStealing });
215
225
 
216
226
  // Only monitor permanent mappings we actually own. A finite lease is expected to expire, so its
217
- // disappearance isn't a supersession worth warning about; a declined/failed claim owns nothing.
227
+ // disappearance isn't a loss worth reporting or reclaiming; a declined/failed claim owns nothing.
218
228
  if (result.owned && permanent) {
219
229
  // unref so a script that only wants a one-off forward (and then exits) isn't held open by
220
230
  // the monitor timer; a running server stays alive on its own and the checks keep firing.
221
231
  let timer = setInterval(() => {
222
- void warnIfSuperseded();
223
- }, SUPERSEDE_CHECK_INTERVAL);
232
+ void checkMapping();
233
+ }, MAPPING_CHECK_INTERVAL);
224
234
  timer.unref?.();
225
235
  }
226
236