@flowfuse/nr-launcher 1.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/LICENSE +178 -0
  3. package/README.md +28 -0
  4. package/index.js +148 -0
  5. package/lib/admin.js +95 -0
  6. package/lib/auditLogger/index.js +41 -0
  7. package/lib/auth/adminAuth.js +77 -0
  8. package/lib/auth/httpAuthMiddleware.js +71 -0
  9. package/lib/auth/httpAuthPlugin.js +10 -0
  10. package/lib/auth/strategy.js +34 -0
  11. package/lib/context/FFContextStorage.js +422 -0
  12. package/lib/context/index.js +9 -0
  13. package/lib/context/memoryCache.js +156 -0
  14. package/lib/launcher.js +695 -0
  15. package/lib/logBuffer.js +57 -0
  16. package/lib/resources/resourcePlugin.js +20 -0
  17. package/lib/resources/sample.js +57 -0
  18. package/lib/resources/sampleBuffer.js +85 -0
  19. package/lib/runtimeSettings.js +320 -0
  20. package/lib/storage/index.js +92 -0
  21. package/lib/storage/libraryPlugin.js +90 -0
  22. package/lib/theme/LICENSE +178 -0
  23. package/lib/theme/README.md +24 -0
  24. package/lib/theme/common/forge-common.css +108 -0
  25. package/lib/theme/common/forge-common.js +75 -0
  26. package/lib/theme/forge-dark/forge-dark-custom.css +2 -0
  27. package/lib/theme/forge-dark/forge-dark-custom.js +1 -0
  28. package/lib/theme/forge-dark/forge-dark-monaco.json +213 -0
  29. package/lib/theme/forge-dark/forge-dark-theme.css +12 -0
  30. package/lib/theme/forge-dark/forge-dark.js +61 -0
  31. package/lib/theme/forge-light/forge-light-custom.css +2 -0
  32. package/lib/theme/forge-light/forge-light-custom.js +1 -0
  33. package/lib/theme/forge-light/forge-light-monaco.json +227 -0
  34. package/lib/theme/forge-light/forge-light-theme.css +12 -0
  35. package/lib/theme/forge-light/forge-light.js +62 -0
  36. package/package.json +72 -0
  37. package/resources/favicon-16x16.png +0 -0
  38. package/resources/favicon-32x32.png +0 -0
  39. package/resources/favicon.ico +0 -0
  40. package/resources/ff-nr.png +0 -0
