statebus 7.2.5 → 7.2.7
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.
- package/client-library.js +2 -1
- package/package.json +1 -1
- package/server-library.js +39 -174
- package/statebus.js +59 -36
package/client-library.js
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
// ****************
|
|
14
14
|
// Connecting over the Network
|
|
15
15
|
function set_cookie (key, val) {
|
|
16
|
-
|
|
16
|
+
// max-age is 400 days, the max allowed in chrome now
|
|
17
|
+
document.cookie = key + '=' + val + '; max-age=34560000;'
|
|
17
18
|
}
|
|
18
19
|
function get_cookie (key) {
|
|
19
20
|
var c = document.cookie.match('(^|;)\\s*' + key + '\\s*=\\s*([^;]+)');
|
package/package.json
CHANGED
package/server-library.js
CHANGED
|
@@ -167,10 +167,9 @@ function import_server (bus, make_statebus, options)
|
|
|
167
167
|
+ Math.random().toString(36).substring(2))
|
|
168
168
|
|
|
169
169
|
// Add peer to response cookie, so client knows we've named it
|
|
170
|
-
|
|
171
|
-
expires.setFullYear(expires.getFullYear() + 20)
|
|
170
|
+
// (max-age is 400 days, the max allowed in chrome now)
|
|
172
171
|
res.setHeader('Set-Cookie', 'peer=' + client_id
|
|
173
|
-
+ ';
|
|
172
|
+
+ '; max-age=34560000;')
|
|
174
173
|
|
|
175
174
|
// Decode the key
|
|
176
175
|
var key = decodeURIComponent(req.url.substr(1))
|
|
@@ -190,10 +189,10 @@ function import_server (bus, make_statebus, options)
|
|
|
190
189
|
|
|
191
190
|
function send_update (obj, t) {
|
|
192
191
|
var body = to_http_body(obj)
|
|
193
|
-
|
|
192
|
+
console.log('http_in: Sending update of', body, 'for', req.url)
|
|
194
193
|
|
|
195
194
|
res.sendUpdate({
|
|
196
|
-
body,
|
|
195
|
+
body: body || '',
|
|
197
196
|
// If body === undefined, send 404
|
|
198
197
|
status: body === undefined ? 404 : 200
|
|
199
198
|
})
|
|
@@ -1033,154 +1032,6 @@ function import_server (bus, make_statebus, options)
|
|
|
1033
1032
|
}
|
|
1034
1033
|
},
|
|
1035
1034
|
|
|
1036
|
-
setup_usage_log (opts) {
|
|
1037
|
-
bus.serve_time()
|
|
1038
|
-
opts = opts || {}
|
|
1039
|
-
opts.filename = opts.filename || 'db.sqlite'
|
|
1040
|
-
var db = new (require('better-sqlite3'))(opts.filename)
|
|
1041
|
-
bus.usage_log_db = db
|
|
1042
|
-
//db.pragma('journal_mode = WAL')
|
|
1043
|
-
db.prepare('create table if not exists usage (date integer, event text, details text)').run()
|
|
1044
|
-
db.prepare('create index if not exists date_index on usage (date)').run()
|
|
1045
|
-
var refresh_interval = 1000*60
|
|
1046
|
-
|
|
1047
|
-
var nots = ["details not like '%facebookexternalhit%'",
|
|
1048
|
-
"details not like '%/apple-touch-icon%'",
|
|
1049
|
-
"details not like '%Googlebot%'",
|
|
1050
|
-
"details not like '%AdsBot-Google%'",
|
|
1051
|
-
"details not like '%Google-Adwords-Instant%'",
|
|
1052
|
-
"details not like '%Apache-HttpClient%'",
|
|
1053
|
-
"details not like '%SafeDNSBot%'",
|
|
1054
|
-
"details not like '%RevueBot%'",
|
|
1055
|
-
"details not like '%MetaURI API%'",
|
|
1056
|
-
"details not like '%redback/v%'",
|
|
1057
|
-
"details not like '%Slackbot%'",
|
|
1058
|
-
"details not like '%HTTP_Request2/%'",
|
|
1059
|
-
"details not like '%python-requests/%'",
|
|
1060
|
-
"details not like '%LightspeedSystemsCrawler/%'",
|
|
1061
|
-
"details not like '%CipaCrawler/%'",
|
|
1062
|
-
"details not like '%Twitterbot/%'",
|
|
1063
|
-
"details not like '%Go-http-client/%'",
|
|
1064
|
-
"details not like '%/cheese_service%'"
|
|
1065
|
-
].join(' and ')
|
|
1066
|
-
bus.usage_log_nots = nots
|
|
1067
|
-
|
|
1068
|
-
// Aggregate all accesses by day, to get daily active users
|
|
1069
|
-
bus('usage').getter = () => {
|
|
1070
|
-
bus.get('time/' + refresh_interval)
|
|
1071
|
-
var days = []
|
|
1072
|
-
var last_day
|
|
1073
|
-
for (var row of db.prepare('select * from usage where '
|
|
1074
|
-
+ nots + ' order by date').iterate()) {
|
|
1075
|
-
row.details = JSON.parse(row.details)
|
|
1076
|
-
if (row.details.agent && row.details.agent.match(/bot/)) continue
|
|
1077
|
-
|
|
1078
|
-
var d = new Date(row.date * 1000)
|
|
1079
|
-
var day = d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate()
|
|
1080
|
-
if (last_day !== day)
|
|
1081
|
-
days.push({day: day, // Init
|
|
1082
|
-
clients: new Set(),
|
|
1083
|
-
ips: new Set(),
|
|
1084
|
-
client_socket_opens: new Set(),
|
|
1085
|
-
ip_socket_opens: new Set()
|
|
1086
|
-
})
|
|
1087
|
-
last_day = day
|
|
1088
|
-
|
|
1089
|
-
if (row.event === 'socket open') {
|
|
1090
|
-
days[days.length-1].client_socket_opens.add(row.details.client)
|
|
1091
|
-
days[days.length-1].ip_socket_opens.add(row.details.ip)
|
|
1092
|
-
}
|
|
1093
|
-
days[days.length-1].clients.add(row.details.client)
|
|
1094
|
-
days[days.length-1].ips.add(row.details.ip)
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
for (var i=0; i<days.length; i++)
|
|
1098
|
-
days[i] = {day: days[i].day,
|
|
1099
|
-
ip_hits: days[i].ips.size,
|
|
1100
|
-
client_hits: days[i].clients.size,
|
|
1101
|
-
client_socket_opens: days[i].client_socket_opens.size,
|
|
1102
|
-
ip_socket_opens: days[i].ip_socket_opens.size
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
return {_: days}
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
bus('recent_hits/*').getter = (rest) => {
|
|
1109
|
-
bus.get('time/' + refresh_interval)
|
|
1110
|
-
var result = []
|
|
1111
|
-
for (var row of db.prepare('select * from usage where '
|
|
1112
|
-
+ nots + ' order by date desc limit ?').iterate(
|
|
1113
|
-
[parseInt(rest)])) {
|
|
1114
|
-
|
|
1115
|
-
row.details = JSON.parse(row.details)
|
|
1116
|
-
if (row.details.agent && row.details.agent.match(/bot/)) continue
|
|
1117
|
-
|
|
1118
|
-
result.push({url: row.details.url, ip: row.details.ip, date: row.date})
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
return {_: result}
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
bus('recent_referers/*').getter = (rest) => {
|
|
1125
|
-
bus.get('time/' + refresh_interval)
|
|
1126
|
-
var result = []
|
|
1127
|
-
for (var row of db.prepare('select * from usage where '
|
|
1128
|
-
+ nots + ' order by date desc limit ?').iterate(
|
|
1129
|
-
[parseInt(rest)])) {
|
|
1130
|
-
|
|
1131
|
-
row.details = JSON.parse(row.details)
|
|
1132
|
-
if (row.details.agent && row.details.agent.match(/bot/)) continue
|
|
1133
|
-
|
|
1134
|
-
if (row.details.referer && !row.details.referer.match(/^https:\/\/cheeseburgertherapy.com/))
|
|
1135
|
-
result.push({url: row.details.url, referer: row.details.referer,
|
|
1136
|
-
date: row.date})
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
return {_: result}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
function sock_open_time (sock_event) {
|
|
1144
|
-
var client = JSON.parse(sock_event.details).client
|
|
1145
|
-
if (!client) return null
|
|
1146
|
-
|
|
1147
|
-
var http_req = db.prepare('select * from usage where event = "http request" and date < ? and '
|
|
1148
|
-
+ ' details like ? and '
|
|
1149
|
-
+ nots + ' order by date desc limit 1').get([sock_event.date,
|
|
1150
|
-
'%'+client+'%'])
|
|
1151
|
-
if (!http_req) return null
|
|
1152
|
-
|
|
1153
|
-
var delay = sock_event.date - http_req.date
|
|
1154
|
-
var res = !delay || delay > 300 ? 'fail' : delay
|
|
1155
|
-
if (delay && delay < 300
|
|
1156
|
-
&& JSON.parse(sock_event.details).ip
|
|
1157
|
-
!== JSON.parse(http_req.details).ip)
|
|
1158
|
-
console.error('Yuck!', delay, sock_event, http_req)
|
|
1159
|
-
return [res, JSON.parse(http_req.details).url]
|
|
1160
|
-
}
|
|
1161
|
-
function sock_open_times () {
|
|
1162
|
-
var opens = db.prepare('select * from usage where event = "socket open" and '
|
|
1163
|
-
+ nots + ' order by date desc limit ?').all(500)
|
|
1164
|
-
var times = []
|
|
1165
|
-
for (var i=0; i<opens.length; i++) {
|
|
1166
|
-
times.push(sock_open_time(opens[i]))
|
|
1167
|
-
// Get the most recent http hit before this open, from the same client id
|
|
1168
|
-
// subract the times
|
|
1169
|
-
}
|
|
1170
|
-
return times
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
bus('socket_load_times').getter = () => {
|
|
1174
|
-
return {_: sock_open_times()}
|
|
1175
|
-
}
|
|
1176
|
-
},
|
|
1177
|
-
log_usage(event, details) {
|
|
1178
|
-
bus.usage_log_db.prepare('insert into usage (date, event, details) values (?, ?, ?)')
|
|
1179
|
-
.run([new Date().getTime()/1000,
|
|
1180
|
-
event,
|
|
1181
|
-
JSON.stringify(details)])
|
|
1182
|
-
},
|
|
1183
|
-
|
|
1184
1035
|
time () {
|
|
1185
1036
|
if (bus('time*').getter.length > 0)
|
|
1186
1037
|
// Then it's already installed
|
|
@@ -2371,32 +2222,41 @@ function import_server (bus, make_statebus, options)
|
|
|
2371
2222
|
// Installs a GET handler at route that gets state from a getter function
|
|
2372
2223
|
// Note: Makes too many textbusses. Should re-use one.
|
|
2373
2224
|
http_serve: function http_serve (route, getter) {
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
}
|
|
2225
|
+
// if (!this.filebus) {
|
|
2226
|
+
// this.filebus = make_statebus()
|
|
2227
|
+
// this.filebus.label = 'filebus'
|
|
2228
|
+
// }
|
|
2229
|
+
// var filebus = this.filebus
|
|
2230
|
+
|
|
2231
|
+
var cache = this.compiled_coffee = this.compiled_coffee || {}
|
|
2232
|
+
|
|
2233
|
+
// filebus('*').getter = (filename, old) => ({
|
|
2234
|
+
// etag: Math.random() + '',
|
|
2235
|
+
// contents: getter(filename)
|
|
2236
|
+
// })
|
|
2381
2237
|
bus.http.get(route, (req, res) => {
|
|
2238
|
+
console.log('Getting', route, req.path, 'from',
|
|
2239
|
+
Object.values(cache).map(x => JSON.stringify(x).substr(0, 100)))
|
|
2382
2240
|
var path = req.path
|
|
2383
|
-
var etag =
|
|
2241
|
+
var etag = cache[path] && cache[path].etag
|
|
2384
2242
|
if (etag && req.get('If-None-Match') === etag) {
|
|
2385
2243
|
res.status(304).end()
|
|
2386
2244
|
return
|
|
2387
2245
|
}
|
|
2388
2246
|
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2247
|
+
if (!cache[path])
|
|
2248
|
+
cache[path] = {
|
|
2249
|
+
etag: Math.random() + '',
|
|
2250
|
+
contents: getter(path)
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
res.setHeader('Cache-Control', 'public')
|
|
2254
|
+
// res.setHeader('Cache-Control', 'public, max-age='
|
|
2255
|
+
// + (60 * 60 * 24 * 30)) // 1 month
|
|
2256
|
+
res.setHeader('ETag', cache[path].etag)
|
|
2257
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
2258
|
+
res.setHeader('Content-Type', 'application/javascript')
|
|
2259
|
+
res.send(cache[path].contents)
|
|
2400
2260
|
})
|
|
2401
2261
|
},
|
|
2402
2262
|
|
|
@@ -2404,7 +2264,11 @@ function import_server (bus, make_statebus, options)
|
|
|
2404
2264
|
bus.http_serve('/client/:filename', (filename) => {
|
|
2405
2265
|
filename = /\/client\/(.*)/.exec(filename)[0]
|
|
2406
2266
|
var source_filename = filename.substr(1)
|
|
2407
|
-
var source =
|
|
2267
|
+
var source = fs.readFileSync(source_filename) + ''
|
|
2268
|
+
// var source = bus.read_file(source_filename)
|
|
2269
|
+
|
|
2270
|
+
// console.log('Source is', source)
|
|
2271
|
+
|
|
2408
2272
|
if (bus.loading()) throw 'loading'
|
|
2409
2273
|
if (filename.match(/\.coffee$/))
|
|
2410
2274
|
return bus.compile_coffee(source, source_filename)
|
|
@@ -2420,7 +2284,8 @@ function import_server (bus, make_statebus, options)
|
|
|
2420
2284
|
sourceMap: true})
|
|
2421
2285
|
} catch (e) {
|
|
2422
2286
|
console.error('Could not compile ' + e.toString())
|
|
2423
|
-
return 'console.error(' + JSON.stringify(
|
|
2287
|
+
return 'console.error(' + JSON.stringify("When compiling " + filename + ": "
|
|
2288
|
+
+ e.toString()) + ')'
|
|
2424
2289
|
}
|
|
2425
2290
|
|
|
2426
2291
|
var source_map = JSON.parse(compiled.v3SourceMap)
|
package/statebus.js
CHANGED
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
|
|
120
120
|
if (!('key' in obj) || typeof obj.key !== 'string') {
|
|
121
121
|
console.error('Error: set(obj) called on object without a key: ', obj)
|
|
122
|
-
console.trace('Bad set(obj)')
|
|
122
|
+
//console.trace('Bad set(obj)')
|
|
123
123
|
}
|
|
124
124
|
bogus_check(obj.key)
|
|
125
125
|
|
|
@@ -963,45 +963,11 @@
|
|
|
963
963
|
// We're currently calling that (param1,param2:val2) part "function params", and presuming
|
|
964
964
|
// that it exists in the () part of the pattern like in /foo/bar*().
|
|
965
965
|
function func_args () {
|
|
966
|
-
// This code is copied from Raphael Walker's parser.coffee
|
|
967
|
-
function split_once (str, char) {
|
|
968
|
-
var i = str.indexOf(char)
|
|
969
|
-
return i === -1 ? [str, ""] : [str.slice(0, i), str.slice(i + 1)]
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// Function Strings -- an alternative to query strings
|
|
973
|
-
//
|
|
974
|
-
function parse_function_string (str) {
|
|
975
|
-
// Coffeescript doesn't have object comprehensions :(
|
|
976
|
-
var ret = {}
|
|
977
|
-
|
|
978
|
-
// Now process the string:
|
|
979
|
-
str
|
|
980
|
-
// Pull out the parentheses
|
|
981
|
-
.slice(1, -1)
|
|
982
|
-
// Split by commas. TODO: Allow spaces after commas?
|
|
983
|
-
.split(",")
|
|
984
|
-
// Delete empty parts (this ensures that empty strings
|
|
985
|
-
// will properly result in empty func_string, and allows trailing
|
|
986
|
-
// commas)
|
|
987
|
-
.filter(part => part.length)
|
|
988
|
-
.forEach(part => {
|
|
989
|
-
// If the part has a comma, its a key:value, otherwise
|
|
990
|
-
// it's just a singleton
|
|
991
|
-
var [k, v] = split_once(part, ":")
|
|
992
|
-
|
|
993
|
-
if (v.length === 0) ret[k] = true
|
|
994
|
-
// If the value is itself a func_string params object, parse it recursively
|
|
995
|
-
else if (v.startsWith("(")) ret[k] = parse_function_string(v)
|
|
996
|
-
else ret[k] = v
|
|
997
|
-
})
|
|
998
|
-
return ret
|
|
999
|
-
}
|
|
1000
966
|
// console.log('Getting func_args from', func.statebus_binding.args)
|
|
1001
967
|
if (!(func.statebus_binding.args
|
|
1002
968
|
&& func.statebus_binding.args.func_args))
|
|
1003
969
|
return undefined
|
|
1004
|
-
return
|
|
970
|
+
return funcarg_parse(func.statebus_binding.args.func_args)
|
|
1005
971
|
}
|
|
1006
972
|
var f = reactive(function () {
|
|
1007
973
|
// Initialize transaction
|
|
@@ -1447,6 +1413,62 @@
|
|
|
1447
1413
|
bus.get_once(key, (o) => resolve(o)))
|
|
1448
1414
|
}
|
|
1449
1415
|
|
|
1416
|
+
// ******************
|
|
1417
|
+
// Function Args -- an alternative to Query Params
|
|
1418
|
+
|
|
1419
|
+
// Parse a funcarg string.
|
|
1420
|
+
|
|
1421
|
+
function funcarg_parse (str) {
|
|
1422
|
+
// Coffeescript doesn't have object comprehensions :(
|
|
1423
|
+
var ret = {}
|
|
1424
|
+
|
|
1425
|
+
function split_once (str, char) {
|
|
1426
|
+
var i = str.indexOf(char)
|
|
1427
|
+
return i === -1 ? [str, ""] : [str.slice(0, i), str.slice(i + 1)]
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// Now process the string:
|
|
1431
|
+
str
|
|
1432
|
+
// Pull out the parentheses
|
|
1433
|
+
.slice(1, -1)
|
|
1434
|
+
// Split by commas. TODO: Allow spaces after commas?
|
|
1435
|
+
.split(",")
|
|
1436
|
+
// Delete empty parts (this ensures that empty strings
|
|
1437
|
+
// will properly result in empty func_string, and allows trailing
|
|
1438
|
+
// commas)
|
|
1439
|
+
.filter(part => part.length)
|
|
1440
|
+
.forEach(part => {
|
|
1441
|
+
// If the part has a comma, its a key:value, otherwise
|
|
1442
|
+
// it's just a singleton
|
|
1443
|
+
var [k, v] = split_once(part, ":")
|
|
1444
|
+
|
|
1445
|
+
if (v.length === 0) ret[k] = true
|
|
1446
|
+
// If the value is itself a func_string params object, parse it recursively
|
|
1447
|
+
else if (v.startsWith("(")) ret[k] = funcarg_parse(v)
|
|
1448
|
+
else ret[k] = v
|
|
1449
|
+
})
|
|
1450
|
+
return ret
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Generate a funcarg string
|
|
1454
|
+
function funcarg_stringify (obj) {
|
|
1455
|
+
var inner = Object.entries(obj)
|
|
1456
|
+
// Allowed values in KSON are: object, string, true
|
|
1457
|
+
// Arrays are in fact objects, and we don't need to treat them differently.
|
|
1458
|
+
// Their order won't change!
|
|
1459
|
+
.filter(([k, v]) => v)
|
|
1460
|
+
.sort()
|
|
1461
|
+
.map(([k, v]) => {
|
|
1462
|
+
if (typeof v === "boolean") return k
|
|
1463
|
+
if (typeof v === "string") return `${k}:${v}`
|
|
1464
|
+
if (typeof v === "object") return `${k}:${funcarg_string(v)}`
|
|
1465
|
+
return ""
|
|
1466
|
+
})
|
|
1467
|
+
.join(",")
|
|
1468
|
+
return inner.length ? `(${inner})` : ""
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
|
|
1450
1472
|
// ******************
|
|
1451
1473
|
// Proxy
|
|
1452
1474
|
|
|
@@ -2717,6 +2739,7 @@
|
|
|
2717
2739
|
'old_subspace new_subspace bindings run_handler bind unbind reactive uncallback',
|
|
2718
2740
|
'versions new_version',
|
|
2719
2741
|
'aget client_bus_for',
|
|
2742
|
+
'funcarg_parse funcarg_stringify',
|
|
2720
2743
|
'funk_key funk_name funks key_id key_name id',
|
|
2721
2744
|
'pending_gets gets_in gets_out loading_keys loading once',
|
|
2722
2745
|
'global_funk busses rerunnable_funks',
|