nexusframework 0.3.0-beta.73 → 0.3.0-beta.74

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 (60) hide show
  1. package/LICENSE.md +55 -55
  2. package/README.md +12 -12
  3. package/about/index.json +5 -5
  4. package/about/index.nhp +4 -4
  5. package/icon.png +0 -0
  6. package/index.d.ts +0 -2
  7. package/index.js +0 -0
  8. package/index.js.map +1 -1
  9. package/index.ts +101 -101
  10. package/indexOfSkeleton.nhp +133 -133
  11. package/legacySkeleton.nhp +22 -22
  12. package/loader/overlay.css +1 -1
  13. package/loader/overlay.html +25 -25
  14. package/loader/overlay.scss +152 -152
  15. package/package.json +102 -90
  16. package/scripts/es5/compat.js +100 -100
  17. package/scripts/es5/compat.min.js +1 -1
  18. package/scripts/es5/loader.js +0 -0
  19. package/scripts/es5/loader.js.map +0 -0
  20. package/scripts/es5/loader.min.js +1 -1
  21. package/scripts/es5/loader.min.js.map +0 -0
  22. package/scripts/es5/nexusframeworkclient.js +1224 -1224
  23. package/scripts/es5/nexusframeworkclient.js.map +0 -0
  24. package/scripts/es5/nexusframeworkclient.min.js +1 -1
  25. package/scripts/es5/nexusframeworkclient.min.js.map +0 -0
  26. package/scripts/es6/compat.js +64 -64
  27. package/scripts/es6/compat.min.js +1 -1
  28. package/scripts/es6/loader.js +0 -0
  29. package/scripts/es6/loader.js.map +0 -0
  30. package/scripts/es6/loader.min.js +1 -1
  31. package/scripts/es6/loader.min.js.map +0 -0
  32. package/scripts/es6/nexusframeworkclient.js +1146 -1146
  33. package/scripts/es6/nexusframeworkclient.js.map +0 -0
  34. package/scripts/es6/nexusframeworkclient.min.js +1 -1
  35. package/scripts/es6/nexusframeworkclient.min.js.map +0 -0
  36. package/scripts/index.d.ts +259 -259
  37. package/scripts/src/compat.ts +62 -62
  38. package/scripts/src/loader.ts +385 -385
  39. package/scripts/src/nexusframeworkclient.ts +1163 -1163
  40. package/scripts/tsconfig.json +11 -11
  41. package/src/cli.d.ts +1 -0
  42. package/src/cli.js +102 -102
  43. package/src/cli.js.map +1 -1
  44. package/src/cli.ts +101 -101
  45. package/src/compileScripts.d.ts +0 -0
  46. package/src/compileScripts.js +0 -0
  47. package/src/compileScripts.js.map +1 -1
  48. package/src/compileScripts.ts +153 -153
  49. package/src/nexusframework.d.ts +5 -5
  50. package/src/nexusframework.js +44 -37
  51. package/src/nexusframework.js.map +1 -1
  52. package/src/nexusframework.ts +3631 -3622
  53. package/templates/basic/package.json +3 -3
  54. package/templates/basic/www/skeleton.nhp +1 -1
  55. package/templates/bootstrap/package.json +2 -2
  56. package/templates/bootstrap/www/skeleton.nhp +1 -1
  57. package/templates/bootstrap-material-design/package.json +2 -2
  58. package/templates/bootstrap-material-design/www/skeleton.nhp +1 -1
  59. package/tsconfig.json +25 -22
  60. package/types.d.ts +612 -612
