Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@flowfuse/device-agent

Package Overview
Dependencies
Maintainers
2
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@flowfuse/device-agent - npm Package Compare versions

Comparing version 1.15.0 to 2.0.0

7

CHANGELOG.md

@@ -0,1 +1,8 @@

#### 2.0.0: Release
- Enable WebSocket Affinity in the device agent (#213) @hardillb
- Fix WS readystate crash (#221) @Steve-Mcl
- Permit Device Agent to automatically re-establish Editor Tunnel after a restart (#220) @Steve-Mcl
- Fix console.log(JSON) (#219) @hardillb
#### 1.15.0: Release

@@ -2,0 +9,0 @@

36

lib/agent.js

@@ -34,2 +34,3 @@ const { IntervalJitter } = require('./IntervalJitter')

this.currentState = 'unknown'
this.editorToken = null
// ensure licensed property is present (default to null)

@@ -54,2 +55,3 @@ if (utils.hasProperty(this.config, 'licensed') === false) {

this.currentApplication = null
this.editorToken = null
} else {

@@ -63,2 +65,3 @@ // New format

this.config.licensed = config.licensed || null
this.editorToken = config.editorToken || null
}

@@ -109,3 +112,4 @@ this.printAgentStatus()

mode: this.currentMode,
licensed: this.config.licensed
licensed: this.config.licensed,
editorToken: this.editorToken
}))

@@ -256,2 +260,3 @@ }

// exiting developer mode
this.editorToken = null // clear the discarded token
let _launcher = this.launcher

@@ -353,3 +358,3 @@ if (!_launcher) {

/** A flag to inhibit updates if we are in developer mode */
const inhibitUpdates = this.currentMode === 'developer'
const developerMode = this.currentMode === 'developer'

@@ -362,3 +367,3 @@ /** A flag to indicate execution should skip to the update step */

// Wipe the local configuration
if (inhibitUpdates === false) {
if (developerMode === false) {
this.stop()

@@ -370,2 +375,3 @@ this.currentSnapshot = null

this.currentMode = null
this.editorToken = null
await this.saveProject()

@@ -375,3 +381,3 @@ this.currentState = 'stopped'

}
} else if (!skipToUpdate && inhibitUpdates === false && newState.application === null && this.currentOwnerType === 'application') {
} else if (!skipToUpdate && developerMode === false && newState.application === null && this.currentOwnerType === 'application') {
if (this.currentApplication) {

@@ -404,3 +410,3 @@ debug('Removed from application')

this.updating = false
} else if (!skipToUpdate && inhibitUpdates === false && newState.project === null && this.currentOwnerType === 'project') {
} else if (!skipToUpdate && developerMode === false && newState.project === null && this.currentOwnerType === 'project') {
if (this.currentProject) {

@@ -433,3 +439,3 @@ debug('Removed from project')

this.updating = false
} else if (!skipToUpdate && inhibitUpdates === false && newState.snapshot === null) {
} else if (!skipToUpdate && developerMode === false && newState.snapshot === null) {
// Snapshot removed, but project/application still set

@@ -486,3 +492,3 @@ if (this.currentSnapshot) {

const applicationUpdatePending = !!(newState.ownerType === 'application' && !this.currentApplication && newState.application)
if (unknownOrStopped && inhibitUpdates && snapShotUpdatePending && (projectUpdatePending || applicationUpdatePending)) {
if (unknownOrStopped && developerMode && snapShotUpdatePending && (projectUpdatePending || applicationUpdatePending)) {
info('Developer Mode: no flows found - updating to latest snapshot')

@@ -493,3 +499,3 @@ this.currentProject = newState.project

updateSettings = true
} else if (inhibitUpdates === false) {
} else if (developerMode === false) {
if (utils.hasProperty(newState, 'project') && (!this.currentSnapshot || newState.project !== this.currentProject)) {

@@ -544,2 +550,5 @@ info('New instance assigned')

this.mqttClient.setApplication(this.currentApplication)
if (developerMode && this.editorToken) {
this.mqttClient.startTunnel(this.editorToken)
}
}

@@ -590,2 +599,5 @@ this.checkIn(2)

this.mqttClient.setApplication(this.currentApplication)
if (developerMode && this.editorToken) {
this.mqttClient.startTunnel(this.editorToken)
}
}

@@ -694,2 +706,10 @@ this.checkIn(2)

}
async saveEditorToken (token) {
const changed = this.editorToken !== token
this.editorToken = token
if (changed) {
await this.saveProject()
}
}
}

@@ -696,0 +716,0 @@

@@ -25,2 +25,3 @@ const { info, warn, debug } = require('../logging/log')

this.options = options || {}
this.affinity = undefined

@@ -54,2 +55,3 @@ // How long to wait before attempting to reconnect. Start at 500ms - back

const thisTunnel = this
let unexpectedPacketMonitor = 0
if (this.socket) {

@@ -62,8 +64,31 @@ this.close()

// * Enable Device Editor (Step 8) - (device->forge:WS) Initiate WS connection (with token)
const headers = {
'x-access-token': this.options.token
}
if (this.affinity) {
headers.cookie = `FFSESSION=${this.affinity}`
}
const socket = newWsConnection(forgeWSEndpoint, {
headers: {
'x-access-token': this.options.token
headers
})
socket.on('upgrade', (evt) => {
if (evt.headers && evt.headers['set-cookie']) {
const cookies = evt.headers['set-cookie']
if (Array.isArray(cookies)) {
cookies.forEach(cookie => {
const parts = cookie.split(';')[0].split(['='])
if (parts === 'FFSESSION') {
this.affinity = parts[1]
}
})
} else {
const parts = cookies.split(';')[0].split(['='])
if (parts === 'FFSESSION') {
this.affinity = parts[1]
}
}
}
})
socket.onopen = (evt) => {
unexpectedPacketMonitor = 0
info('Editor tunnel connected')

@@ -106,3 +131,5 @@ // Reset reconnectDelay

// console.log(`[${request.id}] R>E`, sendData.body)
this.socket?.send(JSON.stringify(sendData))
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(sendData))
}
})

@@ -122,7 +149,9 @@ // Now the local comms is connected, send anything

// websocket to match
this.socket?.send(JSON.stringify({
id: request.id,
ws: true,
closed: true
}))
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({
id: request.id,
ws: true,
closed: true
}))
}
thisTunnel.wsClients[request.id]?.removeAllListeners()

@@ -160,4 +189,9 @@ thisTunnel.wsClients[request.id] = null

} else {
warn(`[${request.id}] Unexpected editor comms packet ${JSON.stringify(request, null, 4)}`)
this.close(1006, 'Non-connect packet received for unknown connection id') // 1006 = Abnormal closure
if (unexpectedPacketMonitor > 1) {
unexpectedPacketMonitor = 0
warn(`[${request.id}] Unexpected editor comms packet ${JSON.stringify(request, null, 4)}`)
this.close(1006, 'Non-connect packet received for unknown connection id') // 1006 = Abnormal closure
} else {
unexpectedPacketMonitor++
}
}

