total5 0.0.1 → 0.0.2

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/test.js ADDED
@@ -0,0 +1,51 @@
1
+ // Total.js Tests
2
+ // The MIT License
3
+ // Copyright 2023 (c) Peter Širka <petersirka@gmail.com>
4
+
5
+ var Test = { items: [], count: 0 };
6
+
7
+ Test.start = function(message) {
8
+ var divider = '------------------------------------------------';
9
+
10
+ if (Test.count)
11
+ console.log('');
12
+
13
+ console.log(divider);
14
+ console.log('> ' + message.padRight(divider.length - 4) + ' <');
15
+ console.log(divider);
16
+ };
17
+
18
+ Test.print = function(message, err) {
19
+ console.log('[' + (err ? 'FAIL' : 'OK') + ']', message);
20
+ Test.count++;
21
+ if (err) {
22
+ setTimeout(() => process.exit(1), 1);
23
+ if (err instanceof Error)
24
+ throw err;
25
+ else
26
+ throw new Error(err.toString());
27
+ }
28
+ };
29
+
30
+ Test.push = function(name, fn) {
31
+ Test.items.push({ name: name, fn: fn });
32
+ };
33
+
34
+ Test.run = function(callback) {
35
+ console.time('Time');
36
+ Test.items.wait(function(item, next) {
37
+ Test.start(item.name);
38
+ item.fn(next);
39
+ }, function() {
40
+ console.log('');
41
+ console.log('Tests:', Test.count);
42
+ console.timeEnd('Time');
43
+ console.log('');
44
+ if (callback)
45
+ callback();
46
+ else
47
+ process.exit(0);
48
+ });
49
+ };
50
+
51
+ global.Test = Test;
package/tms.js ADDED
@@ -0,0 +1,380 @@
1
+ // Total.js TMS
2
+ // The MIT License
3
+ // Copyright 2022-2023 (c) Peter Širka <petersirka@gmail.com>
4
+
5
+ const ErrorBuilder = F.TBuilders.ErrorBuilder;
6
+
7
+ var Cache = {
8
+ subscribers: {},
9
+ swatchers: {}, // watchers for subscribers
10
+ scache: {}, // cache for subscribers
11
+ pcache: {}, // cache for publishers
12
+ calls: {},
13
+ socket: null,
14
+ timeout: null,
15
+ url: ''
16
+ };
17
+
18
+ exports.cache = Cache;
19
+
20
+ function tmscontroller($) {
21
+
22
+ var temporary = F.temporary;
23
+
24
+ Cache.socket = $;
25
+
26
+ $.autodestroy(() => Cache.socket = null);
27
+
28
+ $.on('open', function(client) {
29
+
30
+ if (temporary.tmsblocked[client.ip] > 5) {
31
+ client.close(4001);
32
+ return;
33
+ }
34
+
35
+ if (F.config.secret_tms) {
36
+ var token = client.headers['x-token']; // || client.query.token;
37
+ if (token != F.config.secret_tms) {
38
+ if (temporary.tmsblocked[client.ip])
39
+ temporary.tmsblocked[client.ip]++;
40
+ else
41
+ temporary.tmsblocked[client.ip] = 1;
42
+ client.close(4001);
43
+ return;
44
+ }
45
+ }
46
+
47
+ delete temporary.tmsblocked[client.ip];
48
+ client.$subscribers = {};
49
+ client.tmsready = true;
50
+ refresh(client);
51
+ });
52
+
53
+ $.on('message', function(client, msg) {
54
+
55
+ // msg.type {String}
56
+ // msg.data {Object}
57
+
58
+ var response;
59
+
60
+ if (client.tmsready) {
61
+ if (msg.type === 'ping') {
62
+ msg.type = 'pong';
63
+ client.send(msg);
64
+ } else if (msg.type === 'subscribe' && msg.id) {
65
+ F.stats.performance.subscribe++;
66
+ var schema = Cache.scache[msg.id];
67
+ if (schema) {
68
+ response = schema.transform(msg.data);
69
+ if (!response.error)
70
+ F.TTMS.subscribe(msg.id, response, client);
71
+ }
72
+ } else if (msg.type === 'subscribers' && msg.subscribers instanceof Array) {
73
+ client.$subscribers = {};
74
+ for (let sub of msg.subscribers)
75
+ client.$subscribers[sub] = true;
76
+ } else if (msg.type === 'call' && msg.id) {
77
+ var tmp = Cache.calls[msg.id];
78
+ if (tmp) {
79
+ F.stats.performance.call++;
80
+ response = tmp.schema.transform(msg.data);
81
+ if (response.error) {
82
+ msg.data = response.error instanceof ErrorBuilder ? response.error.output() : response.response.toString();
83
+ msg.error = true;
84
+ client.send(msg);
85
+ } else {
86
+ tmp.callback(response.response, function(err, response) {
87
+ if (err) {
88
+ msg.error = true;
89
+ if (err instanceof ErrorBuilder)
90
+ msg.data = err.output();
91
+ else
92
+ msg.data = [{ error: err + '' }];
93
+ } else {
94
+ msg.success = true;
95
+ msg.data = response;
96
+ }
97
+ if (client && !client.isClosed)
98
+ client.send(msg);
99
+ }, client);
100
+ }
101
+ }
102
+ } else {
103
+ msg.error = true;
104
+ msg.data = new ErrorBuilder.push(404).output();
105
+ client.send(msg);
106
+ }
107
+ }
108
+ });
109
+ }
110
+
111
+ exports.client = function(url, token, callback) {
112
+
113
+ if (typeof(token) === 'function') {
114
+ callback = token;
115
+ token = undefined;
116
+ }
117
+
118
+ var client = F.TWebSocket.createclient();
119
+ var publishers = {};
120
+ var subscribers = {};
121
+ var callbacks = {};
122
+ var isopen = false;
123
+ var callbackid = 0;
124
+ var timeout;
125
+
126
+ if (token)
127
+ client.headers['x-token'] = token;
128
+
129
+ client.options.reconnectserver = true;
130
+ client.connect(url.replace(/^http/, 'ws'));
131
+ client.ready = false;
132
+
133
+ client.on('destroy', function() {
134
+
135
+ publishers = null;
136
+ subscribers = null;
137
+
138
+ for (let key in callbacks) {
139
+ let item = callbacks[key];
140
+ clearTimeout(item.timeout);
141
+ item.callback && item.callback('TMS has been destroyed');
142
+ }
143
+
144
+ callbacks = null;
145
+
146
+ timeout && clearTimeout(timeout);
147
+ timeout = null;
148
+ });
149
+
150
+ client.on('close', function() {
151
+ isopen = false;
152
+ client.ready = false;
153
+ });
154
+
155
+ client.on('message', function(msg) {
156
+ if (msg.type === 'call') {
157
+ if (callbacks[msg.callbackid]) {
158
+ let tmp = callbacks[msg.callbackid];
159
+ tmp.callback(msg.error ? ErrorBuilder.assign(msg.data) : null, msg.success ? msg.data : null);
160
+ tmp.timeout && clearTimeout(tmp.timeout);
161
+ delete callbacks[msg.callbackid];
162
+ }
163
+ } else if (msg.type === 'publish' && subscribers[msg.id] && publishers[msg.id]) {
164
+ var err = new ErrorBuilder();
165
+ var data = F.TJSONSchema.transform(publishers[msg.id], err, msg.data, true);
166
+ if (data) {
167
+ for (let fn of subscribers[msg.id])
168
+ fn(data);
169
+ }
170
+ } else if (msg.type === 'meta') {
171
+ publishers = {};
172
+ for (let item of msg.publish)
173
+ publishers[item.id] = item.schema;
174
+ sync_subscribers();
175
+ isopen = true;
176
+ client.ready = true;
177
+ client.meta = msg;
178
+ if (callback) {
179
+ setImmediate(callback, null, client, client.meta);
180
+ callback = null;
181
+ }
182
+ client.emit('meta', msg);
183
+ client.emit('ready');
184
+ }
185
+ });
186
+
187
+ var timeouthandler = function(id) {
188
+ let obj = callbacks[id];
189
+ obj.callback && obj.callback('408: Timeout');
190
+ delete callbacks[id];
191
+ };
192
+
193
+ client.call = function(name, data, callback, timeout) {
194
+ if (callback)
195
+ client.$call(name, data, callback, timeout);
196
+ else
197
+ return new Promise((resolve, reject) => client.$call(name, data, (err, res) => err ? reject(err) : resolve(res), timeout));
198
+ };
199
+
200
+ client.$call = function(name, data, callback, timeout) {
201
+ if (isopen) {
202
+ let key = (callbackid++) + '';
203
+ let obj = {};
204
+ obj.callback = callback;
205
+ obj.timeout = setTimeout(timeouthandler, timeout || 10000, key);
206
+ callbacks[key] = obj;
207
+ client.send({ type: 'call', id: name, data: data, callbackid: key });
208
+ } else
209
+ callback('TMS is offline');
210
+ };
211
+
212
+ client.subscribe = function(name, callback) {
213
+ timeout && clearTimeout(timeout);
214
+ timeout = setTimeout(sync_subscribers, 30, true);
215
+ if (subscribers[name])
216
+ subscribers[name].push(callback);
217
+ else
218
+ subscribers[name] = [callback];
219
+ };
220
+
221
+ client.publish = function(name, data) {
222
+ isopen && client.send({ type: 'subscribe', id: name, data: data });
223
+ };
224
+
225
+ var sync_subscribers = function(force) {
226
+ timeout && clearTimeout(timeout);
227
+ timeout = null;
228
+ let keys = Object.keys(subscribers);
229
+ if (force || keys.length)
230
+ client.send({ type: 'subscribers', subscribers: keys });
231
+ };
232
+
233
+ return client;
234
+ };
235
+
236
+ function refresh(client) {
237
+
238
+ if (Cache.socket) {
239
+
240
+ var subscribed = [];
241
+ var published = [];
242
+
243
+ for (let key in Cache.pcache)
244
+ published.push({ id: key, schema: Cache.pcache[key] });
245
+
246
+ for (let key in Cache.scache)
247
+ subscribed.push({ id: key, schema: Cache.scache[key] });
248
+
249
+ var calls = [];
250
+ for (let key in Cache.calls)
251
+ calls.push({ id: key, schema: Cache.calls[key].schema });
252
+
253
+ var msg = { type: 'meta', name: F.config.name, subscribe: subscribed, publish: published, subscribers: Object.keys(Cache.subscribers), call: calls };
254
+ if (client)
255
+ client.send(msg);
256
+ else
257
+ Cache.socket.send(msg);
258
+ }
259
+ }
260
+
261
+ exports.refresh = function() {
262
+ Cache.timeout && clearTimeout(Cache.timeout);
263
+ Cache.timeout = setTimeout(refresh, 500);
264
+ };
265
+
266
+ exports.newpublish = function(name, schema) {
267
+
268
+ if (schema == null) {
269
+ delete Cache.pcache[name];
270
+ exports.refresh();
271
+ return;
272
+ }
273
+
274
+ Cache.pcache[name] = F.TUtils.jsonschema(schema);
275
+ exports.refresh();
276
+ };
277
+
278
+ exports.newcall = function(name, schema, callback) {
279
+
280
+ if (schema == null) {
281
+ delete Cache.calls[name];
282
+ exports.refresh();
283
+ return;
284
+ }
285
+
286
+ if (!callback)
287
+ callback = (data, callback, client) => F.action(schema, data, client).callback(callback);
288
+
289
+ let obj = {};
290
+ obj.schema = F.TUtils.jsonschema(schema);
291
+ obj.callback = callback;
292
+ Cache.calls[name] = obj;
293
+ exports.refresh();
294
+ };
295
+
296
+ exports.newsubscribe = function(name, schema, callback) {
297
+
298
+ if (typeof(schema) === 'function') {
299
+ callback = schema;
300
+ schema = null;
301
+ }
302
+
303
+ if (schema)
304
+ Cache.scache[name] = F.TUtils.jsonschema(schema);
305
+ else
306
+ delete Cache.scache[name];
307
+
308
+ callback && exports.subscribe(name, callback);
309
+ exports.refresh();
310
+
311
+ };
312
+
313
+ exports.publish = function(name, value) {
314
+ if (Cache.socket && Cache.pcache[name]) {
315
+ F.stats.performance.publish++;
316
+ Cache.socket.send({ type: 'publish', id: name, data: value }, client => client.tmsready && client.$subscribers[name]);
317
+ }
318
+ };
319
+
320
+ exports.subscribe = function(name, callback, client) {
321
+ if (client) {
322
+ var arr = Cache.swatchers[name];
323
+ if (arr) {
324
+ for (let fn of arr)
325
+ fn(callback, client);
326
+ }
327
+ } else {
328
+ if (Cache.swatchers[name])
329
+ Cache.swatchers[name].push(callback);
330
+ else
331
+ Cache.swatchers[name] = [callback];
332
+ Cache.subscribers[name] = 1;
333
+ exports.refresh();
334
+ }
335
+ };
336
+
337
+ exports.unsubscribe = function(name, callback) {
338
+ if (Cache.swatchers[name]) {
339
+ exports.refresh();
340
+ if (callback) {
341
+ let index = Cache.swatchers[name].indexOf(callback);
342
+ if (index !== -1)
343
+ Cache.swatchers[name].splice(index, 1);
344
+ if (!Cache.swatchers[name].length)
345
+ delete Cache.swatchers[name];
346
+ if (Cache.swatchers[name])
347
+ Cache.subscribers[name] = 1;
348
+ else
349
+ delete Cache.subscribers[name];
350
+ return index !== -1;
351
+ } else {
352
+ delete Cache.swatchers[name];
353
+ delete Cache.subscribers[name];
354
+ return true;
355
+ }
356
+ }
357
+ return false;
358
+ };
359
+
360
+ F.on('$tms', function() {
361
+
362
+ var endpoint = F.config.$tmsurl;
363
+ var is = Cache.url !== endpoint;
364
+
365
+ if (is && Cache.route) {
366
+ Cache.route.remove();
367
+ Cache.route = null;
368
+ }
369
+
370
+ if ((is && endpoint && F.config.$tms) || (endpoint && F.config.$tms && !Cache.route))
371
+ Cache.route = F.route('SOCKET ' + endpoint, tmscontroller, F.config.$tmsmaxsize * 1024);
372
+
373
+ Cache.url = endpoint;
374
+
375
+ if (endpoint && Cache.token !== F.config.secret_tms) {
376
+ Cache.token = F.config.secret_tms;
377
+ Cache.socket && Cache.socket.close(1000, 'Changed TMS secret');
378
+ }
379
+
380
+ });
package/uibuilder.js ADDED
@@ -0,0 +1,242 @@
1
+ // UIBuilder compiler | https://uibuilder.totaljs.com
2
+ // The MIT License
3
+ // Copyright 2023 (c) Peter Širka <petersirka@gmail.com>
4
+
5
+ const REG_END = /;|\n/;
6
+ const REG_STRING = /'|"/g;
7
+
8
+ exports.compile = async function(opt, callback) {
9
+
10
+ // opt.schema {String/Object}
11
+ // |--- opt.schema.origin {String}
12
+ // opt.local {Boolean}
13
+ // opt.download {Boolean}
14
+ // opt.origin {String}
15
+ // opt.filesystem {Boolean} enables loading components from HDD (default: false)
16
+
17
+ if (!callback)
18
+ return new Promise((resolve, reject) => exports.compile(opt, (err, response) => err ? reject(err) : resolve(response)));
19
+
20
+ if (typeof(opt.schema) === 'string')
21
+ opt.schema = opt.schema.parseJSON();
22
+
23
+ var schema = opt.schema;
24
+ var instances = getInstances(schema);
25
+ var used = {};
26
+ var response = {};
27
+
28
+ for (let instance of instances)
29
+ used[instance.component] = '#';
30
+
31
+ if (opt.local) {
32
+ response.components = used;
33
+ } else {
34
+ let components = await getComponents(opt, used);
35
+ response.components = components;
36
+ }
37
+
38
+ for (let key in schema) {
39
+ if (key !== 'components')
40
+ response[key] = schema[key];
41
+ }
42
+
43
+ response.inputs = schema.inputs;
44
+ response.outputs = schema.outputs;
45
+ response.children = schema.children;
46
+ response.compiled = true;
47
+
48
+ if (response.cssoutput)
49
+ response.css = response.cssoutput;
50
+
51
+ delete response.editor;
52
+ delete response.cssoutput;
53
+ delete response.csseditor;
54
+ delete response.csspreview;
55
+
56
+ callback(null, response);
57
+ };
58
+
59
+ exports.download = async function(opt, callback) {
60
+
61
+ if (!callback)
62
+ return new Promise((resolve, reject) => exports.download(opt, (err, response) => err ? reject(err) : resolve(response)));
63
+
64
+ try {
65
+ let response = await getComponents2(opt);
66
+ callback(null, response);
67
+ } catch (e) {
68
+ callback(e);
69
+ }
70
+
71
+ };
72
+
73
+ function getInstances(schema) {
74
+ var response = [];
75
+ var browse = function(parent) {
76
+
77
+ for (let arr of parent.children) {
78
+ for (let child of arr) {
79
+ let cloned = F.TUtils.clone(child);
80
+ cloned.children = undefined;
81
+ response.push(cloned);
82
+ browse(child);
83
+ }
84
+ }
85
+ };
86
+
87
+ browse(schema);
88
+ return response;
89
+ }
90
+
91
+ async function Download(url, local = false) {
92
+ return new Promise(function(resolve) {
93
+
94
+ if (local && url[0] === '~') {
95
+ // File on HDD (potential dangerous)
96
+ F.Fs.readFile(url.substring(1), 'utf8', function(err, response) {
97
+ resolve(err ? '' : (response.isJSON() ? response.parseJSON(true) : response));
98
+ });
99
+ } else {
100
+ let opt = {};
101
+ opt.url = url;
102
+ opt.method = 'GET';
103
+ opt.keepalive = true;
104
+ opt.insecure = true;
105
+ opt.callback = function(err, response) {
106
+ resolve(response.status === 200 ? (response.body.isJSON() ? response.body.parseJSON(true) : response.body) : '');
107
+ };
108
+ REQUEST(opt);
109
+ }
110
+ });
111
+ }
112
+
113
+ function parseorigin(url) {
114
+
115
+ var origin = '';
116
+
117
+ if (url.charAt(0) !== '/') {
118
+ var index = url.indexOf('/', 9);
119
+ origin = index === -1 ? url : url.substring(0, index);
120
+ }
121
+
122
+ return origin;
123
+ }
124
+
125
+ async function getComponents(opt, used) {
126
+
127
+ var schema = opt.schema;
128
+ var download = opt.download;
129
+ var components = {};
130
+ var arr = [];
131
+
132
+ for (let key in schema.components)
133
+ arr.push({ id: key, value: schema.components[key] });
134
+
135
+ for (let com of arr) {
136
+
137
+ if (com.value.indexOf('.json') === -1 && !used[com.id])
138
+ continue;
139
+
140
+ let url = com.value;
141
+ let origin = opt.origin || schema.origin;
142
+
143
+ if (url[0] === '/') {
144
+ url = origin + url;
145
+ } else
146
+ origin = parseorigin(url);
147
+
148
+ let body = await Download(url.format(com.id), opt.filesystem);
149
+
150
+ if (typeof(body) === 'string') {
151
+
152
+ let index = body.indexOf('exports.render');
153
+ if (index === -1) {
154
+ // without render
155
+ continue;
156
+ }
157
+
158
+ index += 14;
159
+
160
+ let end = body.substring(index).match(REG_END);
161
+ if (!end) {
162
+ // without end
163
+ continue;
164
+ }
165
+
166
+ let render = body.substring(body.indexOf('=', index) + 1, index + end.index).trim().replace(REG_STRING, '').format(com.id);
167
+
168
+ if (render[0] === '/')
169
+ render = (origin || schema.origin || '') + render;
170
+
171
+ if (download) {
172
+ if (render.substring(0, 7) === 'base64 ') {
173
+ components[com.id] = render;
174
+ } else {
175
+ let html = await Download(render, opt.filesystem);
176
+ if (html)
177
+ components[com.id] = 'base64 ' + Buffer.from(encodeURIComponent(html), 'utf8').toString('base64');
178
+ }
179
+ } else
180
+ components[com.id] = render;
181
+ } else {
182
+ for (let key in body)
183
+ arr.push({ id: key, value: body[key] });
184
+ }
185
+ }
186
+
187
+ return components;
188
+ }
189
+
190
+ async function getComponents2(opt) {
191
+
192
+ var list = opt.components;
193
+ var origin = opt.origin;
194
+ var components = {};
195
+ var arr = [];
196
+
197
+ for (let key in list)
198
+ arr.push({ id: key, value: list[key] });
199
+
200
+ for (let com of arr) {
201
+
202
+ let url = com.value;
203
+ if (url[0] === '/')
204
+ url = origin + url;
205
+
206
+ let body = await Download(url.format(com.id), opt.filesystem);
207
+
208
+ if (typeof(body) === 'string') {
209
+
210
+ let index = body.indexOf('exports.render');
211
+ if (index === -1) {
212
+ // without render
213
+ continue;
214
+ }
215
+
216
+ index += 14;
217
+
218
+ let end = body.substring(index).match(REG_END);
219
+ if (!end) {
220
+ // without end
221
+ continue;
222
+ }
223
+
224
+ let render = body.substring(body.indexOf('=', index) + 1, index + end.index).trim().replace(REG_STRING, '').format(com.id);
225
+
226
+ if (render.substring(0, 7) === 'base64 ') {
227
+ components[com.id] = render;
228
+ } else {
229
+ if (render[0] === '/')
230
+ render = origin + render;
231
+ let html = await Download(render, opt.filesystem);
232
+ if (html)
233
+ components[com.id] = 'base64 ' + Buffer.from(encodeURIComponent(html), 'utf8').toString('base64');
234
+ }
235
+ } else {
236
+ for (let key in body)
237
+ arr.push({ id: key, value: body[key] });
238
+ }
239
+ }
240
+
241
+ return components;
242
+ }