Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permit Device Agent to automatically re-establish Editor Tunnel after a restart #220

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions lib/agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Agent {
// Track the local state of the agent. Start in 'unknown' state so
// that the first MQTT check-in will trigger a response
this.currentState = 'unknown'
this.editorToken = null
// ensure licensed property is present (default to null)
if (utils.hasProperty(this.config, 'licensed') === false) {
this.config.licensed = null
Expand All @@ -50,6 +51,7 @@ class Agent {
}
this.currentProject = null
this.currentApplication = null
this.editorToken = null
} else {
// New format
this.currentApplication = config.project ? null : (config.application || null)
Expand All @@ -58,6 +60,7 @@ class Agent {
this.currentSettings = config.settings || null
this.currentMode = config.mode || 'autonomous'
this.config.licensed = config.licensed || null
this.editorToken = config.editorToken || null
}
this.printAgentStatus()
} catch (err) {
Expand Down Expand Up @@ -103,7 +106,8 @@ class Agent {
snapshot: this.currentSnapshot,
settings: this.currentSettings,
mode: this.currentMode,
licensed: this.config.licensed
licensed: this.config.licensed,
editorToken: this.editorToken
}))
}

Expand Down Expand Up @@ -249,6 +253,7 @@ class Agent {
await this.saveProject()
} else {
// exiting developer mode
this.editorToken = null // clear the discarded token
let _launcher = this.launcher
if (!_launcher) {
// create a temporary launcher to read the current snapshot on disk
Expand Down Expand Up @@ -345,26 +350,27 @@ class Agent {
}

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

/** A flag to indicate execution should skip to the update step */
const skipToUpdate = newState?.reloadSnapshot === true

if (newState === null) {
// The agent should not be running (bad credentials/device details)
// Wipe the local configuration
if (inhibitUpdates === false) {
if (developerMode === false) {
this.stop()
this.currentSnapshot = null
this.currentApplication = null
this.currentProject = null
this.currentSettings = null
this.currentMode = null
this.editorToken = null
await this.saveProject()
this.currentState = 'stopped'
this.updating = false
}
} 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) {
debug('Removed from application')
}
Expand Down Expand Up @@ -392,7 +398,7 @@ class Agent {
await this.saveProject()
this.currentState = 'stopped'
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) {
debug('Removed from project')
}
Expand Down Expand Up @@ -420,7 +426,7 @@ class Agent {
await this.saveProject()
this.currentState = 'stopped'
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
if (this.currentSnapshot) {
debug('Active snapshot removed')
Expand Down Expand Up @@ -472,13 +478,13 @@ class Agent {
const snapShotUpdatePending = !!(!this.currentSnapshot && newState.snapshot)
const projectUpdatePending = !!(newState.ownerType === 'project' && !this.currentProject && newState.project)
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')
this.currentProject = newState.project
this.currentApplication = newState.application
updateSnapshot = true
updateSettings = true
} else if (inhibitUpdates === false) {
} else if (developerMode === false) {
if (utils.hasProperty(newState, 'project') && (!this.currentSnapshot || newState.project !== this.currentProject)) {
info('New instance assigned')
this.currentApplication = null
Expand Down Expand Up @@ -528,6 +534,9 @@ class Agent {
if (this.mqttClient) {
this.mqttClient.setProject(this.currentProject)
this.mqttClient.setApplication(this.currentApplication)
if (developerMode && this.editorToken) {
this.mqttClient.startTunnel(this.editorToken)
}
}
this.checkIn(2)
this.currentState = 'stopped'
Expand Down Expand Up @@ -573,6 +582,9 @@ class Agent {
if (this.mqttClient) {
this.mqttClient.setProject(this.currentProject)
this.mqttClient.setApplication(this.currentApplication)
if (developerMode && this.editorToken) {
this.mqttClient.startTunnel(this.editorToken)
}
}
this.checkIn(2)
} catch (err) {
Expand Down Expand Up @@ -676,6 +688,14 @@ class Agent {
}
})
}

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

module.exports = {
Expand Down
4 changes: 4 additions & 0 deletions lib/logging/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ function NRlog (msg) {
} catch (eee) {
jsMsg = { ts: Date.now(), level: '', msg }
}
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)
if (typeof jsMsg.msg !== 'string') {
jsMsg.msg = JSON.stringify(jsMsg.msg)
Expand Down
59 changes: 42 additions & 17 deletions lib/mqtt.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,7 @@ class MQTTClient {
this.logEnabled = false
return
} 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
} else if (msg.command === 'stopEditor') {
if (this.tunnel) {
Expand Down Expand Up @@ -281,6 +265,47 @@ class MQTTClient {
}
})
}

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)
}
}

module.exports = {
Expand Down
Loading