@@ -1,1163 +1,1163 @@
1
- /// <reference path="../index.d.ts" />
2
- (function(window) {
3
- const BSON = new window['bson'];
4
- Object.defineProperties(window, {
5
- NexusFrameworkTransport: {
6
- configurable: true,
7
- set: function (instance) {
8
- Object.defineProperty(window, "NexusFrameworkTransport", {
9
- value: instance
10
- });
11
- },
12
- get: function () {
13
- var impl: NexusFrameworkTransport;
14
- if ("XMLHttpRequest" in window) {
15
- class NexusFrameworkXMLHttpRequestResponse implements NexusFrameworkTransportResponse {
16
- hadData: boolean;
17
- abResponse: boolean;
18
- private _url: string;
19
- private parsedJson: any;
20
- private parsedBson: any;
21
- private request: XMLHttpRequest;
22
- private processedHeaders: {[index: string]: string[]};
23
- constructor(request: XMLHttpRequest, url: string, hadData?: boolean, abResponse?: boolean) {
24
- this._url = url;
25
- this.request = request;
26
- this.hadData = hadData;
27
- this.abResponse = abResponse;
28
- }
29
- get url() {
30
- return this.request.responseURL || this._url;
31
- }
32
- get code() {
33
- return this.request.status;
34
- }
35
- get contentLength() {
36
- return parseInt(this.request.getResponseHeader("content-length")) || this.request.responseText.length;
37
- }
38
- get contentFromJSON() {
39
- if (!this.parsedJson)
40
- this.parsedJson = JSON.parse(this.request.responseText);
41
- return this.parsedJson;
42
- }
43
- get contentFromBSON() {
44
- if (!this.parsedBson) {
45
- var response = this.request.response;
46
- if (typeof response === "string")
47
- response = Buffer.from(response, "utf8");
48
- else
49
- response = new Buffer(response);
50
- this.parsedBson = BSON.deserialize(response, {promoteValues:true});
51
- }
52
- return this.parsedBson;
53
- }
54
- get contentAsString() {
55
- return this.request.responseText;
56
- }
57
- get contentAsArrayBuffer() {
58
- return this.request.response;
59
- }
60
- get headers(): {[index: string]: string[]} {
61
- if (!this.processedHeaders) {
62
- const headers: {[index: string]: string[]} = this.processedHeaders = {};
63
- this.request.getAllResponseHeaders().split(/\r?\n/g).forEach(function (header) {
64
- const index = header.indexOf(":");
65
- var key: string, val: string;
66
- if (index > 0) {
67
- key = header.substring(0, index).trim().toLowerCase();
68
- val = header.substring(index + 1).trim();
69
- } else
70
- key = header.trim().toLowerCase();
71
- var list = headers[key];
72
- if (!list)
73
- list = headers[key] = [];
74
- if (val)
75
- list.push(val);
76
- });
77
- }
78
- return this.processedHeaders;
79
- }
80
- }
81
- const execute = function (method: string, url: string, data: any, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
82
- const request = new XMLHttpRequest();
83
- var responseIsArrayBuffer;
84
- try {
85
- if (!wantsBinary)
86
- throw false;
87
- request.responseType = "arraybuffer";
88
- if (request.responseType !== "arraybuffer") {
89
- console.error("Could not request arraybuffer");
90
- throw false;
91
- }
92
- responseIsArrayBuffer = true;
93
- } catch(e) {
94
- responseIsArrayBuffer = false;
95
- }
96
- request.open(method, url, true);
97
- if (extraHeaders)
98
- Object.keys(extraHeaders).forEach(function (key) {
99
- request.setRequestHeader(key, extraHeaders[key]);
100
- });
101
- if (progcb)
102
- request.onprogress = function (ev) {
103
- if (ev.lengthComputable && ev.total)
104
- progcb(ev.loaded, ev.total);
105
- }
106
- request.onreadystatechange = function (e) {
107
- if (request.readyState === XMLHttpRequest.DONE)
108
- cb(new NexusFrameworkXMLHttpRequestResponse(request, url, !!data, responseIsArrayBuffer));
109
- }
110
- const type = data && data.type;
111
- if (type)
112
- switch(type) {
113
- case "":
114
- case "text/urlencoded":
115
- var dat = "";
116
- for (var entry of data.data as any) {
117
- if (dat)
118
- dat += "&";
119
- dat += encodeURIComponent(entry[0]);
120
- dat += "=";
121
- dat += encodeURIComponent(entry[1]);
122
- }
123
- request.setRequestHeader("Content-Type", "text/urlencoded");
124
- request.send(dat);
125
- break;
126
- case "text/json":
127
- request.setRequestHeader("Content-Type", "text/json");
128
- request.send(JSON.stringify(data.data));
129
- break;
130
- case "application/bson":
131
- request.setRequestHeader("Content-Type", "application/bson");
132
- request.send(BSON.serialize(data.data));
133
- break;
134
- case "multipart/form-data":
135
- request.send(data.data);
136
- break;
137
- default:
138
- request.send(data);
139
- }
140
- else
141
- request.send(data);
142
- }
143
- impl = {
144
- get: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
145
- execute("GET", url, undefined, cb, extraHeaders, progcb, wantsBinary);
146
- },
147
- head: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
148
- execute("HEAD", url, undefined, cb, extraHeaders, progcb, wantsBinary);
149
- },
150
- post: function (url: string, data: any, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
151
- execute("POST", url, data, cb, extraHeaders, progcb, wantsBinary);
152
- },
153
- put: function (url: string, data: any, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
154
- execute("PUT", url, data, cb, extraHeaders, progcb, wantsBinary);
155
- },
156
- execute,
157
- del: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
158
- execute("DELETE", url, undefined, cb, extraHeaders, progcb, wantsBinary);
159
- }
160
- };
161
- } else
162
- throw new Error("No transport implementations");
163
- Object.defineProperty(window, "NexusFrameworkTransport", {
164
- value: impl
165
- });
166
- return impl;
167
- }
168
- },
169
- NexusFrameworkImpl: {
170
- configurable: true,
171
- set: function (instance) {
172
- Object.defineProperty(window, "NexusFrameworkImpl", {
173
- value: instance
174
- });
175
- },
176
- get: function () {
177
- const loader = window.NexusFrameworkLoader;
178
- const showError = loader.showError;
179
- const debug = /*function(...args: any[]){};*/console.log.bind(console);
180
- const GA_ANALYTICS: NexusFrameworkAnalyticsAdapter = {
181
- reportError: function (err: Error, fatal?: boolean) {
182
- if (window.ga)
183
- try {
184
- window.ga('send', 'exception', {
185
- 'exDescription': (err.stack || "" + err).replace(/\n/g, "\n\t"),
186
- 'exFatal': fatal
187
- });
188
- } catch (e) {
189
- console.warn(e);
190
- }
191
- },
192
- reportEvent: function (category: string, action: string, label?: string, value?: number) {
193
- if (window.ga)
194
- try {
195
- window.ga('send', 'event', category, action, label, value);
196
- } catch (e) {
197
- console.warn(e);
198
- }
199
- },
200
- reportPage: function (path?: string) {
201
- if (window.ga)
202
- try {
203
- if (!path)
204
- path = location.pathname;
205
- window.ga('set', 'page', path);
206
- window.ga('send', 'pageview');
207
- } catch (e) {
208
- console.warn(e);
209
- }
210
- }
211
- };
212
- const r = document.createElement("a");
213
- const protocol = location.href.match(/^\w+:/)[0];
214
- const resolveUrl = function (url: string) {
215
- r.setAttribute("href", url);
216
- var href = r.href;
217
- if (/^\/\//.test(href))
218
- href = protocol + href;
219
- return href;
220
- }
221
- interface PageSystemImpl {
222
- requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void;
223
- }
224
- const convertResponse = function (res: NexusFrameworkPageSystemResponse, url = location.href): NexusFrameworkTransportResponse{
225
- var storage: any;
226
- return (typeof res.data === "string") ? {
227
- url,
228
- code: res.code,
229
- contentAsString: res.data,
230
- get contentLength() {
231
- return parseInt(res.headers['content-length'] && res.headers['content-length'][0]) || res.data.length
232
- },
233
- get contentAsArrayBuffer(): ArrayBuffer{
234
- throw new Error("Not implemented");
235
- },
236
- get contentFromBSON() {
237
- if (!storage)
238
- storage = JSON.parse(res.data);
239
- return storage;
240
- },
241
- get contentFromJSON() {
242
- if (!storage)
243
- storage = JSON.parse(res.data);
244
- return storage;
245
- },
246
- headers: res.headers
247
- } : {
248
- url,
249
- code: res.code,
250
- get contentAsString() {
251
- if (!storage)
252
- storage = JSON.stringify(res.data);
253
- return storage;
254
- },
255
- get contentAsArrayBuffer(): ArrayBuffer{
256
- throw new Error("Not implemented");
257
- },
258
- contentFromJSON: res.data,
259
- contentFromBSON: res.data,
260
- headers: res.headers,
261
- get contentLength() {
262
- var length = parseInt(res.headers['content-length'] && res.headers['content-length'][0]);
263
- if (length)
264
- return length;
265
- if (!storage)
266
- storage = JSON.stringify(res.data);
267
- return storage.length;
268
- }
269
- };
270
- }
271
- const loaderContainerRegex = /(^|\s)loader\-(progress|error)\-container(\s|$)/;
272
- class NexusFrameworkBase implements NexusFrameworkClient {
273
- public readonly url: string;
274
- public readonly io: SocketIOClient.Socket;
275
- private activerid = 0;
276
- private _analytics: NexusFrameworkAnalyticsAdapter;
277
- private errorreporter: (err: Error, fatal?: boolean) => void;
278
- private components: {[index: string]: NexusFrameworkComponentFactory} = {};
279
- private pagesyshandler: (res: NexusFrameworkTransportResponse, pageReady: (err?: Error) => void) => void;
280
- private pagesysprerequest: (path: string) => boolean;
281
- private pagesysimpl: PageSystemImpl;
282
- private currentUserID: string = undefined;
283
- private progressVisible = false;
284
- private animationTiming = 500;
285
- public constructor(url = "/", io?: SocketIOClient.Socket) {
286
- url = resolveUrl(url);
287
- if (!/\/$/.test(url))
288
- url += "/";
289
- Object.defineProperties(this, {
290
- io: {
291
- value: io
292
- },
293
- url: {
294
- value: url
295
- }
296
- });
297
- this._analytics = GA_ANALYTICS;
298
- }
299
-
300
- resolveUrl(url: string) {
301
- if (/^\w+\:/.test(url))
302
- return url;
303
- return resolveUrl(this.url + url);
304
- }
305
-
306
- disableAll(root: HTMLElement = document.body) {
307
- const focusable = root.querySelectorAll("a, input, select, textarea, iframe, button, *[focusable], *[tabindex]");
308
- for (var i = 0; i < focusable.length; i++) {
309
- const child = focusable[i];
310
- const tabindex = child.getAttribute("tabindex");
311
- if (tabindex == "-1")
312
- return;
313
- child.setAttribute("data-tabindex", tabindex ? tabindex : "");
314
- child.setAttribute("data-tabindex", "-1");
315
- }
316
- }
317
- enableAll(root: HTMLElement = document.body) {
318
- const focusable = root.querySelectorAll("*[data-tabindex]");
319
- for (var i = 0; i < focusable.length; i++) {
320
- const child = focusable[i];
321
- child.setAttribute("tabindex", child.getAttribute("data-tabindex"));
322
- child.removeAttribute("data-tabindex");
323
- }
324
- }
325
-
326
- get analytics() {
327
- return this._analytics;
328
- }
329
- set analytics(value) {
330
- this._analytics = value ? value : GA_ANALYTICS;
331
- }
332
-
333
- reportError(err: Error, fatal?: boolean) {
334
- console[fatal ? "error" : "warn"](err.stack);
335
- if (this.errorreporter)
336
- this.errorreporter(err, fatal);
337
- }
338
- installErrorReporter(errorreporter: (err: Error, fatal?: boolean) => void) {
339
- this.errorreporter = errorreporter;
340
- }
341
-
342
- registerComponent(selector: string, impl: NexusFrameworkComponentFactory) {
343
- if (impl) {
344
- this.components[selector] = impl;
345
- this.createComponents(document.head);
346
- this.createComponents(document.body);
347
- } else {
348
- delete this.components[selector];
349
- var destroy = (root: HTMLElement) => {
350
- const elements = root.querySelectorAll(selector);
351
- if (!elements.length)
352
- return;
353
-
354
- for (var i = 0; i < elements.length; i++) {
355
- const element = elements[i];
356
- var components = element['__nf_cmapping__'];
357
- if (!components)
358
- components = element['__nf_cmapping__'] = {};
359
- const component: NexusFrameworkComponent = components[selector];
360
- if (component) {
361
- try {
362
- component.destroy();
363
- } catch (e) {
364
- this.reportError(e);
365
- }
366
- }
367
- }
368
- }
369
- destroy(document.head);
370
- destroy(document.body);
371
- }
372
- }
373
- unregisterComponent(selector: string, impl: NexusFrameworkComponentFactory) {
374
- }
375
- createComponents(root: Element): void {
376
- Object.keys(this.components).forEach((selector) => {
377
- const elements = root.querySelectorAll(selector);
378
- if (!elements.length)
379
- return;
380
-
381
- for (var i = 0; i < elements.length; i++) {
382
- const element = elements[i];
383
- var components = element['__nf_cmapping__'];
384
- if (!components)
385
- components = element['__nf_cmapping__'] = {};
386
- if (components[selector])
387
- continue;
388
- const componentFactory = this.components[selector];
389
- try {
390
- (components[selector] = new componentFactory()).create(element as HTMLElement);
391
- } catch (e) {
392
- this.reportError(e);
393
- }
394
- }
395
- });
396
- }
397
- destroyComponents(root: HTMLElement): void {
398
- Object.keys(this.components).forEach((selector) => {
399
- const elements = root.querySelectorAll(selector);
400
- if (!elements.length)
401
- return;
402
-
403
- for (var i = 0; i < elements.length; i++) {
404
- const element = elements[i];
405
- var components = element['__nf_cmapping__'];
406
- if (!components)
407
- components = element['__nf_cmapping__'] = {};
408
- const component: NexusFrameworkComponent = components[selector];
409
- if (component) {
410
- try {
411
- component.destroy();
412
- } catch (e) {
413
- this.reportError(e);
414
- }
415
- }
416
- }
417
- });
418
- }
419
- restoreComponents(root: HTMLElement, state: Object): void {
420
- Object.keys(this.components).forEach((selector) => {
421
- const states: any[] = state[selector];
422
- if (!states)
423
- return;
424
-
425
- const elements = root.querySelectorAll(selector);
426
- if (!elements.length)
427
- return;
428
-
429
- for (var i = 0; i < elements.length; i++) {
430
- const element = elements[i];
431
- if (states.length) {
432
- const _state = states.shift();
433
- if (_state) {
434
- var components = element['__nf_cmapping__'];
435
- if (!components)
436
- components = element['__nf_cmapping__'] = {};
437
- var component: NexusFrameworkComponent = components[selector];
438
- if (!component) {
439
- const componentFactory = this.components[selector];
440
- try {
441
- (component = components[selector] = new componentFactory()).create(element as HTMLElement);
442
- } catch (e) {
443
- this.reportError(e);
444
- console.warn(e);
445
- continue;
446
- }
447
- }
448
- try {
449
- component.restore(_state);
450
- } catch (e) {
451
- this.reportError(e);
452
- console.warn(e);
453
- }
454
- }
455
- } else
456
- break;
457
- }
458
- });
459
- }
460
- saveComponents(root: HTMLElement): Object {
461
- var state = {};
462
- Object.keys(this.components).forEach((selector) => {
463
- const states = state[selector] = [];
464
- const elements = root.querySelectorAll(selector);
465
- if (!elements.length)
466
- return;
467
-
468
- for (var i = 0; i < elements.length; i++) {
469
- const element = elements[i];
470
- var components = element['__nf_cmapping__'];
471
- if (!components)
472
- components = element['__nf_cmapping__'] = {};
473
- var component: NexusFrameworkComponent = components[selector];
474
- if (!component) {
475
- const componentFactory = this.components[selector];
476
- try {
477
- (component = components[selector] = new componentFactory).create(element as HTMLElement);
478
- } catch (e) {
479
- states.push(undefined);
480
- this.reportError(e);
481
- console.warn(e);
482
- continue;
483
- }
484
- }
485
- try {
486
- states.push(component.save());
487
- } catch (e) {
488
- states.push(undefined);
489
- this.reportError(e);
490
- console.warn(e);
491
- }
492
- }
493
- });
494
- return state;
495
- }
496
-
497
- initPageSystem(opts?: NexusFrameworkPageSystemOptions) {
498
- if (!loader)
499
- return console.error("The NexusFramework Loader is required for the dynamic Page System to work correctly.");
500
- if (!history.pushState || !history.replaceState)
501
- return console.warn("This browser is missing an essential feature required for the dynamic Page System.")
502
- if (this.pagesyshandler)
503
- return console.warn("Page System already initialized, ignoring additional requests to initialize.")
504
-
505
- interface PageState {
506
- data: {
507
- title?: string;
508
- scroll?: number[];
509
- user?: string | number;
510
- response?: NexusFrameworkTransportResponse;
511
- error?: Error;
512
- body?: any;
513
- };
514
- url: string;
515
- updated: number;
516
- size: number;
517
- }
518
-
519
- opts = opts || {};
520
- const self = this;
521
- var pageStatesSize = 0;
522
- const pageStates: PageState[] = [];
523
- this.animationTiming = opts.animationTiming || 500;
524
- const pageStateCacheSize = opts.pageHistoryCacheSize || 25000000;
525
- const wrapCB = (cb: (res: NexusFrameworkTransportResponse) => void) => {
526
- return (res: NexusFrameworkTransportResponse) => {
527
- const user = res.headers['x-logged-user'];
528
- if (user)
529
- self.currentUserID = user[0];
530
- else
531
- self.currentUserID = undefined;
532
- debug("Current UID", self.currentUserID);
533
- cb(res);
534
- if (res.code === 200)
535
- setTimeout(() => {
536
- self._analytics.reportPage();
537
- });
538
- }
539
- }
540
- loader.showError = function(error: Error) {
541
- const match = location.href.match(beforeHash);
542
- const baseurl = match && match[1] || location.href;
543
- const length = error.stack.length;
544
- const tooBig = pageStateCacheSize <= length;
545
- try {
546
- const cPageStates = pageStates.length;
547
- for(var i=0; i<cPageStates; i++) {
548
- const state = pageStates[i];
549
- if (state.url === baseurl) {
550
- if (tooBig) {
551
- pageStates.splice(i, 1);
552
- pageStatesSize -= state.size;
553
- } else {
554
- state.data = {error};
555
- state.updated = +new Date;
556
- pageStatesSize += length - state.size;
557
- }
558
- throw true;
559
- }
560
- }
561
- if (tooBig)
562
- throw true;
563
- pageStates.push({
564
- url: baseurl,
565
- data: {error},
566
- updated: +new Date,
567
- size: length
568
- });
569
- pageStatesSize += length;
570
- } catch(e) {
571
- if (e !== true)
572
- throw e;
573
- }
574
- return showError(error);
575
- }
576
- const transportPageSystem = {
577
- requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void {
578
- if (self.pagesysprerequest && !self.pagesysprerequest(path)) {
579
- self.defaultRequestPage(path, post);
580
- return;
581
- }
582
- if (!opts.noprogress) {
583
- self.disableAll();
584
- loader.showProgress();
585
- }
586
- const url = self.resolveUrl(":pagesys/" + path);
587
- const _cb = opts.noprogress ? cb : function (res: NexusFrameworkTransportResponse) {
588
- if (opts.noprogress)
589
- cb(res);
590
- else
591
- loader.showProgress(() => {
592
- cb(res);
593
- self.enableAll();
594
- });
595
- };
596
- if (post)
597
- window.NexusFrameworkTransport.post(url, post, wrapCB(_cb), undefined, undefined, true);
598
- else
599
- window.NexusFrameworkTransport.get(url, wrapCB(_cb), undefined, undefined, true);
600
- }
601
- };
602
-
603
- if (!opts.noio && this.io) {
604
- const io = this.io;
605
- this.pagesysimpl = {
606
- requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void {
607
- if (io.connected) {
608
- if (self.pagesysprerequest && !self.pagesysprerequest(path)) {
609
- self.defaultRequestPage(path, post);
610
- return;
611
- }
612
- if (!opts.noprogress) {
613
- self.disableAll();
614
- loader.showProgress();
615
- }
616
- const headers: any = {
617
- "referrer": location.href
618
- };
619
- var val = document.cookie;
620
- if (val)
621
- headers['cookie'] = val;
622
- io.emit("page", post ? "POST" : "GET", path, post, headers, function (res: NexusFrameworkPageSystemResponse) {
623
- if (rid != self.activerid)
624
- return;
625
-
626
- const cookies = res.headers['set-cookie'];
627
- if (cookies) {
628
- cookies.forEach(function (cookie) {
629
- document.cookie = cookie;
630
- });
631
- }
632
-
633
- const _cb = opts.noprogress ? cb : function (res) {
634
- if (!opts.noprogress)
635
- cb(res);
636
- else
637
- loader.showProgress(() => {
638
- cb(res);
639
- self.enableAll();
640
- });
641
- };
642
- wrapCB(_cb)(convertResponse(res, self.resolveUrl(path)));
643
- });
644
- } else
645
- transportPageSystem.requestPage(path, cb, post, rid);
646
- }
647
- }
648
- } else
649
- this.pagesysimpl = transportPageSystem;
650
- var base: HTMLBaseElement | NodeListOf<HTMLBaseElement> = document.getElementsByTagName("base");
651
- base = base && base[0];
652
- this.pagesysprerequest = opts.prerequest;
653
- this.pagesyshandler = opts.handler || ((res: NexusFrameworkTransportResponse, pageReady: () => void) => {
654
- try {
655
- const contentType = res.headers['content-type'][0];
656
- if (!/\/html(;.+)?$/.test(contentType.toLowerCase())) {
657
- throw new Error("Content type is not html");
658
- }
659
- } catch (e) {}
660
- const content = res.contentAsString;
661
- const bodyindex = content.indexOf("<body");
662
- if (bodyindex == -1)
663
- throw new Error("Could not find start of body tag");
664
- const endbodyindex = content.indexOf("</body>") || content.indexOf("</ body>");
665
- if (endbodyindex == -1)
666
- throw new Error("Could not find end of body tag");
667
- const title = content.match(/<title>([^<]+)<\/\s*title>/);
668
- document.title = title && title[1] || "Title Not Set";
669
- const mockhtml = document.createElement("html");
670
- mockhtml.innerHTML = content.substring(bodyindex, endbodyindex) + "</body>";
671
- var loaderScript: any;
672
- var childs = mockhtml.children;
673
- const toAdd: Element[] = [];
674
- for (var i = 0; i < childs.length; i++) {
675
- const child = childs[i];
676
- switch (child.nodeName.toUpperCase()) {
677
- case "HEAD":
678
- break;
679
- case "BODY":
680
- i = -1;
681
- childs = child.children;
682
- break;
683
- case "SCRIPT":
684
- const match = child.innerHTML.match(/^NexusFrameworkLoader\.load\((.+)\);?$/);
685
- if (match)
686
- loaderScript = JSON.parse(match[1]);
687
- break;
688
- default:
689
- if (loaderContainerRegex.test(child.className))
690
- break;
691
- toAdd.push(child);
692
- }
693
- }
694
- if (!toAdd.length)
695
- throw new Error("Nothing found in response to add to page");
696
- if (!loaderScript)
697
- throw new Error("NexusFrameworkLoader script not found...");
698
- childs = document.body.children;
699
- for (var i = childs.length - 1; i >= 0; i--) {
700
- const child = childs[i];
701
- switch (child.nodeName.toUpperCase()) {
702
- case "LINK":
703
- case "SCRIPT":
704
- break;
705
- default:
706
- if (loaderContainerRegex.test(child.className))
707
- break;
708
- document.body.removeChild(child);
709
- }
710
- }
711
- const fragment = document.createDocumentFragment();
712
- toAdd.forEach((el) => {
713
- fragment.appendChild(el);
714
- this.createComponents(el);
715
- });
716
- document.body.appendChild(fragment);
717
- loader.load(loaderScript, function() {
718
- self.progressVisible = true;
719
- loader.resetProgress();
720
- pageReady();
721
- });
722
- return true;
723
- });
724
- var forwardPopState: any[];
725
- const beforeHash = /^([^#]+)(#.*)?$/;
726
- var chash = location.href.match(beforeHash)[1];
727
- const startsWith = new RegExp("^" + this.url.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + "(.*)$", "i");
728
- class AnchorElementComponent implements NexusFrameworkComponent {
729
- private readonly handler = (e: Event) => {
730
- if (this.element.hasAttribute("data-nopagesys") || this.element.hasAttribute("data-nodynamic"))
731
- return;
732
- const href = this.element.getAttribute("href");
733
- if (!href || !href.length)
734
- return;
735
- var url = this.element.href;
736
- const bhash = url.match(beforeHash);
737
- if (bhash[2] && chash === bhash[1])
738
- return;
739
- if (startsWith.test(url))
740
- try {
741
- const match = url.match(/^(.+)#.*$/);
742
- if (match)
743
- url = match[1];
744
- self.requestPage(url.substring(self.url.length));
745
- try {
746
- e.stopPropagation();
747
- } catch (e) {}
748
- try {
749
- e.preventDefault();
750
- } catch (e) {}
751
- } catch (e) {
752
- console.warn(e);
753
- }
754
- else
755
- debug("Navigating", url);
756
- };
757
- private element: HTMLAnchorElement;
758
- create(element: HTMLAnchorElement): void {
759
- element.addEventListener("click", this.handler);
760
- this.element = element;
761
- }
762
- destroy(): void {
763
- this.element.removeEventListener("click", this.handler);
764
- }
765
- restore(data: any): void {}
766
- save() {}
767
- }
768
- class FormElementComponent implements NexusFrameworkComponent {
769
- private readonly handler = (e: Event) => {
770
- if (this.element.hasAttribute("data-nopagesys") || this.element.hasAttribute("data-nodynamic"))
771
- return;
772
- const method = this.element.getAttribute("method") || "get";
773
- const enctype = this.element.getAttribute("enctype") || "text/urlencoded";
774
- const action = this.element.getAttribute("action") || "";
775
- var url = resolveUrl(action);
776
- if (startsWith.test(url)) {
777
- try {
778
- const match = url.match(/^(.+)#.*$/);
779
- if (match)
780
- url = match[1];
781
- const formData = new FormData(this.element);
782
- loader.showProgress(undefined, "Submitting Form", "Your form data is being processed");
783
- if (method.trim().toLowerCase() === "post")
784
- self.requestPage(url.substring(self.url.length), {type:enctype,data:formData});
785
- else {
786
- var first = true;
787
- var reqUrl = url.substring(self.url.length) + "?";
788
- for (var entry of formData as any) {
789
- if (first)
790
- first = false;
791
- else
792
- reqUrl += "&";
793
- reqUrl += encodeURIComponent(entry[0]);
794
- reqUrl += "=";
795
- reqUrl += encodeURIComponent(entry[1]);
796
- }
797
- self.requestPage(reqUrl);
798
- }
799
- try {
800
- e.stopPropagation();
801
- } catch (e) {}
802
- try {
803
- e.preventDefault();
804
- } catch (e) {}
805
- Array.prototype.forEach.call(this.element.querySelectorAll("a, input, select, textarea, iframe, button, *[name]"), function(el) {
806
- el.setAttribute("disabled", "disabled");
807
- el.className += " disabled";
808
- });
809
- Array.prototype.forEach.call(this.element.querySelectorAll("button:not([type]), button[type=submit]"), function(button) {
810
- button.innerHTML = "Processing Request";
811
- });
812
- Array.prototype.forEach.call(this.element.querySelectorAll("input[type=submit]"), function(input) {
813
- input.value = "Processing Request";
814
- });
815
- return;
816
- } catch (e) {
817
- debug(e);
818
- }
819
- }
820
- debug("Submitting", url);
821
- };
822
- private element: HTMLFormElement;
823
- create(element: HTMLFormElement): void {
824
- element.addEventListener("submit", this.handler);
825
- this.element = element;
826
- }
827
- destroy(): void {
828
- this.element.removeEventListener("submit", this.handler);
829
- }
830
- restore(data: any): void {}
831
- save() {}
832
- }
833
- const requestPage = this.requestPage = (path: string, post?: any, replace = false) => {
834
- const match = path.match(/\.([^\/]+)([\?#].*)?$/);
835
- if (match && match[0] !== "htm" && match[0] !== "html") {
836
- this.defaultRequestPage(path, post);
837
- debug("Ignoring navigation", path, match);
838
- } else {
839
- debug("requestPage", path);
840
- const rid = ++self.activerid;
841
- const baseurl = this.resolveUrl(path);
842
- const fullurl = baseurl + (match && match[2] || "");
843
- if (replace)
844
- history.replaceState(!!post, "Loading...", fullurl);
845
- else {
846
- history.pushState(!!post, "Loading...", fullurl);
847
- chash = fullurl.match(beforeHash)[1];
848
- window.scrollTo(0, 0);
849
- }
850
- loader.resetError();
851
- loader.resetProgress();
852
- this.pagesysimpl.requestPage(path, (res) => {
853
- try {
854
- if (rid !== self.activerid)
855
- return;
856
-
857
- if (!res.code) {
858
- loader.showProgress(undefined, "Connection Issue", "Check your network and try again");
859
- document.title = "Connection Issue";
860
- history.replaceState(res.hadData, "Connection Issue", fullurl);
861
- setTimeout(function() {
862
- if (rid !== self.activerid)
863
- return;
864
- requestPage(path, post, true);
865
- }, 900000);
866
- return;
867
- } else if (res.code === 502 || res.code === 503) {
868
- loader.showProgress(undefined, "Scheduled Maintenance", "Sorry but this website is undergoing scheduled maintenance");
869
- document.title = "Scheduled Maintenance";
870
- history.replaceState(res.hadData, "Scheduled Maintenance", fullurl);
871
- setTimeout(function() {
872
- if (rid !== self.activerid)
873
- return;
874
- requestPage(path, post, true);
875
- }, 900000);
876
- return;
877
- }
878
-
879
- const location = res.headers['x-location'] || res.headers['location'];
880
- if (location) {
881
- const url = resolveUrl(location[0]);
882
- if (startsWith.test(url)) {
883
- (this.requestPage as any)(url.substring(this.url.length), undefined, true);
884
- return;
885
- }
886
-
887
- console.warn("Requested redirect to external url:", url);
888
- window.location.href = url;
889
- return;
890
- }
891
-
892
- const contentDisposition = res.headers['content-disposition'];
893
- if (contentDisposition && contentDisposition[0].toLowerCase() == "attachment")
894
- throw new Error("Attachment disposition");
895
- this.pagesyshandler(res, function() {
896
- if (rid !== self.activerid)
897
- return;
898
- try {
899
- const title = document.title;
900
-
901
- const length = res.contentLength;
902
- const tooBig = pageStateCacheSize <= length;
903
- const stateData = tooBig ? undefined : {
904
- title: title,
905
- user: self.currentUserID,
906
- scroll: [window.scrollX, window.scrollY],
907
- body: self.saveComponents(document.body),
908
- basehref: base ? base['href'] : undefined,
909
- response: res
910
- };
911
- var i: number;
912
- const cPageStates = pageStates.length;
913
- try {
914
- for(i=0; i<cPageStates; i++) {
915
- const state = pageStates[i];
916
- if (state.url === baseurl) {
917
- if (tooBig) {
918
- pageStates.splice(i, 1);
919
- pageStatesSize -= state.size;
920
- } else {
921
- state.data = stateData;
922
- state.updated = +new Date;
923
- pageStatesSize += length - state.size;
924
- }
925
- throw true;
926
- }
927
- }
928
- if (!tooBig) {
929
- pageStates.push({
930
- url: baseurl,
931
- data: stateData,
932
- updated: +new Date,
933
- size: length
934
- });
935
- pageStatesSize += length;
936
- throw true;
937
- } else
938
- debug("Response too big to store", baseurl, length);
939
- } catch(e) {
940
- if (e === true) {
941
- const over = pageStatesSize - pageStateCacheSize;
942
- if (over > 0) {
943
- pageStates.sort(function(a, b) {
944
- return a.updated - b.updated;
945
- });
946
- var found = 0;
947
- for(i=0; i<cPageStates; i++) {
948
- found += pageStates[i].size;
949
- if (found >= over)
950
- break;
951
- }
952
- i ++;
953
- pageStates.splice(0, i);
954
- debug("Trimmed", i, "items...");
955
- }
956
- } else
957
- throw e;
958
- }
959
-
960
- history.replaceState(res.hadData, document.title, fullurl);
961
- self.emit("page", baseurl, path);
962
- } catch(e) {
963
- debug(e);
964
- if (replace)
965
- window.location.reload(true);
966
- else
967
- try {
968
- chash = undefined;
969
- forwardPopState = [path, post];
970
- debug("Going backwards");
971
- history.go(-1);
972
- } catch (e) {}
973
- }
974
- });
975
- } catch (e) {
976
- debug(e);
977
- if (replace)
978
- window.location.reload(true);
979
- else
980
- try {
981
- chash = undefined;
982
- forwardPopState = [path, post];
983
- debug("Going backwards");
984
- history.go(-1);
985
- } catch (e) {}
986
- }
987
- }, post, rid);
988
- }
989
- };
990
- this.registerComponent("a", AnchorElementComponent);
991
- this.registerComponent("form", FormElementComponent);
992
- window.addEventListener('popstate', (e) => {
993
- try {
994
- var state: PageState;
995
- const bhash = location.href.match(beforeHash);
996
- const cStates = pageStates.length;
997
- const baseurl = bhash[1];
998
- for(var i=0; i<cStates; i++) {
999
- const _state = pageStates[i];
1000
- if (_state.url === baseurl) {
1001
- state = _state;
1002
- break;
1003
- }
1004
- }
1005
- const hasState = !!state;
1006
- const rid = ++ self.activerid;
1007
-
1008
- const error = hasState && state.data.error;
1009
- if (error) {
1010
- showError(error);
1011
- return;
1012
- }
1013
-
1014
- if (forwardPopState) {
1015
- debug("forwardPopState", forwardPopState);
1016
- const forward = forwardPopState;
1017
- setTimeout(() => {
1018
- if (rid === self.activerid)
1019
- self.defaultRequestPage(forward[0], forward[1]);
1020
- });
1021
- forwardPopState = undefined;
1022
- return;
1023
- }
1024
-
1025
- if (chash === baseurl) {
1026
- debug("Only hash has changed...", baseurl);
1027
- return;
1028
- }
1029
- chash = bhash[1];
1030
-
1031
- if (e.state)
1032
- alert("You submitted data to this page, which cannot be re-sent. Because of this, what you're viewing may not be the same now.");
1033
-
1034
- if (!hasState)
1035
- throw new Error("No state for: " + baseurl + ", reloading...");
1036
- if (state.data.user != self.currentUserID)
1037
- throw new Error("User has changed since state was created, reloading...");
1038
- document.title = state.data.title;
1039
- loader.resetProgress();
1040
- loader.resetError();
1041
- self.pagesyshandler(state.data.response, function(err?: Error) {
1042
- if (rid !== self.activerid)
1043
- return;
1044
- try {
1045
- if (err)
1046
- throw err;
1047
- if (state.data.body)
1048
- self.restoreComponents(document.body, state.data.body);
1049
- if (state.data.scroll)
1050
- window.scrollTo.apply(window, state.data.scroll);
1051
- debug("Used stored page state!", state);
1052
- } catch(err) {
1053
- debug(err);
1054
- var url = location.href;
1055
- if (startsWith.test(url)) {
1056
- try {
1057
- if (/reloading\.\.\.$/.test(err.message)) {
1058
- (self.requestPage as any)(url.substring(self.url.length), undefined, true);
1059
- return;
1060
- }
1061
- console.error("Unknown error occured, actually navigating to page...");
1062
- } catch (e) {
1063
- console.error(e);
1064
- }
1065
- }
1066
- location.reload(true);
1067
- }
1068
- });
1069
- } catch (err) {
1070
- debug(err);
1071
- var url = location.href;
1072
- if (startsWith.test(url)) {
1073
- try {
1074
- if (/reloading\.\.\.$/.test(err.message)) {
1075
- (self.requestPage as any)(url.substring(self.url.length), undefined, true);
1076
- return;
1077
- }
1078
- console.error("Unknown error occured, actually navigating to page...");
1079
- } catch (e) {
1080
- console.error(e);
1081
- }
1082
- }
1083
- location.reload(true);
1084
- }
1085
- });
1086
- return true;
1087
- }
1088
- get currentUser() {
1089
- return this.currentUserID;
1090
- }
1091
- defaultRequestPage(path: string, post?: any) {
1092
- if (post)
1093
- throw new Error("Posting is not supported without an initialized page system, yet");
1094
- else
1095
- location.href = this.resolveUrl(path);
1096
- }
1097
- requestPage = this.defaultRequestPage;
1098
- private _listeners: {[index: string]: Function[]} = {};
1099
- on(event: string, cb: (...args: any[]) => void) {
1100
- var listeners = this._listeners[event];
1101
- if (listeners)
1102
- listeners.push(cb);
1103
- else
1104
- this._listeners[event] = listeners = [cb];
1105
-
1106
- }
1107
- off(event: string, cb: (...args: any[]) => void) {
1108
- var index: number;
1109
- var listeners = this._listeners[event];
1110
- if (listeners && (index = listeners.indexOf(cb)) > -1)
1111
- listeners.splice(index, 1);
1112
- }
1113
- emit(event: string, ...args: any[]) {
1114
- const self = this;
1115
- const listeners = this._listeners[event];
1116
- if (listeners)
1117
- listeners.forEach(function (cb) {
1118
- cb.apply(self, args);
1119
- })
1120
- }
1121
- }
1122
- var impl;
1123
- if (window.io)
1124
- impl = class NexusFrameworkWithIO extends NexusFrameworkBase {
1125
- public constructor(url?: string) {
1126
- super(url, window.io({ // TODO: Parse the root URL and give a path and hostname properly
1127
- path: "/:io"
1128
- }));
1129
- const io = this.io;
1130
- io.on("connect", function () {
1131
- io.emit("init", loader.requestedResources());
1132
- });
1133
- }
1134
- }
1135
- else
1136
- impl = class NexusFrameworkNoIO extends NexusFrameworkBase {
1137
- public constructor(url?: string) {
1138
- super(url);
1139
- }
1140
- }
1141
- Object.defineProperty(window, "NexusFrameworkImpl", {
1142
- value: impl
1143
- });
1144
- return impl;
1145
- }
1146
- },
1147
- NexusFrameworkClient: {
1148
- configurable: true,
1149
- set: function (instance) {
1150
- Object.defineProperty(window, "NexusFrameworkClient", {
1151
- value: instance
1152
- });
1153
- },
1154
- get: function () {
1155
- const instance = new window.NexusFrameworkImpl();
1156
- Object.defineProperty(window, "NexusFrameworkClient", {
1157
- value: instance
1158
- });
1159
- return instance;
1160
- }
1161
- }
1162
- });
1163
- })(window);
1
+ /// <reference path="../index.d.ts" />
2
+ (function(window) {
3
+ const BSON = new window['bson'];
4
+ Object.defineProperties(window, {
5
+ NexusFrameworkTransport: {
6
+ configurable: true,
7
+ set: function (instance) {
8
+ Object.defineProperty(window, "NexusFrameworkTransport", {
9
+ value: instance
10
+ });
11
+ },
12
+ get: function () {
13
+ var impl: NexusFrameworkTransport;
14
+ if ("XMLHttpRequest" in window) {
15
+ class NexusFrameworkXMLHttpRequestResponse implements NexusFrameworkTransportResponse {
16
+ hadData: boolean;
17
+ abResponse: boolean;
18
+ private _url: string;
19
+ private parsedJson: any;
20
+ private parsedBson: any;
21
+ private request: XMLHttpRequest;
22
+ private processedHeaders: {[index: string]: string[]};
23
+ constructor(request: XMLHttpRequest, url: string, hadData?: boolean, abResponse?: boolean) {
24
+ this._url = url;
25
+ this.request = request;
26
+ this.hadData = hadData;
27
+ this.abResponse = abResponse;
28
+ }
29
+ get url() {
30
+ return this.request.responseURL || this._url;
31
+ }
32
+ get code() {
33
+ return this.request.status;
34
+ }
35
+ get contentLength() {
36
+ return parseInt(this.request.getResponseHeader("content-length")) || this.request.responseText.length;
37
+ }
38
+ get contentFromJSON() {
39
+ if (!this.parsedJson)
40
+ this.parsedJson = JSON.parse(this.request.responseText);
41
+ return this.parsedJson;
42
+ }
43
+ get contentFromBSON() {
44
+ if (!this.parsedBson) {
45
+ var response = this.request.response;
46
+ if (typeof response === "string")
47
+ response = Buffer.from(response, "utf8");
48
+ else
49
+ response = new Buffer(response);
50
+ this.parsedBson = BSON.deserialize(response, {promoteValues:true});
51
+ }
52
+ return this.parsedBson;
53
+ }
54
+ get contentAsString() {
55
+ return this.request.responseText;
56
+ }
57
+ get contentAsArrayBuffer() {
58
+ return this.request.response;
59
+ }
60
+ get headers(): {[index: string]: string[]} {
61
+ if (!this.processedHeaders) {
62
+ const headers: {[index: string]: string[]} = this.processedHeaders = {};
63
+ this.request.getAllResponseHeaders().split(/\r?\n/g).forEach(function (header) {
64
+ const index = header.indexOf(":");
65
+ var key: string, val: string;
66
+ if (index > 0) {
67
+ key = header.substring(0, index).trim().toLowerCase();
68
+ val = header.substring(index + 1).trim();
69
+ } else
70
+ key = header.trim().toLowerCase();
71
+ var list = headers[key];
72
+ if (!list)
73
+ list = headers[key] = [];
74
+ if (val)
75
+ list.push(val);
76
+ });
77
+ }
78
+ return this.processedHeaders;
79
+ }
80
+ }
81
+ const execute = function (method: string, url: string, data: any, cb: (res: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
82
+ const request = new XMLHttpRequest();
83
+ var responseIsArrayBuffer;
84
+ try {
85
+ if (!wantsBinary)
86
+ throw false;
87
+ request.responseType = "arraybuffer";
88
+ if (request.responseType !== "arraybuffer") {
89
+ console.error("Could not request arraybuffer");
90
+ throw false;
91
+ }
92
+ responseIsArrayBuffer = true;
93
+ } catch(e) {
94
+ responseIsArrayBuffer = false;
95
+ }
96
+ request.open(method, url, true);
97
+ if (extraHeaders)
98
+ Object.keys(extraHeaders).forEach(function (key) {
99
+ request.setRequestHeader(key, extraHeaders[key]);
100
+ });
101
+ if (progcb)
102
+ request.onprogress = function (ev) {
103
+ if (ev.lengthComputable && ev.total)
104
+ progcb(ev.loaded, ev.total);
105
+ }
106
+ request.onreadystatechange = function (e) {
107
+ if (request.readyState === XMLHttpRequest.DONE)
108
+ cb(new NexusFrameworkXMLHttpRequestResponse(request, url, !!data, responseIsArrayBuffer));
109
+ }
110
+ const type = data && data.type;
111
+ if (type)
112
+ switch(type) {
113
+ case "":
114
+ case "text/urlencoded":
115
+ var dat = "";
116
+ for (var entry of data.data as any) {
117
+ if (dat)
118
+ dat += "&";
119
+ dat += encodeURIComponent(entry[0]);
120
+ dat += "=";
121
+ dat += encodeURIComponent(entry[1]);
122
+ }
123
+ request.setRequestHeader("Content-Type", "text/urlencoded");
124
+ request.send(dat);
125
+ break;
126
+ case "text/json":
127
+ request.setRequestHeader("Content-Type", "text/json");
128
+ request.send(JSON.stringify(data.data));
129
+ break;
130
+ case "application/bson":
131
+ request.setRequestHeader("Content-Type", "application/bson");
132
+ request.send(BSON.serialize(data.data));
133
+ break;
134
+ case "multipart/form-data":
135
+ request.send(data.data);
136
+ break;
137
+ default:
138
+ request.send(data);
139
+ }
140
+ else
141
+ request.send(data);
142
+ }
143
+ impl = {
144
+ get: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
145
+ execute("GET", url, undefined, cb, extraHeaders, progcb, wantsBinary);
146
+ },
147
+ head: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
148
+ execute("HEAD", url, undefined, cb, extraHeaders, progcb, wantsBinary);
149
+ },
150
+ post: function (url: string, data: any, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
151
+ execute("POST", url, data, cb, extraHeaders, progcb, wantsBinary);
152
+ },
153
+ put: function (url: string, data: any, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
154
+ execute("PUT", url, data, cb, extraHeaders, progcb, wantsBinary);
155
+ },
156
+ execute,
157
+ del: function (url: string, cb: (res?: NexusFrameworkTransportResponse) => void, extraHeaders?: {[index: string]: string}, progcb?: (complete: number, total: number) => void, wantsBinary?: boolean) {
158
+ execute("DELETE", url, undefined, cb, extraHeaders, progcb, wantsBinary);
159
+ }
160
+ };
161
+ } else
162
+ throw new Error("No transport implementations");
163
+ Object.defineProperty(window, "NexusFrameworkTransport", {
164
+ value: impl
165
+ });
166
+ return impl;
167
+ }
168
+ },
169
+ NexusFrameworkImpl: {
170
+ configurable: true,
171
+ set: function (instance) {
172
+ Object.defineProperty(window, "NexusFrameworkImpl", {
173
+ value: instance
174
+ });
175
+ },
176
+ get: function () {
177
+ const loader = window.NexusFrameworkLoader;
178
+ const showError = loader.showError;
179
+ const debug = /*function(...args: any[]){};*/console.log.bind(console);
180
+ const GA_ANALYTICS: NexusFrameworkAnalyticsAdapter = {
181
+ reportError: function (err: Error, fatal?: boolean) {
182
+ if (window.ga)
183
+ try {
184
+ window.ga('send', 'exception', {
185
+ 'exDescription': (err.stack || "" + err).replace(/\n/g, "\n\t"),
186
+ 'exFatal': fatal
187
+ });
188
+ } catch (e) {
189
+ console.warn(e);
190
+ }
191
+ },
192
+ reportEvent: function (category: string, action: string, label?: string, value?: number) {
193
+ if (window.ga)
194
+ try {
195
+ window.ga('send', 'event', category, action, label, value);
196
+ } catch (e) {
197
+ console.warn(e);
198
+ }
199
+ },
200
+ reportPage: function (path?: string) {
201
+ if (window.ga)
202
+ try {
203
+ if (!path)
204
+ path = location.pathname;
205
+ window.ga('set', 'page', path);
206
+ window.ga('send', 'pageview');
207
+ } catch (e) {
208
+ console.warn(e);
209
+ }
210
+ }
211
+ };
212
+ const r = document.createElement("a");
213
+ const protocol = location.href.match(/^\w+:/)[0];
214
+ const resolveUrl = function (url: string) {
215
+ r.setAttribute("href", url);
216
+ var href = r.href;
217
+ if (/^\/\//.test(href))
218
+ href = protocol + href;
219
+ return href;
220
+ }
221
+ interface PageSystemImpl {
222
+ requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void;
223
+ }
224
+ const convertResponse = function (res: NexusFrameworkPageSystemResponse, url = location.href): NexusFrameworkTransportResponse{
225
+ var storage: any;
226
+ return (typeof res.data === "string") ? {
227
+ url,
228
+ code: res.code,
229
+ contentAsString: res.data,
230
+ get contentLength() {
231
+ return parseInt(res.headers['content-length'] && res.headers['content-length'][0]) || res.data.length
232
+ },
233
+ get contentAsArrayBuffer(): ArrayBuffer{
234
+ throw new Error("Not implemented");
235
+ },
236
+ get contentFromBSON() {
237
+ if (!storage)
238
+ storage = JSON.parse(res.data);
239
+ return storage;
240
+ },
241
+ get contentFromJSON() {
242
+ if (!storage)
243
+ storage = JSON.parse(res.data);
244
+ return storage;
245
+ },
246
+ headers: res.headers
247
+ } : {
248
+ url,
249
+ code: res.code,
250
+ get contentAsString() {
251
+ if (!storage)
252
+ storage = JSON.stringify(res.data);
253
+ return storage;
254
+ },
255
+ get contentAsArrayBuffer(): ArrayBuffer{
256
+ throw new Error("Not implemented");
257
+ },
258
+ contentFromJSON: res.data,
259
+ contentFromBSON: res.data,
260
+ headers: res.headers,
261
+ get contentLength() {
262
+ var length = parseInt(res.headers['content-length'] && res.headers['content-length'][0]);
263
+ if (length)
264
+ return length;
265
+ if (!storage)
266
+ storage = JSON.stringify(res.data);
267
+ return storage.length;
268
+ }
269
+ };
270
+ }
271
+ const loaderContainerRegex = /(^|\s)loader\-(progress|error)\-container(\s|$)/;
272
+ class NexusFrameworkBase implements NexusFrameworkClient {
273
+ public readonly url: string;
274
+ public readonly io: SocketIOClient.Socket;
275
+ private activerid = 0;
276
+ private _analytics: NexusFrameworkAnalyticsAdapter;
277
+ private errorreporter: (err: Error, fatal?: boolean) => void;
278
+ private components: {[index: string]: NexusFrameworkComponentFactory} = {};
279
+ private pagesyshandler: (res: NexusFrameworkTransportResponse, pageReady: (err?: Error) => void) => void;
280
+ private pagesysprerequest: (path: string) => boolean;
281
+ private pagesysimpl: PageSystemImpl;
282
+ private currentUserID: string = undefined;
283
+ private progressVisible = false;
284
+ private animationTiming = 500;
285
+ public constructor(url = "/", io?: SocketIOClient.Socket) {
286
+ url = resolveUrl(url);
287
+ if (!/\/$/.test(url))
288
+ url += "/";
289
+ Object.defineProperties(this, {
290
+ io: {
291
+ value: io
292
+ },
293
+ url: {
294
+ value: url
295
+ }
296
+ });
297
+ this._analytics = GA_ANALYTICS;
298
+ }
299
+
300
+ resolveUrl(url: string) {
301
+ if (/^\w+\:/.test(url))
302
+ return url;
303
+ return resolveUrl(this.url + url);
304
+ }
305
+
306
+ disableAll(root: HTMLElement = document.body) {
307
+ const focusable = root.querySelectorAll("a, input, select, textarea, iframe, button, *[focusable], *[tabindex]");
308
+ for (var i = 0; i < focusable.length; i++) {
309
+ const child = focusable[i];
310
+ const tabindex = child.getAttribute("tabindex");
311
+ if (tabindex == "-1")
312
+ return;
313
+ child.setAttribute("data-tabindex", tabindex ? tabindex : "");
314
+ child.setAttribute("data-tabindex", "-1");
315
+ }
316
+ }
317
+ enableAll(root: HTMLElement = document.body) {
318
+ const focusable = root.querySelectorAll("*[data-tabindex]");
319
+ for (var i = 0; i < focusable.length; i++) {
320
+ const child = focusable[i];
321
+ child.setAttribute("tabindex", child.getAttribute("data-tabindex"));
322
+ child.removeAttribute("data-tabindex");
323
+ }
324
+ }
325
+
326
+ get analytics() {
327
+ return this._analytics;
328
+ }
329
+ set analytics(value) {
330
+ this._analytics = value ? value : GA_ANALYTICS;
331
+ }
332
+
333
+ reportError(err: Error, fatal?: boolean) {
334
+ console[fatal ? "error" : "warn"](err.stack);
335
+ if (this.errorreporter)
336
+ this.errorreporter(err, fatal);
337
+ }
338
+ installErrorReporter(errorreporter: (err: Error, fatal?: boolean) => void) {
339
+ this.errorreporter = errorreporter;
340
+ }
341
+
342
+ registerComponent(selector: string, impl: NexusFrameworkComponentFactory) {
343
+ if (impl) {
344
+ this.components[selector] = impl;
345
+ this.createComponents(document.head);
346
+ this.createComponents(document.body);
347
+ } else {
348
+ delete this.components[selector];
349
+ var destroy = (root: HTMLElement) => {
350
+ const elements = root.querySelectorAll(selector);
351
+ if (!elements.length)
352
+ return;
353
+
354
+ for (var i = 0; i < elements.length; i++) {
355
+ const element = elements[i];
356
+ var components = element['__nf_cmapping__'];
357
+ if (!components)
358
+ components = element['__nf_cmapping__'] = {};
359
+ const component: NexusFrameworkComponent = components[selector];
360
+ if (component) {
361
+ try {
362
+ component.destroy();
363
+ } catch (e) {
364
+ this.reportError(e);
365
+ }
366
+ }
367
+ }
368
+ }
369
+ destroy(document.head);
370
+ destroy(document.body);
371
+ }
372
+ }
373
+ unregisterComponent(selector: string, impl: NexusFrameworkComponentFactory) {
374
+ }
375
+ createComponents(root: Element): void {
376
+ Object.keys(this.components).forEach((selector) => {
377
+ const elements = root.querySelectorAll(selector);
378
+ if (!elements.length)
379
+ return;
380
+
381
+ for (var i = 0; i < elements.length; i++) {
382
+ const element = elements[i];
383
+ var components = element['__nf_cmapping__'];
384
+ if (!components)
385
+ components = element['__nf_cmapping__'] = {};
386
+ if (components[selector])
387
+ continue;
388
+ const componentFactory = this.components[selector];
389
+ try {
390
+ (components[selector] = new componentFactory()).create(element as HTMLElement);
391
+ } catch (e) {
392
+ this.reportError(e);
393
+ }
394
+ }
395
+ });
396
+ }
397
+ destroyComponents(root: HTMLElement): void {
398
+ Object.keys(this.components).forEach((selector) => {
399
+ const elements = root.querySelectorAll(selector);
400
+ if (!elements.length)
401
+ return;
402
+
403
+ for (var i = 0; i < elements.length; i++) {
404
+ const element = elements[i];
405
+ var components = element['__nf_cmapping__'];
406
+ if (!components)
407
+ components = element['__nf_cmapping__'] = {};
408
+ const component: NexusFrameworkComponent = components[selector];
409
+ if (component) {
410
+ try {
411
+ component.destroy();
412
+ } catch (e) {
413
+ this.reportError(e);
414
+ }
415
+ }
416
+ }
417
+ });
418
+ }
419
+ restoreComponents(root: HTMLElement, state: Object): void {
420
+ Object.keys(this.components).forEach((selector) => {
421
+ const states: any[] = state[selector];
422
+ if (!states)
423
+ return;
424
+
425
+ const elements = root.querySelectorAll(selector);
426
+ if (!elements.length)
427
+ return;
428
+
429
+ for (var i = 0; i < elements.length; i++) {
430
+ const element = elements[i];
431
+ if (states.length) {
432
+ const _state = states.shift();
433
+ if (_state) {
434
+ var components = element['__nf_cmapping__'];
435
+ if (!components)
436
+ components = element['__nf_cmapping__'] = {};
437
+ var component: NexusFrameworkComponent = components[selector];
438
+ if (!component) {
439
+ const componentFactory = this.components[selector];
440
+ try {
441
+ (component = components[selector] = new componentFactory()).create(element as HTMLElement);
442
+ } catch (e) {
443
+ this.reportError(e);
444
+ console.warn(e);
445
+ continue;
446
+ }
447
+ }
448
+ try {
449
+ component.restore(_state);
450
+ } catch (e) {
451
+ this.reportError(e);
452
+ console.warn(e);
453
+ }
454
+ }
455
+ } else
456
+ break;
457
+ }
458
+ });
459
+ }
460
+ saveComponents(root: HTMLElement): Object {
461
+ var state = {};
462
+ Object.keys(this.components).forEach((selector) => {
463
+ const states = state[selector] = [];
464
+ const elements = root.querySelectorAll(selector);
465
+ if (!elements.length)
466
+ return;
467
+
468
+ for (var i = 0; i < elements.length; i++) {
469
+ const element = elements[i];
470
+ var components = element['__nf_cmapping__'];
471
+ if (!components)
472
+ components = element['__nf_cmapping__'] = {};
473
+ var component: NexusFrameworkComponent = components[selector];
474
+ if (!component) {
475
+ const componentFactory = this.components[selector];
476
+ try {
477
+ (component = components[selector] = new componentFactory).create(element as HTMLElement);
478
+ } catch (e) {
479
+ states.push(undefined);
480
+ this.reportError(e);
481
+ console.warn(e);
482
+ continue;
483
+ }
484
+ }
485
+ try {
486
+ states.push(component.save());
487
+ } catch (e) {
488
+ states.push(undefined);
489
+ this.reportError(e);
490
+ console.warn(e);
491
+ }
492
+ }
493
+ });
494
+ return state;
495
+ }
496
+
497
+ initPageSystem(opts?: NexusFrameworkPageSystemOptions) {
498
+ if (!loader)
499
+ return console.error("The NexusFramework Loader is required for the dynamic Page System to work correctly.");
500
+ if (!history.pushState || !history.replaceState)
501
+ return console.warn("This browser is missing an essential feature required for the dynamic Page System.")
502
+ if (this.pagesyshandler)
503
+ return console.warn("Page System already initialized, ignoring additional requests to initialize.")
504
+
505
+ interface PageState {
506
+ data: {
507
+ title?: string;
508
+ scroll?: number[];
509
+ user?: string | number;
510
+ response?: NexusFrameworkTransportResponse;
511
+ error?: Error;
512
+ body?: any;
513
+ };
514
+ url: string;
515
+ updated: number;
516
+ size: number;
517
+ }
518
+
519
+ opts = opts || {};
520
+ const self = this;
521
+ var pageStatesSize = 0;
522
+ const pageStates: PageState[] = [];
523
+ this.animationTiming = opts.animationTiming || 500;
524
+ const pageStateCacheSize = opts.pageHistoryCacheSize || 25000000;
525
+ const wrapCB = (cb: (res: NexusFrameworkTransportResponse) => void) => {
526
+ return (res: NexusFrameworkTransportResponse) => {
527
+ const user = res.headers['x-logged-user'];
528
+ if (user)
529
+ self.currentUserID = user[0];
530
+ else
531
+ self.currentUserID = undefined;
532
+ debug("Current UID", self.currentUserID);
533
+ cb(res);
534
+ if (res.code === 200)
535
+ setTimeout(() => {
536
+ self._analytics.reportPage();
537
+ });
538
+ }
539
+ }
540
+ loader.showError = function(error: Error) {
541
+ const match = location.href.match(beforeHash);
542
+ const baseurl = match && match[1] || location.href;
543
+ const length = error.stack.length;
544
+ const tooBig = pageStateCacheSize <= length;
545
+ try {
546
+ const cPageStates = pageStates.length;
547
+ for(var i=0; i<cPageStates; i++) {
548
+ const state = pageStates[i];
549
+ if (state.url === baseurl) {
550
+ if (tooBig) {
551
+ pageStates.splice(i, 1);
552
+ pageStatesSize -= state.size;
553
+ } else {
554
+ state.data = {error};
555
+ state.updated = +new Date;
556
+ pageStatesSize += length - state.size;
557
+ }
558
+ throw true;
559
+ }
560
+ }
561
+ if (tooBig)
562
+ throw true;
563
+ pageStates.push({
564
+ url: baseurl,
565
+ data: {error},
566
+ updated: +new Date,
567
+ size: length
568
+ });
569
+ pageStatesSize += length;
570
+ } catch(e) {
571
+ if (e !== true)
572
+ throw e;
573
+ }
574
+ return showError(error);
575
+ }
576
+ const transportPageSystem = {
577
+ requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void {
578
+ if (self.pagesysprerequest && !self.pagesysprerequest(path)) {
579
+ self.defaultRequestPage(path, post);
580
+ return;
581
+ }
582
+ if (!opts.noprogress) {
583
+ self.disableAll();
584
+ loader.showProgress();
585
+ }
586
+ const url = self.resolveUrl(":pagesys/" + path);
587
+ const _cb = opts.noprogress ? cb : function (res: NexusFrameworkTransportResponse) {
588
+ if (opts.noprogress)
589
+ cb(res);
590
+ else
591
+ loader.showProgress(() => {
592
+ cb(res);
593
+ self.enableAll();
594
+ });
595
+ };
596
+ if (post)
597
+ window.NexusFrameworkTransport.post(url, post, wrapCB(_cb), undefined, undefined, true);
598
+ else
599
+ window.NexusFrameworkTransport.get(url, wrapCB(_cb), undefined, undefined, true);
600
+ }
601
+ };
602
+
603
+ if (!opts.noio && this.io) {
604
+ const io = this.io;
605
+ this.pagesysimpl = {
606
+ requestPage(path: string, cb: (res: NexusFrameworkTransportResponse) => void, post?: any, rid?: number): void {
607
+ if (io.connected) {
608
+ if (self.pagesysprerequest && !self.pagesysprerequest(path)) {
609
+ self.defaultRequestPage(path, post);
610
+ return;
611
+ }
612
+ if (!opts.noprogress) {
613
+ self.disableAll();
614
+ loader.showProgress();
615
+ }
616
+ const headers: any = {
617
+ "referrer": location.href
618
+ };
619
+ var val = document.cookie;
620
+ if (val)
621
+ headers['cookie'] = val;
622
+ io.emit("page", post ? "POST" : "GET", path, post, headers, function (res: NexusFrameworkPageSystemResponse) {
623
+ if (rid != self.activerid)
624
+ return;
625
+
626
+ const cookies = res.headers['set-cookie'];
627
+ if (cookies) {
628
+ cookies.forEach(function (cookie) {
629
+ document.cookie = cookie;
630
+ });
631
+ }
632
+
633
+ const _cb = opts.noprogress ? cb : function (res) {
634
+ if (!opts.noprogress)
635
+ cb(res);
636
+ else
637
+ loader.showProgress(() => {
638
+ cb(res);
639
+ self.enableAll();
640
+ });
641
+ };
642
+ wrapCB(_cb)(convertResponse(res, self.resolveUrl(path)));
643
+ });
644
+ } else
645
+ transportPageSystem.requestPage(path, cb, post, rid);
646
+ }
647
+ }
648
+ } else
649
+ this.pagesysimpl = transportPageSystem;
650
+ var base: HTMLBaseElement | NodeListOf<HTMLBaseElement> = document.getElementsByTagName("base");
651
+ base = base && base[0];
652
+ this.pagesysprerequest = opts.prerequest;
653
+ this.pagesyshandler = opts.handler || ((res: NexusFrameworkTransportResponse, pageReady: () => void) => {
654
+ try {
655
+ const contentType = res.headers['content-type'][0];
656
+ if (!/\/html(;.+)?$/.test(contentType.toLowerCase())) {
657
+ throw new Error("Content type is not html");
658
+ }
659
+ } catch (e) {}
660
+ const content = res.contentAsString;
661
+ const bodyindex = content.indexOf("<body");
662
+ if (bodyindex == -1)
663
+ throw new Error("Could not find start of body tag");
664
+ const endbodyindex = content.indexOf("</body>") || content.indexOf("</ body>");
665
+ if (endbodyindex == -1)
666
+ throw new Error("Could not find end of body tag");
667
+ const title = content.match(/<title>([^<]+)<\/\s*title>/);
668
+ document.title = title && title[1] || "Title Not Set";
669
+ const mockhtml = document.createElement("html");
670
+ mockhtml.innerHTML = content.substring(bodyindex, endbodyindex) + "</body>";
671
+ var loaderScript: any;
672
+ var childs = mockhtml.children;
673
+ const toAdd: Element[] = [];
674
+ for (var i = 0; i < childs.length; i++) {
675
+ const child = childs[i];
676
+ switch (child.nodeName.toUpperCase()) {
677
+ case "HEAD":
678
+ break;
679
+ case "BODY":
680
+ i = -1;
681
+ childs = child.children;
682
+ break;
683
+ case "SCRIPT":
684
+ const match = child.innerHTML.match(/^NexusFrameworkLoader\.load\((.+)\);?$/);
685
+ if (match)
686
+ loaderScript = JSON.parse(match[1]);
687
+ break;
688
+ default:
689
+ if (loaderContainerRegex.test(child.className))
690
+ break;
691
+ toAdd.push(child);
692
+ }
693
+ }
694
+ if (!toAdd.length)
695
+ throw new Error("Nothing found in response to add to page");
696
+ if (!loaderScript)
697
+ throw new Error("NexusFrameworkLoader script not found...");
698
+ childs = document.body.children;
699
+ for (var i = childs.length - 1; i >= 0; i--) {
700
+ const child = childs[i];
701
+ switch (child.nodeName.toUpperCase()) {
702
+ case "LINK":
703
+ case "SCRIPT":
704
+ break;
705
+ default:
706
+ if (loaderContainerRegex.test(child.className))
707
+ break;
708
+ document.body.removeChild(child);
709
+ }
710
+ }
711
+ const fragment = document.createDocumentFragment();
712
+ toAdd.forEach((el) => {
713
+ fragment.appendChild(el);
714
+ this.createComponents(el);
715
+ });
716
+ document.body.appendChild(fragment);
717
+ loader.load(loaderScript, function() {
718
+ self.progressVisible = true;
719
+ loader.resetProgress();
720
+ pageReady();
721
+ });
722
+ return true;
723
+ });
724
+ var forwardPopState: any[];
725
+ const beforeHash = /^([^#]+)(#.*)?$/;
726
+ var chash = location.href.match(beforeHash)[1];
727
+ const startsWith = new RegExp("^" + this.url.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&") + "(.*)$", "i");
728
+ class AnchorElementComponent implements NexusFrameworkComponent {
729
+ private readonly handler = (e: Event) => {
730
+ if (this.element.hasAttribute("data-nopagesys") || this.element.hasAttribute("data-nodynamic"))
731
+ return;
732
+ const href = this.element.getAttribute("href");
733
+ if (!href || !href.length)
734
+ return;
735
+ var url = this.element.href;
736
+ const bhash = url.match(beforeHash);
737
+ if (bhash[2] && chash === bhash[1])
738
+ return;
739
+ if (startsWith.test(url))
740
+ try {
741
+ const match = url.match(/^(.+)#.*$/);
742
+ if (match)
743
+ url = match[1];
744
+ self.requestPage(url.substring(self.url.length));
745
+ try {
746
+ e.stopPropagation();
747
+ } catch (e) {}
748
+ try {
749
+ e.preventDefault();
750
+ } catch (e) {}
751
+ } catch (e) {
752
+ console.warn(e);
753
+ }
754
+ else
755
+ debug("Navigating", url);
756
+ };
757
+ private element: HTMLAnchorElement;
758
+ create(element: HTMLAnchorElement): void {
759
+ element.addEventListener("click", this.handler);
760
+ this.element = element;
761
+ }
762
+ destroy(): void {
763
+ this.element.removeEventListener("click", this.handler);
764
+ }
765
+ restore(data: any): void {}
766
+ save() {}
767
+ }
768
+ class FormElementComponent implements NexusFrameworkComponent {
769
+ private readonly handler = (e: Event) => {
770
+ if (this.element.hasAttribute("data-nopagesys") || this.element.hasAttribute("data-nodynamic"))
771
+ return;
772
+ const method = this.element.getAttribute("method") || "get";
773
+ const enctype = this.element.getAttribute("enctype") || "text/urlencoded";
774
+ const action = this.element.getAttribute("action") || "";
775
+ var url = resolveUrl(action);
776
+ if (startsWith.test(url)) {
777
+ try {
778
+ const match = url.match(/^(.+)#.*$/);
779
+ if (match)
780
+ url = match[1];
781
+ const formData = new FormData(this.element);
782
+ loader.showProgress(undefined, "Submitting Form", "Your form data is being processed");
783
+ if (method.trim().toLowerCase() === "post")
784
+ self.requestPage(url.substring(self.url.length), {type:enctype,data:formData});
785
+ else {
786
+ var first = true;
787
+ var reqUrl = url.substring(self.url.length) + "?";
788
+ for (var entry of formData as any) {
789
+ if (first)
790
+ first = false;
791
+ else
792
+ reqUrl += "&";
793
+ reqUrl += encodeURIComponent(entry[0]);
794
+ reqUrl += "=";
795
+ reqUrl += encodeURIComponent(entry[1]);
796
+ }
797
+ self.requestPage(reqUrl);
798
+ }
799
+ try {
800
+ e.stopPropagation();
801
+ } catch (e) {}
802
+ try {
803
+ e.preventDefault();
804
+ } catch (e) {}
805
+ Array.prototype.forEach.call(this.element.querySelectorAll("a, input, select, textarea, iframe, button, *[name]"), function(el) {
806
+ el.setAttribute("disabled", "disabled");
807
+ el.className += " disabled";
808
+ });
809
+ Array.prototype.forEach.call(this.element.querySelectorAll("button:not([type]), button[type=submit]"), function(button) {
810
+ button.innerHTML = "Processing Request";
811
+ });
812
+ Array.prototype.forEach.call(this.element.querySelectorAll("input[type=submit]"), function(input) {
813
+ input.value = "Processing Request";
814
+ });
815
+ return;
816
+ } catch (e) {
817
+ debug(e);
818
+ }
819
+ }
820
+ debug("Submitting", url);
821
+ };
822
+ private element: HTMLFormElement;
823
+ create(element: HTMLFormElement): void {
824
+ element.addEventListener("submit", this.handler);
825
+ this.element = element;
826
+ }
827
+ destroy(): void {
828
+ this.element.removeEventListener("submit", this.handler);
829
+ }
830
+ restore(data: any): void {}
831
+ save() {}
832
+ }
833
+ const requestPage = this.requestPage = (path: string, post?: any, replace = false) => {
834
+ const match = path.match(/\.([^\/]+)([\?#].*)?$/);
835
+ if (match && match[0] !== "htm" && match[0] !== "html") {
836
+ this.defaultRequestPage(path, post);
837
+ debug("Ignoring navigation", path, match);
838
+ } else {
839
+ debug("requestPage", path);
840
+ const rid = ++self.activerid;
841
+ const baseurl = this.resolveUrl(path);
842
+ const fullurl = baseurl + (match && match[2] || "");
843
+ if (replace)
844
+ history.replaceState(!!post, "Loading...", fullurl);
845
+ else {
846
+ history.pushState(!!post, "Loading...", fullurl);
847
+ chash = fullurl.match(beforeHash)[1];
848
+ window.scrollTo(0, 0);
849
+ }
850
+ loader.resetError();
851
+ loader.resetProgress();
852
+ this.pagesysimpl.requestPage(path, (res) => {
853
+ try {
854
+ if (rid !== self.activerid)
855
+ return;
856
+
857
+ if (!res.code) {
858
+ loader.showProgress(undefined, "Connection Issue", "Check your network and try again");
859
+ document.title = "Connection Issue";
860
+ history.replaceState(res.hadData, "Connection Issue", fullurl);
861
+ setTimeout(function() {
862
+ if (rid !== self.activerid)
863
+ return;
864
+ requestPage(path, post, true);
865
+ }, 900000);
866
+ return;
867
+ } else if (res.code === 502 || res.code === 503) {
868
+ loader.showProgress(undefined, "Scheduled Maintenance", "Sorry but this website is undergoing scheduled maintenance");
869
+ document.title = "Scheduled Maintenance";
870
+ history.replaceState(res.hadData, "Scheduled Maintenance", fullurl);
871
+ setTimeout(function() {
872
+ if (rid !== self.activerid)
873
+ return;
874
+ requestPage(path, post, true);
875
+ }, 900000);
876
+ return;
877
+ }
878
+
879
+ const location = res.headers['x-location'] || res.headers['location'];
880
+ if (location) {
881
+ const url = resolveUrl(location[0]);
882
+ if (startsWith.test(url)) {
883
+ (this.requestPage as any)(url.substring(this.url.length), undefined, true);
884
+ return;
885
+ }
886
+
887
+ console.warn("Requested redirect to external url:", url);
888
+ window.location.href = url;
889
+ return;
890
+ }
891
+
892
+ const contentDisposition = res.headers['content-disposition'];
893
+ if (contentDisposition && contentDisposition[0].toLowerCase() == "attachment")
894
+ throw new Error("Attachment disposition");
895
+ this.pagesyshandler(res, function() {
896
+ if (rid !== self.activerid)
897
+ return;
898
+ try {
899
+ const title = document.title;
900
+
901
+ const length = res.contentLength;
902
+ const tooBig = pageStateCacheSize <= length;
903
+ const stateData = tooBig ? undefined : {
904
+ title: title,
905
+ user: self.currentUserID,
906
+ scroll: [window.scrollX, window.scrollY],
907
+ body: self.saveComponents(document.body),
908
+ basehref: base ? base['href'] : undefined,
909
+ response: res
910
+ };
911
+ var i: number;
912
+ const cPageStates = pageStates.length;
913
+ try {
914
+ for(i=0; i<cPageStates; i++) {
915
+ const state = pageStates[i];
916
+ if (state.url === baseurl) {
917
+ if (tooBig) {
918
+ pageStates.splice(i, 1);
919
+ pageStatesSize -= state.size;
920
+ } else {
921
+ state.data = stateData;
922
+ state.updated = +new Date;
923
+ pageStatesSize += length - state.size;
924
+ }
925
+ throw true;
926
+ }
927
+ }
928
+ if (!tooBig) {
929
+ pageStates.push({
930
+ url: baseurl,
931
+ data: stateData,
932
+ updated: +new Date,
933
+ size: length
934
+ });
935
+ pageStatesSize += length;
936
+ throw true;
937
+ } else
938
+ debug("Response too big to store", baseurl, length);
939
+ } catch(e) {
940
+ if (e === true) {
941
+ const over = pageStatesSize - pageStateCacheSize;
942
+ if (over > 0) {
943
+ pageStates.sort(function(a, b) {
944
+ return a.updated - b.updated;
945
+ });
946
+ var found = 0;
947
+ for(i=0; i<cPageStates; i++) {
948
+ found += pageStates[i].size;
949
+ if (found >= over)
950
+ break;
951
+ }
952
+ i ++;
953
+ pageStates.splice(0, i);
954
+ debug("Trimmed", i, "items...");
955
+ }
956
+ } else
957
+ throw e;
958
+ }
959
+
960
+ history.replaceState(res.hadData, document.title, fullurl);
961
+ self.emit("page", baseurl, path);
962
+ } catch(e) {
963
+ debug(e);
964
+ if (replace)
965
+ window.location.reload(true);
966
+ else
967
+ try {
968
+ chash = undefined;
969
+ forwardPopState = [path, post];
970
+ debug("Going backwards");
971
+ history.go(-1);
972
+ } catch (e) {}
973
+ }
974
+ });
975
+ } catch (e) {
976
+ debug(e);
977
+ if (replace)
978
+ window.location.reload(true);
979
+ else
980
+ try {
981
+ chash = undefined;
982
+ forwardPopState = [path, post];
983
+ debug("Going backwards");
984
+ history.go(-1);
985
+ } catch (e) {}
986
+ }
987
+ }, post, rid);
988
+ }
989
+ };
990
+ this.registerComponent("a", AnchorElementComponent);
991
+ this.registerComponent("form", FormElementComponent);
992
+ window.addEventListener('popstate', (e) => {
993
+ try {
994
+ var state: PageState;
995
+ const bhash = location.href.match(beforeHash);
996
+ const cStates = pageStates.length;
997
+ const baseurl = bhash[1];
998
+ for(var i=0; i<cStates; i++) {
999
+ const _state = pageStates[i];
1000
+ if (_state.url === baseurl) {
1001
+ state = _state;
1002
+ break;
1003
+ }
1004
+ }
1005
+ const hasState = !!state;
1006
+ const rid = ++ self.activerid;
1007
+
1008
+ const error = hasState && state.data.error;
1009
+ if (error) {
1010
+ showError(error);
1011
+ return;
1012
+ }
1013
+
1014
+ if (forwardPopState) {
1015
+ debug("forwardPopState", forwardPopState);
1016
+ const forward = forwardPopState;
1017
+ setTimeout(() => {
1018
+ if (rid === self.activerid)
1019
+ self.defaultRequestPage(forward[0], forward[1]);
1020
+ });
1021
+ forwardPopState = undefined;
1022
+ return;
1023
+ }
1024
+
1025
+ if (chash === baseurl) {
1026
+ debug("Only hash has changed...", baseurl);
1027
+ return;
1028
+ }
1029
+ chash = bhash[1];
1030
+
1031
+ if (e.state)
1032
+ alert("You submitted data to this page, which cannot be re-sent. Because of this, what you're viewing may not be the same now.");
1033
+
1034
+ if (!hasState)
1035
+ throw new Error("No state for: " + baseurl + ", reloading...");
1036
+ if (state.data.user != self.currentUserID)
1037
+ throw new Error("User has changed since state was created, reloading...");
1038
+ document.title = state.data.title;
1039
+ loader.resetProgress();
1040
+ loader.resetError();
1041
+ self.pagesyshandler(state.data.response, function(err?: Error) {
1042
+ if (rid !== self.activerid)
1043
+ return;
1044
+ try {
1045
+ if (err)
1046
+ throw err;
1047
+ if (state.data.body)
1048
+ self.restoreComponents(document.body, state.data.body);
1049
+ if (state.data.scroll)
1050
+ window.scrollTo.apply(window, state.data.scroll);
1051
+ debug("Used stored page state!", state);
1052
+ } catch(err) {
1053
+ debug(err);
1054
+ var url = location.href;
1055
+ if (startsWith.test(url)) {
1056
+ try {
1057
+ if (/reloading\.\.\.$/.test(err.message)) {
1058
+ (self.requestPage as any)(url.substring(self.url.length), undefined, true);
1059
+ return;
1060
+ }
1061
+ console.error("Unknown error occured, actually navigating to page...");
1062
+ } catch (e) {
1063
+ console.error(e);
1064
+ }
1065
+ }
1066
+ location.reload(true);
1067
+ }
1068
+ });
1069
+ } catch (err) {
1070
+ debug(err);
1071
+ var url = location.href;
1072
+ if (startsWith.test(url)) {
1073
+ try {
1074
+ if (/reloading\.\.\.$/.test(err.message)) {
1075
+ (self.requestPage as any)(url.substring(self.url.length), undefined, true);
1076
+ return;
1077
+ }
1078
+ console.error("Unknown error occured, actually navigating to page...");
1079
+ } catch (e) {
1080
+ console.error(e);
1081
+ }
1082
+ }
1083
+ location.reload(true);
1084
+ }
1085
+ });
1086
+ return true;
1087
+ }
1088
+ get currentUser() {
1089
+ return this.currentUserID;
1090
+ }
1091
+ defaultRequestPage(path: string, post?: any) {
1092
+ if (post)
1093
+ throw new Error("Posting is not supported without an initialized page system, yet");
1094
+ else
1095
+ location.href = this.resolveUrl(path);
1096
+ }
1097
+ requestPage = this.defaultRequestPage;
1098
+ private _listeners: {[index: string]: Function[]} = {};
1099
+ on(event: string, cb: (...args: any[]) => void) {
1100
+ var listeners = this._listeners[event];
1101
+ if (listeners)
1102
+ listeners.push(cb);
1103
+ else
1104
+ this._listeners[event] = listeners = [cb];
1105
+
1106
+ }
1107
+ off(event: string, cb: (...args: any[]) => void) {
1108
+ var index: number;
1109
+ var listeners = this._listeners[event];
1110
+ if (listeners && (index = listeners.indexOf(cb)) > -1)
1111
+ listeners.splice(index, 1);
1112
+ }
1113
+ emit(event: string, ...args: any[]) {
1114
+ const self = this;
1115
+ const listeners = this._listeners[event];
1116
+ if (listeners)
1117
+ listeners.forEach(function (cb) {
1118
+ cb.apply(self, args);
1119
+ })
1120
+ }
1121
+ }
1122
+ var impl;
1123
+ if (window.io)
1124
+ impl = class NexusFrameworkWithIO extends NexusFrameworkBase {
1125
+ public constructor(url?: string) {
1126
+ super(url, window.io({ // TODO: Parse the root URL and give a path and hostname properly
1127
+ path: "/:io"
1128
+ }));
1129
+ const io = this.io;
1130
+ io.on("connect", function () {
1131
+ io.emit("init", loader.requestedResources());
1132
+ });
1133
+ }
1134
+ }
1135
+ else
1136
+ impl = class NexusFrameworkNoIO extends NexusFrameworkBase {
1137
+ public constructor(url?: string) {
1138
+ super(url);
1139
+ }
1140
+ }
1141
+ Object.defineProperty(window, "NexusFrameworkImpl", {
1142
+ value: impl
1143
+ });
1144
+ return impl;
1145
+ }
1146
+ },
1147
+ NexusFrameworkClient: {
1148
+ configurable: true,
1149
+ set: function (instance) {
1150
+ Object.defineProperty(window, "NexusFrameworkClient", {
1151
+ value: instance
1152
+ });
1153
+ },
1154
+ get: function () {
1155
+ const instance = new window.NexusFrameworkImpl();
1156
+ Object.defineProperty(window, "NexusFrameworkClient", {
1157
+ value: instance
1158
+ });
1159
+ return instance;
1160
+ }
1161
+ }
1162
+ });
1163
+ })(window);