@naanlang/naan 1.0.1 → 1.0.4

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 (37) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +5 -5
  3. package/bin/index.js +18 -15
  4. package/dist/env_web.js +146 -16
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/sworker.js +299 -91
  7. package/frameworks/browser/terminals.nlg +23 -10
  8. package/frameworks/browser/workers.nlg +26 -12
  9. package/frameworks/client/apiclient.nlg +17 -4
  10. package/frameworks/client/psm_client.nlg +178 -81
  11. package/frameworks/common/common.nlg +14 -4
  12. package/frameworks/node/apiserver.nlg +31 -15
  13. package/frameworks/node/filesystem.nlg +230 -46
  14. package/frameworks/node/gitter.nlg +27 -6
  15. package/frameworks/node/psm_server.nlg +113 -37
  16. package/frameworks/project/build.nlg +40 -23
  17. package/frameworks/project/projects.nlg +50 -27
  18. package/frameworks/running/debugnub.nlg +2 -5
  19. package/frameworks/storage/csv.nlg +120 -0
  20. package/frameworks/storage/dbt_pouch.nlg +41 -25
  21. package/frameworks/storage/psm.nlg +108 -28
  22. package/frameworks/storage/psm_dbtables.nlg +7 -3
  23. package/frameworks/storage/resources.nlg +30 -2
  24. package/lib/browser/env_web.js +148 -18
  25. package/lib/browser/env_webworker.js +1 -1
  26. package/lib/browser/require.js +1 -1
  27. package/lib/core/naanlib.js +5 -5
  28. package/lib/env_node.js +9 -1
  29. package/package.json +2 -1
  30. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +1 -1
  31. package/plugins/serviceAws/aws_dynamo.nlg +625 -141
  32. package/plugins/serviceAws/aws_dynextra.nlg +294 -0
  33. package/plugins/serviceAws/dbt_aws.nlg +31 -11
  34. package/plugins/serviceAws/psm_aws.nlg +42 -26
  35. package/plugins/serviceAws/serviceAws.nlg +1 -0
  36. package/plugins/serviceGitHub/psm_github.nlg +41 -25
  37. package/plugins/serviceGitLab/psm_gitlab.nlg +41 -25
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2021 by Richard C. Zulch
9
+ * Copyright (c) 2017-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -20,9 +20,10 @@
20
20
  *
21
21
  */
22
22
 
