mockaton
Version:
HTTP Mock Server
118 lines (100 loc) • 2.82 kB
JavaScript
import { join } from 'node:path'
import { watch } from 'node:fs'
import { EventEmitter } from 'node:events'
import {
HEADER_SYNC_VERSION,
LONG_POLL_SERVER_TIMEOUT
} from './ApiConstants.js'
import { config } from './config.js'
import { sendJSON } from './utils/http-response.js'
import { isFile, isDirectory } from './utils/fs.js'
import * as staticCollection from './staticCollection.js'
import * as mockBrokerCollection from './mockBrokersCollection.js'
/**
* ARR Event = Add, Remove, or Rename Mock
*
* The emitter is debounced so it handles e.g. bulk deletes,
* and also renames, which are two events (delete + add).
*/
const uiSyncVersion = new class extends EventEmitter {
delay = Number(process.env.MOCKATON_WATCHER_DEBOUNCE_MS ?? 80)
version = 0
increment = /** @type {function} */ this.#debounce(() => {
this.version++
super.emit('ARR')
})
subscribe(listener) {
this.once('ARR', listener)
}
unsubscribe(listener) {
this.removeListener('ARR', listener)
}
#debounce(fn) { // TESTME
let timer
return () => {
clearTimeout(timer)
timer = setTimeout(fn, this.delay)
}
}
}
export function watchMocksDir() {
const dir = config.mocksDir
watch(dir, { recursive: true, persistent: false }, (_, file) => {
if (!file)
return
if (isDirectory(join(dir, file))) {
mockBrokerCollection.init()
uiSyncVersion.increment()
}
else if (!isFile(join(dir, file))) { // file deleted
mockBrokerCollection.unregisterMock(file)
uiSyncVersion.increment()
}
else if (mockBrokerCollection.registerMock(file, Boolean('isFromWatcher')))
uiSyncVersion.increment()
else {
// ignore file edits
}
})
}
export function watchStaticDir() {
const dir = config.staticDir
if (!dir)
return
watch(dir, { recursive: true, persistent: false }, (_, file) => {
if (!file)
return
if (isDirectory(join(dir, file))) {
staticCollection.init()
uiSyncVersion.increment()
}
else if (!isFile(join(dir, file))) { // file deleted
staticCollection.unregisterMock(file)
uiSyncVersion.increment()
}
else if (staticCollection.registerMock(file))
uiSyncVersion.increment()
else {
// ignore file edits
}
})
}
/** Realtime notify ARR Events */
export function longPollClientSyncVersion(req, response) {
const clientVersion = req.headers[HEADER_SYNC_VERSION]
if (clientVersion !== undefined && uiSyncVersion.version !== Number(clientVersion)) {
// e.g., tab was hidden while new mocks were added or removed
sendJSON(response, uiSyncVersion.version)
return
}
function onARR() {
uiSyncVersion.unsubscribe(onARR)
sendJSON(response, uiSyncVersion.version)
}
response.setTimeout(LONG_POLL_SERVER_TIMEOUT, onARR)
req.on('error', () => {
uiSyncVersion.unsubscribe(onARR)
response.destroy()
})
uiSyncVersion.subscribe(onARR)
}