@@ -199,8 +233,10 @@ } else {

// send response back to the forge
this.socket?.send(JSON.stringify({
id: request.id,
headers: response.headers,
body: response.rawBody,
status: response.statusCode
}))
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({
id: request.id,
headers: response.headers,
body: response.rawBody,
status: response.statusCode
}))
}
}).catch(_err => {

@@ -211,7 +247,9 @@ // debug(`proxy [${request.method}] ${fullUrl} : error ${_err.toString()}`)

// console.log(JSON.stringify(request))
this.socket?.send(JSON.stringify({
id: request.id,
body: undefined,
status: 404
}))
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({
id: request.id,
body: undefined,
status: 404
}))
}
})

@@ -218,0 +256,0 @@ }

@@ -27,2 +27,6 @@ const LogBuffer = require('./logBuffer')

}
if (!Object.hasOwn(jsMsg, 'ts') && !Object.hasOwn(jsMsg, 'level')) {
// not a NR log message
jsMsg = { ts: Date.now(), level: '', msg }
}
const date = new Date(jsMsg.ts)

@@ -29,0 +33,0 @@ if (typeof jsMsg.msg !== 'string') {

@@ -103,19 +103,3 @@ const mqtt = require('mqtt')

} else if (msg.command === 'startEditor') {
info('Enabling remote editor access')
if (this.tunnel) {
this.tunnel.close()
this.tunnel = null
}
if (!this.agent.launcher) {
info('No running Node-RED instance, not starting editor')
this.sendCommandResponse(msg, { connected: false, token: msg?.payload?.token, error: 'noNRRunning' })
return
}
// * Enable Device Editor (Step 6) - (forge:MQTT->device) Create the tunnel on the device
this.tunnel = EditorTunnel.create(this.config, { token: msg?.payload?.token })
// * Enable Device Editor (Step 7) - (device) Begin the device tunnel connect process
const result = await this.tunnel.connect()
// * Enable Device Editor (Step 10) - (device->forge:MQTT) Send a response to the platform
this.sendCommandResponse(msg, { connected: result, token: msg?.payload?.token })
this.sendStatus()
await this.startTunnel(msg.payload?.token, msg)
return

@@ -285,2 +269,43 @@ } else if (msg.command === 'stopEditor') {

}
async startTunnel (token, msg) {
info('Enabling remote editor access')
try {
if (this.tunnel) {
this.tunnel.close()
this.tunnel = null
}
if (!this.agent.launcher) {
info('No running Node-RED instance, not starting editor')
if (msg) {
this.sendCommandResponse(msg, { connected: false, token, error: 'noNRRunning' })
}
return
}
// * Enable Device Editor (Step 6) - (forge:MQTT->device) Create the tunnel on the device
this.tunnel = EditorTunnel.create(this.config, { token })
// * Enable Device Editor (Step 7) - (device) Begin the device tunnel connect process
const result = await this.tunnel.connect()
// store the token for later use (i.e. device agent is restarted)
await this.saveEditorToken(result ? token : null)
if (msg) {
// * Enable Device Editor (Step 10) - (device->forge:MQTT) Send a response to the platform
this.sendCommandResponse(msg, { connected: result, token })
}
} catch (err) {
warn(`Error starting editor tunnel: ${err}`)
if (msg) {
this.sendCommandResponse(msg, { connected: false, token, error: err.toString() })
}
}
this.sendStatus()
}
async saveEditorToken (token) {
await this.agent?.saveEditorToken(token)
}
}

@@ -287,0 +312,0 @@

{
"name": "@flowfuse/device-agent",
"version": "1.15.0",
"version": "2.0.0",
"description": "An Edge Agent for running Node-RED instances deployed from the FlowFuse Platform",

@@ -5,0 +5,0 @@ "exports": {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc