@samitouri / QOSami-HFS / commits / dea8f2e5

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 }