plugins: event publicIpsChanged
Massimo Melina committed
May 31, 2024 at 00:26 UTC
dea8f2e5fa18bb33ea784ee9af56fceeee5b023a
8 files changed
+53
-25
dev-plugins.md
+9
-7
@@ -388,10 +388,10 @@ The example above will return false only when the file is NOT ending with .jpg,
388
This section is still partially documented, and you may need to have a look at the sources for further details.
389
390
- `deleting`
391
- - parameters: { node, ctx }
392
- - called just before trying to delete a file or folder (which still may not exist and fail)
393
- - async supported
394
- - stoppable
391
+ - parameters: { node, ctx }
392
+ - called just before trying to delete a file or folder (which still may not exist and fail)
393
+ - async supported
394
+ - stoppable
395
- `logout`
396
- `config ready`
397
- `config.KEY` where KEY is the key of a config that has changed
@@ -413,10 +413,12 @@ This section is still partially documented, and you may need to have a look at t
413
- `pluginStopped`
414
- `pluginStarted`
415
- `uploadStart`
416
- - parameters: { ctx, writeStream }
417
- - stoppable
418
- - return: callback to call when upload is finished
416
+ - parameters: { ctx, writeStream }
417
+ - stoppable
418
+ - return: callback to call when upload is finished
419
- `uploadFinished`
420
+- `publicIpsChanged`
421
+ - parameters: { IPs, IP4, IP6, IPX }
422
423
# The `ctx` object
424
src/api.log.ts
+1
-1
@@ -6,7 +6,7 @@ import events from './events'
6
import { loggers } from './log'
7
import { SendListReadable } from './SendList'
8
import { serveFile } from './serveFile'
9
-import { ips } from './ip'
9
+import { ips } from './ips'
10
11
export default {
12
async get_log_file({ file = 'log', range = '' }, ctx) {
src/const.ts
+1
-1
@@ -7,7 +7,7 @@ import { mkdirSync } from 'fs'
7
import { basename, dirname, join } from 'path'
8
export * from './cross-const'
9
10
-export const API_VERSION = 8.82
10
+export const API_VERSION = 8.83
11
export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
12
export const HFS_REPO = 'rejetto/hfs'
13
src/ddns.ts
+24
-13
@@ -1,5 +1,5 @@
1
import { defineConfig } from './config'
2
-import { Callback, CFG, HOUR, repeat, replace, splitAt } from './cross'
2
+import { CFG, HOUR, repeat, replace, splitAt } from './cross'
3
import _ from 'lodash'
4
import { httpWithBody } from './util-http'
5
import { isIPv4 } from 'node:net'
@@ -11,23 +11,34 @@ import { getPublicIps } from './nat'
11
// optionally you can append '>' and a regular expression to determine what body is considered successful
12
const dynamicDnsUrl = defineConfig(CFG.dynamic_dns_url, '')
13
14
-let stop: Callback | undefined
14
+// listening this event will trigger public-ips fetching
15
+const EVENT = 'publicIpsChanged'
16
+let stopFetching: any
17
+let lastIps: any
18
+events.onListeners(EVENT, cbs => {
19
+ stopFetching?.()
20
+ if (!cbs?.size()) return
21
+ stopFetching = repeat(HOUR, async () => {
22
+ const IPs = await getPublicIps()
23
+ if (_.isEqual(lastIps, IPs)) return
24
+ events.emit(EVENT, {
25
+ IPs,
26
+ IPX: IPs[0] || '',
27
+ IP4: _.find(IPs, isIPv4) || '',
28
+ IP6: _.find(IPs, isIPv6) || '',
29
+ })
30
+ })
31
+})
32
+
33
export interface DynamicDnsResult { ts: string, error: string, url: string }
34
+let stopEvent: any
35
dynamicDnsUrl.sub(v => {
17
- stop?.()
36
+ stopEvent?.()
37
if (!v) return
19
- let lastIps: any
20
- stop = repeat(HOUR, async () => {
21
- const ips = await getPublicIps()
22
- if (_.isEqual(lastIps, ips)) return
23
- lastIps = ips
38
+ stopEvent = events.on(EVENT, async map => {
39
const all: DynamicDnsResult[] = await Promise.all(v.split('\n').map(async line => {
40
const [templateUrl, re] = splitAt('>', line)
26
- const url = replace(templateUrl, {
27
- IPX: ips[0] || '',
28
- IP4: _.find(ips, isIPv4) || '',
29
- IP6: _.find(ips, isIPv6) || '',
30
- }, '$')
41
+ const url = replace(templateUrl, map, '$')
42
const error = await httpWithBody(url, { httpThrow: false, headers: { 'User-Agent': "HFS/" + VERSION } }) // UA specified as requested by no-ip guidelines
43
.then(async res => {
44
const str = String(res.body).trim()
src/events.ts
+13
-2
@@ -2,6 +2,8 @@
2
3
type Listener = (...args: any[]) => unknown
4
type Listeners = Set<Listener>
5
+const LISTENERS_SUFFIX = '\0listeners'
6
+
7
export class BetterEventEmitter {
8
protected listeners = new Map<string, Listeners>()
9
on(event: string | string[], listener: Listener, { warnAfter=10 }={}) {
@@ -14,12 +16,21 @@ export class BetterEventEmitter {
16
cbs.add(listener)
17
if (cbs.size > warnAfter)
18
console.warn("Warning: many events listeners for ", e)
19
+ this.emit(e + LISTENERS_SUFFIX, cbs)
20
}
21
return () => {
19
- for (const e of event)
20
- this.listeners.get(e)?.delete(listener)
22
+ for (const e of event) {
23
+ const cbs = this.listeners.get(e)
24
+ if (!cbs) continue
25
+ cbs.delete(listener)
26
+ this.emit(e + LISTENERS_SUFFIX, cbs)
27
+ }
28
}
29
}
30
+ // call me when listeners for event have changed
31
+ onListeners(event: string, listener: Listener) {
32
+ return this.on(event + LISTENERS_SUFFIX, listener)
33
+ }
34
once(event: string, listener?: Listener) {
35
return new Promise<any[]>(resolve => {
36
const off = this.on(event, function(...args){
src/index.ts
+1
-1
@@ -25,7 +25,7 @@ import './geo'
25
import { geoFilter } from './geo'
26
import { rootsMiddleware } from './roots'
27
import events from './events'
28
-import { trackIpsMw } from './ip'
28
+import { trackIpsMw } from './ips'
29
import { storedMap } from './persistence'
30
31
ok(_.intersection(Object.keys(frontEndApis), Object.keys(adminApis)).length === 0) // they share same endpoints, don't clash
src/ips.ts
renamed
src/util-http.ts
+4
@@ -33,6 +33,10 @@ export function httpStream(url: string, { body, jar, noRedirect, httpThrow, ...o
33
options.headers ??= {}
34
if (body) {
35
options.method ||= 'POST'
36
+ if (_.isPlainObject(body)) {
37
+ options.headers['Content-Type'] ??= 'application/json'
38
+ body = JSON.stringify(body)
39
+ }
40
if (!(body instanceof Readable))
41
options.headers['Content-Length'] ??= Buffer.byteLength(body)
42
}