@@ -0,0 +1,57 @@
1
+ const os = require('node:os')
2
+ const crypto = require('crypto')
3
+
4
+ const instanceId = crypto.createHash('md5').update(os.hostname()).digest('hex').substring(0, 4)
5
+ class LogBuffer {
6
+ constructor (size = 1000) {
7
+ this.size = size
8
+ this.buffer = new Array(this.size)
9
+ this.head = 0
10
+ this.wrapped = false
11
+ this.lastLogTimestamp = 0
12
+ this.lastLogTimestampCount = 0
13
+ }
14
+
15
+ add (logEntry) {
16
+ if (!logEntry.ts) {
17
+ logEntry.ts = Date.now()
18
+ }
19
+ if (logEntry.ts === this.lastLogTimestamp) {
20
+ this.lastLogTimestampCount++
21
+ } else {
22
+ this.lastLogTimestamp = logEntry.ts
23
+ this.lastLogTimestampCount = 0
24
+ }
25
+ logEntry.ts = logEntry.ts + ('' + this.lastLogTimestampCount).padStart(4, '0')
26
+ if (logEntry.level === 'system') {
27
+ console.log(logEntry.msg)
28
+ }
29
+ if (!logEntry.src) {
30
+ logEntry.src = instanceId
31
+ }
32
+ this.buffer[this.head++] = logEntry
33
+ if (this.head === this.size) {
34
+ this.head = 0
35
+ this.wrapped = true
36
+ }
37
+ return logEntry
38
+ }
39
+
40
+ clear () {
41
+ this.buffer = new Array(this.size)
42
+ this.head = 0
43
+ this.wrapped = false
44
+ }
45
+
46
+ toArray () {
47
+ if (!this.wrapped) {
48
+ return this.buffer.slice(0, this.head)
49
+ } else {
50
+ const result = this.buffer.slice(this.head, this.size)
51
+ result.push(...this.buffer.slice(0, this.head))
52
+ return result
53
+ }
54
+ }
55
+ }
56
+
57
+ module.exports = LogBuffer
@@ -0,0 +1,20 @@
1
+ const client = require('prom-client')
2
+
3
+ module.exports = (RED) => {
4
+ RED.plugins.registerPlugin('ff-nr-metrics', {
5
+ settings: {
6
+ '*': { exportable: true }
7
+ },
8
+ onadd: function () {
9
+ const collectDefaultMetrics = client.collectDefaultMetrics
10
+ const Registry = client.Registry
11
+ const register = new Registry()
12
+ collectDefaultMetrics({ register })
13
+ RED.httpAdmin.get('/ff/metrics', async function (req, res) {
14
+ const metrics = await register.metrics()
15
+ res.send(metrics)
16
+ })
17
+ RED.log.info('FlowFuse Metrics Plugin loaded')
18
+ }
19
+ })
20
+ }
@@ -0,0 +1,57 @@
1
+ const got = require('got')
2
+ let pptf
3
+ (async function () {
4
+ try {
5
+ pptf = (await import('parse-prometheus-text-format')).default
6
+ } catch (err) {
7
+ console.log(err)
8
+ }
9
+ })();
10
+
11
+
12
+ let lastCPUTime = 0
13
+
14
+ async function sampleResources (url, time) {
15
+ const response = {}
16
+ try {
17
+ const res = await got.get(url, {
18
+ headers: {
19
+ pragma: 'no-cache',
20
+ 'Cache-Control': 'max-age=0, must-revalidate, no-cache'
21
+ },
22
+ timeout: { request: 2000 },
23
+ retry: { limit: 0 }
24
+ })
25
+ const parsed = pptf(res.body)
26
+ parsed.forEach(metric => {
27
+ if (metric.name === 'nodejs_heap_space_size_total_bytes') {
28
+ metric.metrics.forEach(v => {
29
+ response.hs = parseInt(v.value)
30
+ })
31
+ } else if (metric.name === 'nodejs_heap_space_size_used_bytes') {
32
+ metric.metrics.forEach(v => {
33
+ response.hu = parseInt(v.value)
34
+ })
35
+ } else if (metric.name === 'process_resident_memory_bytes') {
36
+ response.ps = parseInt(metric.metrics[0].value)/(1024*1024)
37
+ } else if (metric.name === 'process_cpu_seconds_total') {
38
+ cpuTime = parseFloat(metric.metrics[0].value)
39
+ if (lastCPUTime != 0) {
40
+ const delta = cpuTime - lastCPUTime
41
+ response.cpu = (delta/time) * 100
42
+ }
43
+ lastCPUTime = cpuTime
44
+ } else if (metric.name === 'nodejs_eventloop_lag_mean_seconds') {
45
+ response.ela = parseFloat(metric.metrics[0].value)
46
+ } else if (metric.name === 'nodejs_eventloop_lag_p99_seconds') {
47
+ response.el99 = parseFloat(metric.metrics[0].value)
48
+ }
49
+ })
50
+ } catch (err) {
51
+ response.err = err.message
52
+ }
53
+
54
+ return response
55
+ }
56
+
57
+ module.exports = sampleResources
@@ -0,0 +1,85 @@
1
+ const os = require('node:os')
2
+ const crypto = require('crypto')
3
+
4
+ const instanceId = crypto.createHash('md5').update(os.hostname()).digest('hex').substring(0, 4)
5
+ class SampleBuffer {
6
+ constructor (size = 1000) {
7
+ this.size = size
8
+ this.buffer = new Array(this.size)
9
+ this.head = 0
10
+ this.wrapped = false
11
+ this.lastTimestamp = 0
12
+ this.lastTimestampCount = 0
13
+ }
14
+
15
+ add (sample) {
16
+ if (!sample.ts) {
17
+ sample.ts = Date.now()
18
+ }
19
+ this.buffer[this.head++] = sample
20
+ if (this.head == this.size) {
21
+ this.head = 0
22
+ this.wrapped = true
23
+ }
24
+ return sample
25
+ }
26
+
27
+ clear () {
28
+ this.buffer = new Array(this.size)
29
+ this.head = 0
30
+ this.wrapped = false
31
+ }
32
+
33
+ toArray () {
34
+ if (!this.wrapped) {
35
+ return this.buffer.slice(0, this.head)
36
+ } else {
37
+ const result = this.buffer.slice(this.head, this.size)
38
+ result.push(...this.buffer.slice(0,this.head))
39
+ return result
40
+ }
41
+ }
42
+
43
+ lastX (x) {
44
+ if (this.head > x) {
45
+ return this.buffer.slice(this.head - x, this.head)
46
+ } else {
47
+ if (this.wrapped) {
48
+ const d = x - this.head
49
+ const result = this.buffer.slice(this.size - d, this.size)
50
+ result.push(...this.buffer.slice(0, this.head))
51
+ return result
52
+ } else {
53
+ return this.buffer.slice(0, this.head)
54
+ }
55
+ }
56
+ }
57
+
58
+ avgLastX (x) {
59
+ const samples = this.lastX(x)
60
+ const result = {}
61
+ let skipped = 0
62
+ samples.forEach(sample => {
63
+ if (!sample.err) {
64
+ for (const [key, value] of Object.entries(sample)) {
65
+ if (key !== 'ts' && key !== 'err') {
66
+ if (result[key]) {
67
+ result[key] += value
68
+ } else {
69
+ result[key] = value
70
+ }
71
+ }
72
+ }
73
+ } else {
74
+ skipped++
75
+ }
76
+ })
77
+ for (const [key, value] of Object.entries(result)) {
78
+ result[key] = value/(samples.length-skipped)
79
+ }
80
+ result.count = samples.length
81
+ return result
82
+ }
83
+ }
84
+
85
+ module.exports = SampleBuffer
@@ -0,0 +1,320 @@
1
+ function getSettingsFile (settings) {
2
+ const projectSettings = {
3
+ credentialSecret: '',
4
+ httpAdminRoot: '',
5
+ dashboardUI: '',
6
+ disableEditor: '',
7
+ codeEditor: 'monaco',
8
+ theme: 'forge-light',
9
+ page_title: 'FlowFuse',
10
+ page_favicon: '',
11
+ header_title: 'FlowFuse',
12
+ header_url: `url: '${settings.forgeURL}/project/${settings.projectID}'`,
13
+ palette: {
14
+ allowInstall: true,
15
+ nodesExcludes: [],
16
+ denyList: [],
17
+ allowList: ['*'],
18
+ catalogues: ['https://catalogue.nodered.org/catalogue.json']
19
+ },
20
+ modules: {
21
+ allowInstall: true,
22
+ denyList: [],
23
+ allowList: ['*']
24
+ },
25
+ fileStore: null,
26
+ projectLink: null,
27
+ httpNodeAuth: '',
28
+ setupAuthMiddleware: '',
29
+ httpNodeMiddleware: '',
30
+ tcpInAllowInboundConnections: '',
31
+ udpInAllowInboundConnections: '',
32
+ tours: true,
33
+ libraries: ''
34
+ }
35
+ let authMiddlewareRequired = false
36
+ if (settings.settings) {
37
+ // Template/project supplied settings
38
+ if (settings.settings.httpNodeAuth?.user && settings.settings.httpNodeAuth?.pass) {
39
+ projectSettings.httpNodeAuth = `httpNodeAuth: ${JSON.stringify(settings.settings.httpNodeAuth)},`
40
+ } else if (settings.settings.httpNodeAuth?.type === 'flowforge-user') {
41
+ authMiddlewareRequired = true
42
+ projectSettings.setupAuthMiddleware = `const flowforgeAuthMiddleware = require('@flowfuse/nr-launcher/authMiddleware').init({
43
+ type: 'flowforge-user',
44
+ baseURL: '${settings.baseURL}',
45
+ forgeURL: '${settings.forgeURL}',
46
+ clientID: '${settings.clientID}',
47
+ clientSecret: '${settings.clientSecret}'
48
+ })`
49
+ projectSettings.httpNodeMiddleware = 'httpNodeMiddleware: flowforgeAuthMiddleware,'
50
+ }
51
+ if (settings.credentialSecret !== undefined) {
52
+ projectSettings.credentialSecret = `credentialSecret: '${settings.credentialSecret}',`
53
+ }
54
+ if (settings.settings.httpAdminRoot !== undefined) {
55
+ projectSettings.httpAdminRoot = `httpAdminRoot: '${settings.settings.httpAdminRoot}',`
56
+ }
57
+ if (settings.settings.dashboardUI !== undefined || authMiddlewareRequired) {
58
+ const dashboardSettings = []
59
+ if (settings.settings.dashboardUI !== undefined) {
60
+ dashboardSettings.push(`path: '${settings.settings.dashboardUI}'`)
61
+ }
62
+ if (authMiddlewareRequired) {
63
+ dashboardSettings.push('middleware: flowforgeAuthMiddleware')
64
+ }
65
+ projectSettings.dashboardUI = `ui: { ${dashboardSettings.join(', ')}},`
66
+ }
67
+ if (settings.settings.disableEditor !== undefined) {
68
+ projectSettings.disableEditor = `disableEditor: ${settings.settings.disableEditor},`
69
+ }
70
+ if (settings.settings.ha?.replicas >= 2) {
71
+ projectSettings.disableEditor = 'disableEditor: true,'
72
+ }
73
+ if (settings.settings.codeEditor) {
74
+ projectSettings.codeEditor = settings.settings.codeEditor
75
+ }
76
+ if (typeof settings.settings.theme === 'string') {
77
+ if (settings.settings.theme === '' || settings.settings.theme === 'node-red') {
78
+ projectSettings.theme = '' // use default node-red theme
79
+ } else {
80
+ projectSettings.theme = settings.settings.theme
81
+ }
82
+ }
83
+ if (settings.settings.page?.title) {
84
+ projectSettings.page_title = settings.settings.page.title
85
+ }
86
+ projectSettings.page_favicon = 'favicon: "/theme/css/favicon.ico",' // TODO: get from theme
87
+ if (settings.settings.page?.favicon) {
88
+ projectSettings.page_favicon = `favicon: "${settings.settings.page.favicon}",`
89
+ }
90
+ if (settings.settings.header?.title) {
91
+ projectSettings.header_title = settings.settings.header.title
92
+ }
93
+ if (settings.settings.header?.url) {
94
+ projectSettings.header_url = settings.settings.header.url
95
+ }
96
+ if (settings.settings.palette?.allowInstall !== undefined) {
97
+ projectSettings.palette.allowInstall = settings.settings.palette.allowInstall
98
+ }
99
+ if (settings.settings.palette?.nodesExcludes !== undefined) {
100
+ projectSettings.palette.nodesExcludes = settings.settings.palette.nodesExcludes.split(',').map(fn => fn.trim()).filter(fn => fn.length > 0)
101
+ }
102
+ if (settings.settings.modules?.allowInstall !== undefined) {
103
+ projectSettings.modules.allowInstall = settings.settings.modules.allowInstall
104
+ }
105
+ if (settings.settings.palette?.allowList !== undefined) {
106
+ projectSettings.palette.allowList = settings.settings.palette.allowList || ['*']
107
+ }
108
+ if (settings.settings.palette?.denyList !== undefined) {
109
+ projectSettings.palette.denyList = settings.settings.palette.denyList || []
110
+ }
111
+ if (settings.settings.modules?.allowList !== undefined) {
112
+ projectSettings.modules.allowList = settings.settings.modules.allowList || ['*']
113
+ }
114
+ if (settings.settings.modules?.denyList !== undefined) {
115
+ projectSettings.modules.denyList = settings.settings.modules.denyList || []
116
+ }
117
+ if (settings.allowInboundTcp === true || settings.allowInboundTcp === false) {
118
+ projectSettings.tcpInAllowInboundConnections = `tcpInAllowInboundConnections: ${settings.allowInboundTcp},`
119
+ }
120
+ if (settings.allowInboundUdp === true || settings.allowInboundUdp === false) {
121
+ projectSettings.udpInAllowInboundConnections = `udpInAllowInboundConnections: ${settings.allowInboundUdp},`
122
+ }
123
+ if (settings.settings.disableTours) {
124
+ projectSettings.tours = false
125
+ }
126
+ }
127
+
128
+ if (settings.features?.projectComms && settings.broker) {
129
+ // Enable the projectLink nodes if a broker configuration is provided
130
+ projectSettings.projectLink = {
131
+ token: settings.projectToken,
132
+ broker: { ...settings.broker }
133
+ }
134
+ if (settings.settings.ha?.replicas >= 2) {
135
+ projectSettings.projectLink.useSharedSubscriptions = true
136
+ }
137
+ }
138
+ let contextStorage = ''
139
+ if (settings.fileStore?.url) {
140
+ // file nodes settings
141
+ projectSettings.fileStore = { ...settings.fileStore }
142
+ projectSettings.fileStore.token ||= settings.projectToken
143
+ if (settings.licenseType === 'ee') {
144
+ // context storage settings
145
+ contextStorage = `contextStorage: {
146
+ default: "memory",
147
+ memory: { module: 'memory' },
148
+ persistent: {
149
+ module: require("@flowfuse/nr-launcher/context"),
150
+ config: {
151
+ projectID: '${settings.projectID}',
152
+ url: '${settings.fileStore.url}',
153
+ token: '${settings.projectToken}'
154
+ }
155
+ }
156
+ },`
157
+ }
158
+ }
159
+ if (projectSettings.theme) {
160
+ const themeSettings = {
161
+ launcherVersion: settings.launcherVersion,
162
+ forgeURL: settings.forgeURL,
163
+ projectURL: `${settings.forgeURL}/project/${settings.projectID}`
164
+ }
165
+ projectSettings.themeSettings = `"${projectSettings.theme}": ${JSON.stringify(themeSettings)},`
166
+ projectSettings.theme = `theme: '${projectSettings.theme}',`
167
+ }
168
+
169
+ let nodesDir = ''
170
+ if (settings.nodesDir && settings.nodesDir.length) {
171
+ nodesDir = `nodesDir: ${JSON.stringify(settings.nodesDir)},`
172
+ }
173
+ if (settings.features?.['shared-library']) {
174
+ const sharedLibraryConfig = {
175
+ id: 'flowforge-team-library',
176
+ type: 'flowforge-team-library',
177
+ label: 'Team Library',
178
+ icon: 'font-awesome/fa-users',
179
+ baseURL: settings.storageURL,
180
+ projectID: settings.projectID,
181
+ libraryID: settings.teamID,
182
+ token: settings.projectToken
183
+ }
184
+ projectSettings.libraries = `library: { sources: [ ${JSON.stringify(sharedLibraryConfig)} ] },`
185
+ }
186
+ if (settings.licenseType === 'ee') {
187
+ if (settings.settings.palette?.catalogue !== undefined) {
188
+ projectSettings.palette.catalogues = settings.settings.palette.catalogue
189
+ }
190
+ }
191
+
192
+ const settingsTemplate = `
193
+ ${projectSettings.setupAuthMiddleware}
194
+ module.exports = {
195
+ flowFile: 'flows.json',
196
+ flowFilePretty: true,
197
+ adminAuth: require('@flowfuse/nr-launcher/adminAuth')({
198
+ baseURL: '${settings.baseURL}',
199
+ forgeURL: '${settings.forgeURL}',
200
+ clientID: '${settings.clientID}',
201
+ clientSecret: '${settings.clientSecret}'
202
+ }),
203
+ ${projectSettings.credentialSecret}
204
+ ${projectSettings.httpAdminRoot}
205
+ ${projectSettings.dashboardUI}
206
+ ${projectSettings.disableEditor}
207
+ ${projectSettings.httpNodeAuth}
208
+ ${projectSettings.httpNodeMiddleware}
209
+ ${projectSettings.tcpInAllowInboundConnections}
210
+ ${projectSettings.udpInAllowInboundConnections}
211
+ httpServerOptions: {
212
+ "trust proxy": true
213
+ },
214
+ storageModule: require('@flowfuse/nr-launcher/storage'),
215
+ httpStorage: {
216
+ projectID: '${settings.projectID}',
217
+ baseURL: '${settings.storageURL}',
218
+ token: '${settings.projectToken}'
219
+ },
220
+ ${contextStorage}
221
+ logging: {
222
+ console: { level: 'info', metric: false, audit: false, handler: () => {
223
+ const levelNames = {
224
+ 10: "fatal",
225
+ 20: "error",
226
+ 30: "warn",
227
+ 40: "info",
228
+ 50: "debug",
229
+ 60: "trace",
230
+ 98: "audit",
231
+ 99: "metric"
232
+ }
233
+ return (msg) => {
234
+ let message = msg.msg;
235
+ try {
236
+ if (typeof message === 'object' && message !== null && message.toString() === '[object Object]' && message.message) {
237
+ message = message.message;
238
+ }
239
+ } catch(e){
240
+ message = 'Exception trying to log: '+util.inspect(message);
241
+ }
242
+ console.log(JSON.stringify({
243
+ ts: Date.now(),
244
+ level: levelNames[msg.level],
245
+ type: msg.type,
246
+ name: msg.name,
247
+ id:msg.id,
248
+ msg: message
249
+ }))
250
+ }
251
+ }
252
+
253
+ },
254
+ auditLogger: {
255
+ level: 'off', audit: true, handler: require('@flowfuse/nr-launcher/auditLogger'),
256
+ loggingURL: '${settings.auditURL}',
257
+ projectID: '${settings.projectID}',
258
+ token: '${settings.projectToken}'
259
+ }
260
+ },
261
+ ${nodesDir}
262
+ ${projectSettings.themeSettings || ''}
263
+ editorTheme: {
264
+ ${projectSettings.theme}
265
+ page: {
266
+ title: '${projectSettings.page_title}',
267
+ ${projectSettings.page_favicon}
268
+ },
269
+ header: {
270
+ title: '${projectSettings.header_title}',
271
+ ${projectSettings.header_url}
272
+ },
273
+ logout: {
274
+ redirect: '${settings.forgeURL}/project/${settings.projectID}'
275
+ },
276
+ codeEditor: {
277
+ lib: '${projectSettings.codeEditor}'
278
+ },
279
+ ${projectSettings.libraries}
280
+ tours: ${projectSettings.tours},
281
+ palette: {
282
+ catalogues: ${JSON.stringify(projectSettings.palette.catalogues)}
283
+ }
284
+ },
285
+ nodesExcludes: ${JSON.stringify(projectSettings.palette.nodesExcludes)},
286
+ externalModules: {
287
+ // autoInstall: true,
288
+ palette: {
289
+ allowInstall: ${projectSettings.palette.allowInstall},
290
+ allowUpload: false,
291
+ denyList: ${JSON.stringify(projectSettings.palette.denyList)},
292
+ allowList: ${JSON.stringify(projectSettings.palette.allowList)}
293
+ },
294
+ modules: {
295
+ allowInstall: ${projectSettings.modules.allowInstall},
296
+ denyList: ${JSON.stringify(projectSettings.modules.denyList)},
297
+ allowList: ${JSON.stringify(projectSettings.modules.allowList)}
298
+ }
299
+ },
300
+ functionExternalModules: ${projectSettings.modules.allowInstall},
301
+ flowforge: {
302
+ forgeURL: '${settings.forgeURL}',
303
+ teamID: '${settings.teamID}',
304
+ projectID: '${settings.projectID}',
305
+ launcherVersion: '${settings.launcherVersion}',
306
+ ${projectSettings.fileStore ? 'fileStore: ' + JSON.stringify(projectSettings.fileStore) + ',' : ''}
307
+ ${projectSettings.projectLink ? 'projectLink: ' + JSON.stringify(projectSettings.projectLink) : ''}
308
+ },
309
+ runtimeState: {
310
+ enabled: true,
311
+ ui: true
312
+ }
313
+ }
314
+ `
315
+ return settingsTemplate
316
+ }
317
+
318
+ module.exports = {
319
+ getSettingsFile
320
+ }
@@ -0,0 +1,92 @@
1
+ const got = require('got')
2
+
3
+ let settings
4
+
5
+ module.exports = {
6
+ init: (nrSettings) => {
7
+ settings = nrSettings.httpStorage || {}
8
+
9
+ if (Object.keys(settings) === 0) {
10
+ const err = Promise.reject(new Error('No settings for flow storage module found'))
11
+ // err.catch(err => {})
12
+ return err
13
+ }
14
+
15
+ const projectID = settings.projectID
16
+
17
+ // console.log(settings)
18
+ // console.log(settings.baseURL + "/" + projectID + "/")
19
+
20
+ this._client = got.extend({
21
+ prefixUrl: settings.baseURL + '/' + projectID + '/',
22
+ headers: {
23
+ 'user-agent': 'FlowFuse HTTP Storage v0.1',
24
+ authorization: 'Bearer ' + settings.token
25
+ },
26
+ timeout: {
27
+ request: 10000
28
+ }
29
+ })
30
+
31
+ return Promise.resolve()
32
+ },
33
+ getFlows: async () => {
34
+ return this._client.get('flows').json()
35
+ },
36
+ saveFlows: async (flow) => {
37
+ return this._client.post('flows', {
38
+ json: flow,
39
+ responseType: 'json'
40
+ })
41
+ },
42
+ getCredentials: async () => {
43
+ return this._client.get('credentials').json()
44
+ },
45
+ saveCredentials: async (credentials) => {
46
+ return this._client.post('credentials', {
47
+ json: credentials,
48
+ responseType: 'json'
49
+ })
50
+ },
51
+ getSettings: () => {
52
+ return this._client.get('settings').json()
53
+ },
54
+ saveSettings: (settings) => {
55
+ return this._client.post('settings', {
56
+ json: settings,
57
+ responseType: 'json'
58
+ })
59
+ },
60
+ getSessions: () => {
61
+ this._client.get('sessions').json()
62
+ },
63
+ saveSessions: (sessions) => {
64
+ return this._client.post('sessions', {
65
+ json: sessions,
66
+ responseType: 'json'
67
+ })
68
+ },
69
+ getLibraryEntry: (type, name) => {
70
+ return this._client.get('library/' + type, {
71
+ searchParams: {
72
+ name
73
+ }
74
+ }).then(entry => {
75
+ if (entry.headers['content-type'].startsWith('application/json')) {
76
+ return JSON.parse(entry.body)
77
+ } else {
78
+ return entry.body
79
+ }
80
+ })
81
+ },
82
+ saveLibraryEntry: (type, name, meta, body) => {
83
+ return this._client.post('library/' + type, {
84
+ json: {
85
+ name,
86
+ meta,
87
+ body
88
+ },
89
+ responseType: 'json'
90
+ })
91
+ }
92
+ }