23
- closure MakeNideAPIclient(url, local client) {
23
+ closure MakeNideAPIclient(url, guid, local client) {
24
24
  client = new(object, this)
25
25
  client.url = url
26
+ client.guid = guid
26
27
  client.notifyAfter = 0
27
28
 
28
29
  // clientRequest() - general request routine
@@ -51,6 +52,8 @@ closure MakeNideAPIclient(url, local client) {
51
52
  request.responseType = options.responseType
52
53
  if options.contentType
53
54
  request.setRequestHeader("Content-Type", options.contentType)
55
+ if guid
56
+ request.setRequestHeader("x-naanlang-api-guid", guid)
54
57
  request.send(options.putdata)
55
58
  }
56
59
 
@@ -117,7 +120,7 @@ closure MakeNideAPIclient(url, local client) {
117
120
  // Otherwise if communication failed then the result is:
118
121
  // callback(<error>).
119
122
 
120
- client.psmRemote = closure psmRemote(options, data, callback, local query, method, reqOptions) {
123
+ client.psmRemote = closure psmRemote(options, data, callback, local query, method, reqOptions, error) {
121
124
  query = EncodeQuery("/psm/?", options)
122
125
  if data
123
126
  method = "PUT"
@@ -131,6 +134,16 @@ closure MakeNideAPIclient(url, local client) {
131
134
  reqOptions.contentType = "application/octet-stream" // we are sending binary data
132
135
  else
133
136
  reqOptions.responseType = "arraybuffer" // we are requesting binary data
137
+ } else if options.encoding == "json" {
138
+ if data {
139
+ reqOptions.contentType = "application/json;charset=UTF-8" // we are sending JSON
140
+ `(error, data) = JsonStringify(data)
141
+ if error
142
+ return (callback(Error("psmRemote failed:", error)))
143
+ reqOptions.putdata = data
144
+ }
145
+ else
146
+ reqOptions.responseType = "application/json" // we are requesting JSON
134
147
  }
135
148
  clientRequest(method, url.concat(query), reqOptions, function (error, req, event, local data) {
136
149
  if !error && req.status != 200
@@ -172,7 +185,7 @@ closure MakeNideAPIclient(url, local client) {
172
185
 
173
186
  function apicInit(local manifest) {
174
187
 
175
- manifest = `(apic, apicInit)
188
+ manifest = `(MakeNideAPIclient, apicInit)
176
189
 
177
190
  Naan.module.build(module.id, "apiclient", function(modobj, compobj) {
178
191
  Naan.module.require("./client.nlg")
@@ -22,6 +22,44 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
22
22
  view = new(object, this)
23
23
  view.api = api
24
24
  view.rootpath = rootpath
25
+
26
+ // data
27
+ //
28
+ // Return the filesystem data, i.e. the tree data field. The minimum information is:
29
+ // platform -- e.g. "darwin" or "win32"
30
+ // semantics -- e.g. "win32" or "posix"
31
+ // pathsep -- e.g. / or \
32
+
33
+ view.data = closure data(callback) {
34
+ if !callback
35
+ return (syncAdapter(data))
36
+ params = {
37
+ path: ""
38
+ op: "tree"
39
+ depthlimit: 0
40
+ }
41
+ api.psmRemote(params, false, function(error, tree) {
42
+ view.fsdata = tree.data
43
+ callback(error, tree.data)
44
+ })
45
+ }
46
+
47
+ // pathInit
48
+ //
49
+ // Initialize our path module for the applicable path type.
50
+
51
+ closure pathInit(local result) {
52
+ result = view.data()
53
+ if result.0
54
+ pathmod = false
55
+ else {
56
+ if view.fsdata.pathsep == "\\"
57
+ pathmod = JSpath.win32
58
+ else
59
+ pathmod = JSpath.posix
60
+ false
61
+ }
62
+ }
25
63
 
26
64
  // psmRemote
27
65
  //
@@ -30,10 +68,14 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
30
68
  closure psmRemote(path, op, options, putdata, callback, local params, item) {
31
69
  if !callback
32
70
  return (syncAdapter(psmRemote, path, op, options, putdata))
71
+ if !pathmod && pathInit()
72
+ return // failed to initialize path module
33
73
  if !string(path)
34
74
  return (asyncResult(callback, Error("invalid path:", typeof(path))))
75
+ if rootpath != ""
76
+ path = pathmod.resolve(rootpath, path)
35
77
  params = {
36
- path: JSpath.resolve(rootpath, path)
78
+ path: path
37
79
  op: op
38
80
  }
39
81
  for item in options
@@ -59,38 +101,30 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
59
101
 
60
102
  // path
61
103
  //
62
- // The path sub-object remotes calls to the NodeJS path module.
104
+ // Redirect path calls to module chosen for filesystem semantics. It is possible for this to fail
105
+ // on first connection to the filesystem, in which case we return false for the path result.
106
+ // Throwing an error would complicate our callers and testing. This way there's a small chance
107
+ // that a transient error will give a bad path, but that will be detected downstream.
63
108
 
64
109
  view.path = new(object, this)
65
- view.path[".undefined"] = closure path args {
66
- closure (selector, arglist, local error, info) {
67
- if !pathmod {
68
- `(error, data) = view.data()
69
- if error
70
- return (list(error))
71
- if info.pathsep == "\\"
72
- pathmod = JSpath.win32
73
- else
74
- pathmod = JSpath.posix
75
- }
76
- xapply(pathmod, selector, arglist)
77
- } (args.0, args.-1)
110
+ view.path[".undefined"] = function path args {
111
+ if !pathmod
112
+ pathInit()
113
+ xapply(pathmod, args.0, args.-1)
78
114
  }
79
115
 
80
- // data
116
+ // folderPath
81
117
  //
82
- // Return the filesystem data, i.e. the tree data field. The minimum information is:
83
- // platform -- e.g. "darwin" or "win32"
84
- // semantics -- e.g. "win32" or "posix"
85
- // pathsep -- e.g. / or \
86
-
87
- view.data = closure data(callback) {
88
- if !callback
89
- return (syncAdapter(data))
90
- psmRemote("", "tree", { depthlimit: 0 }, false, function(error, tree) {
91
- view.fsdata = tree.data
92
- callback(error, tree.data)
93
- })
118
+ // Return the path of the specified special folder:
119
+ // "HomeDir"
120
+ // "TempDir"
121
+ // "PackageDir"
122
+ // The returned path is relative to our root if within it, or absolute otherwise.
123
+
124
+ view.folderPath = function folderPath(folderID, callback) {
125
+ psmRemote("", "folderPath", {
126
+ folderid: folderID
127
+ }, false, callback)
94
128
  }
95
129
 
96
130
  // readLines
@@ -123,7 +157,28 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
123
157
  view.writeFile = function writeFile(path, data, options, callback) {
124
158
  psmRemote(path, "writeFile", options, data, callback)
125
159
  }
126
-
160
+
161
+ // readlink
162
+ //
163
+ // Read the contents of a symbolic link at the specified path
164
+
165
+ view.readlink = function readlink(path, callback) {
166
+ psmRemote(path, "readlink", { }, false, callback)
167
+ }
168
+
169
+ // symlink
170
+ //
171
+ // Create a symbolic link path pointing to (containing the string) target
172
+
173
+ view.symlink = function symlink(target, path, options, callback) {
174
+ if options
175
+ options = new(options)
176
+ else
177
+ options = { }
178
+ options.target = target
179
+ psmRemote(path, "symlink", options, false, callback)
180
+ }
181
+
127
182
  // delete
128
183
  //
129
184
  // Delete a file or directory at the specified path
@@ -134,12 +189,45 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
134
189
 
135
190
  // info
136
191
  //
137
- // Get file info at the specified path
192
+ // Get file or directory info at the specified path
193
+
194
+ view.info = function info(path, options, callback) {
195
+ if !options
196
+ options = { }
197
+ psmRemote(path, "info", options, false, callback)
198
+ }
199
+
200
+ // setInfo
201
+ //
202
+ // Set file or directory info obtained from info() call.
138
203
 
139
- view.info = function info(path, callback) {
140
- psmRemote(path, "info", { }, false, callback)
204
+ view.setInfo = function setInfo(path, info, callback) {
205
+ psmRemote(path, "setInfo", {
206
+ encoding: "json"
207
+ }, info, callback)
141
208
  }
142
209
 
210
+ // setOwnerGroup
211
+ //
212
+ // Set file or directory owner or group at the specified path
213
+
214
+ view.setOwnerGroup = function setOwnerGroup(path, uid, gid, callback) {
215
+ psmRemote(path, "setOwnerGroup", {
216
+ uid: uid
217
+ gid: gid
218
+ }, false, callback)
219
+ }
220
+
221
+ // setMode
222
+ //
223
+ // Set file or directory mode at the specified path
224
+
225
+ view.setMode = function setMode(path, mode, callback) {
226
+ psmRemote(path, "setMode", {
227
+ mode: mode
228
+ }, false, callback)
229
+ }
230
+
143
231
  // setTimes
144
232
  //
145
233
  // Set file modify and access times at the specified path
@@ -157,7 +245,7 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
157
245
 
158
246
  view.dirList = function dirList(path, options, callback) {
159
247
  psmRemote(path, "dirList", {
160
- head: options.head
248
+ stat: options.stat
161
249
  }, false, callback)
162
250
  }
163
251
 
@@ -167,7 +255,7 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
167
255
 
168
256
  view.readir = function readir(path, callback) {
169
257
  psmRemote(path, "dirList", {
170
- head: true
258
+ stat: true
171
259
  }, false, callback)
172
260
  }
173
261
 
@@ -209,7 +297,9 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
209
297
  // delete the source after the copy.
210
298
 
211
299
  view.copy = closure copy(srcpath, dest, options, callback, local parameters) {
212
- if !options
300
+ if options
301
+ options = new(options)
302
+ else
213
303
  options = { }
214
304
  options.destination = JSpath.resolve(rootpath, dest)
215
305
  psmRemote(srcpath, "copy", options, false, callback)
@@ -325,7 +415,7 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
325
415
  connector.psm = psm
326
416
  connector.api = nicc.api
327
417
  connector.vault = psm.vault(connClassID)
328
- connector.label = "Naan IDE Server"
418
+ connector.label = "Naan-Server"
329
419
  watch = psm.watchable(connector)
330
420
 
331
421
  // uifields
@@ -337,57 +427,44 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
337
427
  placeholder: "http://host.com/endpoint"
338
428
  key: "urlName" },
339
429
  {
340
- label: "Auth Name"
341
- placeholder: "account name"
342
- key: "authName" },
343
- {
344
- label: "Auth Password"
345
- placeholder: "account password"
346
- key: "authPW"
347
- type: "password" },
430
+ label: "Auth Secret"
431
+ placeholder: "guid"
432
+ key: "authSecret" }
348
433
  ]
349
434
  }
350
-
351
- // hashResourceID
352
- //
353
- // Make a stable hash based on the resource credentials that we can persist or share between
354
- // different domains accessing the same resource.
355
- connector.hashResourceID = function hashResourceID(resource, local ident) {
356
- ident = resource.urlName
357
- if resource.label
358
- ident = ident.concat(resource.label)
359
- HashSHA256(ident)
360
- }
361
435
 
362
436
  // findResource
363
437
  //
364
- // Find a resource with the specified contents, returning the resID or false. The loose criteria
365
- // is: if added, would the specified contents be redundant to an existing resource?
438
+ // Find a resource with the specified contents, returning the resID or false.
366
439
  connector.findResource = function findResource(resource, local resID, creds) {
367
440
  for resID in listResources().1 {
368
441
  creds = access(resID).1
369
- if resource.urlName == creds.urlName
370
- && (!resource.label || resource.label == creds.label)
371
- && resource.authName == creds.authName
442
+ if ((!resource.urlName || resource.urlName == creds.urlName)
443
+ && (!resource.label || resource.label == creds.label))
372
444
  return (resID)
373
445
  }
374
446
  false
375
447
  }
376
448
 
377
- // addResource
449
+
450
+ // writeResource
378
451
  //
379
- // Add credentials for a named resource. The resource is a dictionary comprising these keys:
452
+ // Add or update credentials for a named resource. If no resID is specified this adds a new
453
+ // resource; otherwise it updates an existing one. The resource is a dictionary comprising these
454
+ // keys:
380
455
  // label - [optional] label we use for this resource
381
456
  // urlName - URL (e.g. "http://host.com/node/fsapi")
382
- // authName - [optional] the authentication name for a URL
383
- // authPW - [optional] the autentication password, required if authname is provided
457
+ // authSecret - access-control token for this URL
384
458
  // hidden - [optional] don't enumerate to user
385
459
  // locked - [optional] don't allow user delete
386
460
  //
387
- // The return value is (error, resID) tuple. Error, if non-false, is a dictionary of field
461
+ // The return value is an (error, resID) tuple. Error, if non-false, is a dictionary of field
388
462
  // keys and error diagnostic strings.
389
- connector.addResource = function addResource(resource, local creds, errors, error, resID, data) {
463
+ connector.writeResource = function writeResource(resource, resID,
464
+ local creds, errors, error, data, change) {
465
+ // badField - test for a bad string
390
466
  function badField(str) { !string(str) || str.trim().length == 0 }
467
+
391
468
  creds = { urlName: resource.urlName }
392
469
  errors = { }
393
470
  if resource.label
@@ -396,13 +473,13 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
396
473
  creds.label = resource.urlName
397
474
  if badField(creds.label)
398
475
  errors.label = "invalid label"
476
+ else if !resID && findResource({ label: creds.label })
477
+ errors.label = "duplicate name"
399
478
  if badField(resource.urlName)
400
479
  errors.urlName = "required field"
401
- if !badField(resource.authName) {
402
- creds.authName = resource.authName
403
- creds.authPW = resource.authPW
404
- if badField(resource.authPW)
405
- errors.authPW = "required if Auth Name specified" }
480
+ if badField(resource.authSecret)
481
+ errors.authSecret = "required field"
482
+ creds.authSecret = resource.authSecret
406
483
  if length(errors) > 0
407
484
  error = errors
408
485
  else {
@@ -410,14 +487,36 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
410
487
  creds.hidden = resource.hidden
411
488
  if resource.locked
412
489
  creds.locked = resource.locked
413
- resID = hashResourceID(resource)
414
- `(error, data) = connector.vault.addResource(resID, creds)
490
+ if resID {
491
+ `(error, data) = connector.vault.updateResource(resID, creds)
492
+ change = { changed: [resID] }
493
+ } else {
494
+ resID = UUID()
495
+ `(error, data) = connector.vault.addResource(resID, creds)
496
+ change = { added: [resID] }
497
+ }
415
498
  if data {
416
- watch.notify(connClassID, { added: [resID] })
417
- data = resID } }
499
+ watch.notify(connClassID, change)
500
+ data = resID
501
+ }
502
+ }
418
503
  list(error, data)
419
504
  }
420
-
505
+
506
+ // addResource
507
+ //
508
+ // Add a new resource, returning a standard error tuple per writeResource.
509
+ connector.addResource = function addResource(resource) {
510
+ writeResource(resource)
511
+ }
512
+
513
+ // updateResource
514
+ //
515
+ // Update an existing resource, returning a standard error tuple per writeResource.
516
+ connector.updateResource = function updateResource(resID, resource) {
517
+ writeResource(resource, resID)
518
+ }
519
+
421
520
  // getResource
422
521
  //
423
522
  // Get credentials for a named resource, matching the dictionary semantics of addResource. The
@@ -432,10 +531,8 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
432
531
  }
433
532
  if string(creds.urlName)
434
533
  resource.urlName = creds.urlName
435
- if string(creds.authName)
436
- resource.authName = creds.authName
437
- if string(creds.authPW)
438
- resource.authPW = creds.authPW
534
+ if string(creds.authSecret)
535
+ resource.authSecret = creds.authSecret
439
536
  list(false, resource)
440
537
  }
441
538
 
@@ -510,7 +607,7 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
510
607
  if creds == "HostFS"
511
608
  api = connector.api
512
609
  else
513
- `(error, api) = MakeNideAPIclient(creds.urlName) // API to remote NodeJS filesystem
610
+ api = MakeNideAPIclient(creds.urlName, creds.authSecret) // API to remote NodeJS filesystem
514
611
  }
515
612
  if error
516
613
  list(error)
@@ -66,7 +66,7 @@ function JsonParse(data) {
66
66
  list(false, data)
67
67
  } catch {
68
68
  if true
69
- list(exception)
69
+ list(exception)
70
70
  }
71
71
  };
72
72
 
@@ -84,7 +84,7 @@ function JsonStringify(data) {
84
84
  list(false, data)
85
85
  } catch {
86
86
  if true
87
- list(exception)
87
+ list(exception)
88
88
  }
89
89
  };
90
90
 
@@ -256,6 +256,8 @@ TextEncoder = false;
256
256
  TextDecoder = false;
257
257
  EncodeURIComponent = false;
258
258
  DecodeURIComponent = false;
259
+ EncodeBase64 = false;
260
+ DecodeBase64 = false;
259
261
  JSONstringify = false;
260
262
  JSONparse = false;
261
263
 
@@ -289,6 +291,10 @@ function comrInit(local manifest) {
289
291
  EncodeURIComponent = wsg.encodeURIComponent
290
292
  DecodeURIComponent = wsg.decodeURIComponent
291
293
  //
294
+ // everywhere: Base65
295
+ EncodeBase64 = wsg.btoa
296
+ DecodeBase64 = wsg.atob
297
+ //
292
298
  // everywhere: JSON
293
299
  JSONstringify = wsg.JSON.stringify
294
300
  JSONparse = wsg.JSON.parse
@@ -338,7 +344,7 @@ function comrInit(local manifest) {
338
344
  //
339
345
  DecodeBase64url = function decodeBase64url(base64url, local padding) {
340
346
  base64url = base64url.replace(RegExp("-", "g"), '+').replace(RegExp("_", "g"), '/')
341
- wsg.atob(base64url)
347
+ DecodeBase64(base64url)
342
348
  }
343
349
  //
344
350
  // For browser windows and web workers
@@ -358,7 +364,7 @@ function comrInit(local manifest) {
358
364
  //
359
365
  // HashMD5 in base64
360
366
  HashMD5_base64 = function browserMD5_base64(data) {
361
- wsg.btoa(browserMD5(data, true)) }
367
+ EncodeBase64(browserMD5(data, true)) }
362
368
  //
363
369
  // browserDigest - only in secure contexts (localhost and HTTPS)
364
370
  //
@@ -431,6 +437,8 @@ function comrInit(local manifest) {
431
437
  HashMD5 =
432
438
  HashMD5_base64 =
433
439
  HashSHA1 =
440
+ HashSHA256 =
441
+ HashSHA256_base64 =
434
442
  RandomBytes = function() { throw(Error("crypto functions unavailable")) }
435
443
  }
436
444
  }
@@ -442,6 +450,8 @@ function comrInit(local manifest) {
442
450
  modobj.exports.TextDecoder = TextDecoder
443
451
  modobj.exports.EncodeURIComponent = EncodeURIComponent
444
452
  modobj.exports.DecodeURIComponent = DecodeURIComponent
453
+ modobj.exports.EncodeBase64 = EncodeBase64
454
+ modobj.exports.DecodeBase64 = DecodeBase64
445
455
  modobj.exports.JSONstringify = JSONstringify
446
456
  modobj.exports.JSONparse = JSONparse
447
457
  modobj.exports.JsonStringify = JsonStringify
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2021 by Richard C. Zulch
9
+ * Copyright (c) 2017-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -36,6 +36,7 @@ closure MakeAPI(port, options, callback, local chroot, server) {
36
36
  server.psm = require("./psm_server.nlg").MakeServerPSM(server)
37
37
  server.root = chroot
38
38
  server.app = express()
39
+ server.guid = options.guid
39
40
  server.notifyq = []
40
41
  server.responseq = []
41
42
  server.projects = { }
@@ -99,8 +100,9 @@ closure MakeAPI(port, options, callback, local chroot, server) {
99
100
  // body parsing
100
101
  //
101
102
 
102
- server.app.use(bodyParser.text({limit: "20mb"}))
103
- server.app.use(bodyParser.raw({limit: "20mb"}))
103
+ server.app.use(bodyParser.json({limit: "5mb"}))
104
+ server.app.use(bodyParser.text({limit: "25mb"}))
105
+ server.app.use(bodyParser.raw({limit: "25mb"}))
104
106
 
105
107
  //
106
108
  // Express.js endpoints
@@ -108,8 +110,12 @@ closure MakeAPI(port, options, callback, local chroot, server) {
108
110
 
109
111
  server.app.use(function(req,res,next) {
110
112
  res.header("Access-Control-Allow-Origin", "*")
111
- res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
112
- next()
113
+ res.header("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS")
114
+ res.header("Access-Control-Allow-Headers", "x-naanlang-api-guid")
115
+ if req.method == "OPTIONS"
116
+ res.sendStatus(200)
117
+ else
118
+ next()
113
119
  })
114
120
 
115
121
  server.app.use("", express.static(chroot))
@@ -121,15 +127,18 @@ closure MakeAPI(port, options, callback, local chroot, server) {
121
127
  // "/"
122
128
 
123
129
  server.app.get("/", function(req, res) {
124
- res.send("<u>NaanIDE API</u><br>\r\n"
130
+ res.send("<u>naan-server API</u><br>\r\n"
125
131
  " /readfile?path=<i>filepath</i><br>\r\n"
126
132
  " /status?timeout=<i>msec</i>&after=<i>stamp</i><br>\r\n")
127
133
  })
128
134
 
129
135
  // "/readfile"
130
136
 
131
- server.app.get("/readfile", closure(req, res) {
132
- if not string(req.query.path)
137
+ server.app.get("/readfile", closure(req, res, local guid) {
138
+ guid = req.get("x-naanlang-api-guid")
139
+ if server.guid && guid != server.guid
140
+ res.status(403).send("unauthorized")
141
+ else if not string(req.query.path)
133
142
  res.status(400).send("Invalid filename")
134
143
  else if RegExp("^\\.\\.[/\\\\]|[/\\\\]\\.\\.$|[/\\\\]\\.\\.[/\\\\]").test(req.query.path)
135
144
  res.status(403).send("\"Parent path references forbidden\"")
@@ -156,7 +165,10 @@ closure MakeAPI(port, options, callback, local chroot, server) {
156
165
 
157
166
  // "/status"
158
167
 
159
- server.app.get("/status", closure(req,res, local timeoutms, after, responder) {
168
+ server.app.get("/status", closure(req, res, local guid, timeoutms, after, responder) {
169
+ guid = req.get("x-naanlang-api-guid")
170
+ if server.guid && guid != server.guid
171
+ res.status(403).send("unauthorized")
160
172
  timeoutms = toint(req.query.timeout)
161
173
  after = toint(req.query.after)
162
174
  if not integer(timeoutms) or timeoutms > 10000
@@ -187,8 +199,11 @@ closure MakeAPI(port, options, callback, local chroot, server) {
187
199
 
188
200
  // "/psm"
189
201
 
190
- server.app.all("/psm", closure(req, res) {
191
- if !server.hostfs
202
+ server.app.all("/psm", closure(req, res, local guid) {
203
+ guid = req.get("x-naanlang-api-guid")
204
+ if server.guid && guid != server.guid && req.method !== "OPTIONS"
205
+ res.status(403).send("unauthorized")
206
+ else if !server.hostfs
192
207
  res.status(503).send("host filesystem not available")
193
208
  else
194
209
  server.hostfs.api(req, res)
@@ -227,7 +242,6 @@ closure MakeAPI(port, options, callback, local chroot, server) {
227
242
  server.ws = WebSocketServer(server)
228
243
  server.port = port
229
244
  callback(false, port, server) // notify of success
230
- debuglog("server open on port", port)
231
245
  result = server.psm.conns.NodeFS.connect("Host", "NideFS", list("")) // ### this should go through resource tracker
232
246
  if result.0
233
247
  ErrorDebuglog(Error("can't access HostFS", result.0))
@@ -264,7 +278,7 @@ closure WebSocketServer(server, local websocks) {
264
278
  // Event handlers
265
279
 
266
280
  websocks.wss.on("connection", function(socket, request) {
267
- websocks.conns.push(WesCon(websocks, socket, request))
281
+ websocks.conns.push(WesCon(server, websocks, socket, request))
268
282
  })
269
283
 
270
284
  server.wss.on("error", function(error) {
@@ -303,7 +317,7 @@ closure WebSocketServer(server, local websocks) {
303
317
  *
304
318
  */
305
319
 
306
- closure WesCon(websocks, socket, request, local wescon) {
320
+ closure WesCon(server, websocks, socket, request, local wescon) {
307
321
  wescon = new(object, this)
308
322
  wescon.socket = socket
309
323
  wescon.serverIDs = []
@@ -315,7 +329,9 @@ closure WesCon(websocks, socket, request, local wescon) {
315
329
  socket.on("message", function onmessage(text, local message, error) {
316
330
  try {
317
331
  message = new(js.g.JSON.parse(text))
318
- if message.clientID && websocks.subsystems[message.serverID] {
332
+ if server.guid && message.guid != server.guid
333
+ error = Error("unauthorized")
334
+ else if message.clientID && websocks.subsystems[message.serverID] {
319
335
  wescon.serverIDs.push(message.serverID) // ### track subsystems for closing
320
336
  websocks.subsystems[message.serverID].wsReceive(message, wescon)
321
337
  }