@webqit/webflo 0.8.45 → 0.8.49

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 (38) hide show
  1. package/docker/Dockerfile +25 -0
  2. package/docker/README.md +69 -0
  3. package/package.json +9 -5
  4. package/src/cmd/client.js +68 -97
  5. package/src/cmd/origins.js +2 -2
  6. package/src/modules/Router.js +130 -0
  7. package/src/modules/_FormData.js +60 -0
  8. package/src/modules/_Headers.js +88 -0
  9. package/src/modules/_MessageStream.js +191 -0
  10. package/src/modules/_NavigationEvent.js +89 -0
  11. package/src/modules/_Request.js +61 -0
  12. package/src/modules/_RequestHeaders.js +72 -0
  13. package/src/modules/_Response.js +56 -0
  14. package/src/modules/_ResponseHeaders.js +81 -0
  15. package/src/modules/_URL.js +111 -0
  16. package/src/modules/client/Cache.js +38 -0
  17. package/src/modules/client/Client.js +51 -22
  18. package/src/modules/client/Http.js +26 -11
  19. package/src/modules/client/NavigationEvent.js +20 -0
  20. package/src/modules/client/Router.js +30 -104
  21. package/src/modules/client/StdRequest.js +34 -33
  22. package/src/modules/client/Storage.js +56 -0
  23. package/src/modules/client/Url.js +1 -1
  24. package/src/modules/client/Worker.js +74 -68
  25. package/src/modules/client/WorkerClient.js +102 -0
  26. package/src/modules/client/WorkerComm.js +183 -0
  27. package/src/modules/client/effects/sounds.js +64 -0
  28. package/src/modules/server/NavigationEvent.js +38 -0
  29. package/src/modules/server/Router.js +53 -124
  30. package/src/modules/server/Server.js +195 -87
  31. package/src/modules/util.js +7 -7
  32. package/src/modules/NavigationEvent.js +0 -32
  33. package/src/modules/Response.js +0 -98
  34. package/src/modules/XURL.js +0 -125
  35. package/src/modules/client/ClientNavigationEvent.js +0 -22
  36. package/src/modules/client/Push.js +0 -84
  37. package/src/modules/server/ServerNavigationEvent.js +0 -23
  38. package/src/modules/server/StdIncomingMessage.js +0 -70
@@ -2,11 +2,14 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
+ import { OOHTML, Observer } from '@webqit/pseudo-browser/index2.js';
5
6
  import _isObject from '@webqit/util/js/isObject.js';
6
7
  import _before from '@webqit/util/str/before.js';
8
+ import _unique from '@webqit/util/arr/unique.js';
7
9
  import _fetch from '@webqit/browser-pie/src/apis/fetch.js';
8
- import { OOHTML, Observer } from '@webqit/pseudo-browser/index2.js';
9
- import ClientNavigationEvent from './ClientNavigationEvent.js';
10
+ import NavigationEvent from './NavigationEvent.js';
11
+ import WorkerClient from './WorkerClient.js';
12
+ import Storage from './Storage.js';
10
13
  import Router from './Router.js';
11
14
  import Http from './Http.js';
12
15
 
@@ -26,6 +29,12 @@ OOHTML.call(window);
26
29
 
27
30
  export default function(layout, params) {
28
31
 
32
+ const session = Storage();
33
+ const workerClient = new WorkerClient('/worker.js', { startMessages: true });
34
+ Observer.observe(workerClient, changes => {
35
+ console.log('SERVICE_WORKER_STATE', changes[0].name, changes[0].value);
36
+ });
37
+
29
38
  // Copy..
30
39
  layout = {...layout};
31
40
  params = {...params};
@@ -38,6 +47,7 @@ export default function(layout, params) {
38
47
  * Apply routing
39
48
  * ----------------
40
49
  */
50
+
41
51
  Http.createClient(async function(request, event = null) {
42
52
 
43
53
  const httpInstance = this;
@@ -47,7 +57,6 @@ export default function(layout, params) {
47
57
  // -------------------
48
58
 
49
59
  // The srvice object
50
- const clientNavigationEvent = new ClientNavigationEvent(request);
51
60
  const $context = {
52
61
  layout,
53
62
  onHydration: !event && (await window.WebQit.OOHTML.meta.get('isomorphic')),
@@ -55,6 +64,7 @@ export default function(layout, params) {
55
64
  }
56
65
 
57
66
  // The app router
67
+ const clientNavigationEvent = new NavigationEvent(request, session);
58
68
  const requestPath = clientNavigationEvent.url.pathname;
59
69
  const router = new Router(requestPath, layout, $context);
60
70
  if (networkProgressOngoing) {
@@ -68,32 +78,54 @@ export default function(layout, params) {
68
78
  // ROUTE FOR DATA
69
79
  // --------
70
80
  const httpMethodName = clientNavigationEvent.request.method.toLowerCase();
71
- $context.response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], [clientNavigationEvent], null, async function() {
81
+ $context.response = await router.route([httpMethodName === 'delete' ? 'del' : httpMethodName, 'default'], clientNavigationEvent, document.state, async function(event) {
72
82
  // -----------------
73
83
  var networkProgress = networkProgressOngoing = new RequestHandle();
74
- networkProgress.setActive(true);
84
+ networkProgress.setActive(true, event.request._method || event.request.method);
75
85
  // -----------------
76
- const headers = clientNavigationEvent.request.headers;
86
+ const headers = event.request.headers;
77
87
  if (!headers.get('Accept')) {
78
88
  headers.set('Accept', 'application/json');
89
+ }
90
+ if (!headers.get('Cache-Control')) {
79
91
  headers.set('Cache-Control', 'no-store');
80
92
  }
81
- const response = _fetch(clientNavigationEvent.request, {}, networkProgress.updateProgress.bind(networkProgress));
93
+ // -----------------
94
+ // Sync session data to cache to be available to service-worker routers
95
+ const response = fetch(event.request, {}, networkProgress.updateProgress.bind(networkProgress));
96
+ // -----------------
82
97
  // -----------------
83
98
  response.catch(e => networkProgress.throw(e.message));
84
- return response.then(response => {
99
+ return response.then(async _response => {
100
+ _response = new clientNavigationEvent.Response(_response.body, {
101
+ status: _response.status,
102
+ statusText: _response.statusText,
103
+ headers: _response.headers,
104
+ _proxy: {
105
+ url: _response.url,
106
+ ok: _response.ok,
107
+ redirected: _response.redirected
108
+ },
109
+ });
110
+ // Save a reference to this
111
+ $context.responseClone = _response;
112
+ // Return a promise that never resolves as a new response is underway
85
113
  if (!networkProgress.active) {
86
114
  return new Promise(() => {});
87
115
  }
116
+ // Stop loading status
88
117
  networkProgress.setActive(false);
89
- return response.ok ? response.json() : null;
118
+ return _response;
90
119
  });
91
- }, []);
120
+ });
121
+ if ($context.response instanceof clientNavigationEvent.Response) {
122
+ $context.response = await $context.response.jsonBuild();
123
+ }
92
124
 
93
125
  // --------
94
126
  // Render
95
127
  // --------
96
- const rendering = await router.route('render', [clientNavigationEvent], $context.response, async function(data) {
128
+ const rendering = await router.route('render', clientNavigationEvent, $context.response, async function(event, data) {
97
129
  // --------
98
130
  // OOHTML would waiting for DOM-ready in order to be initialized
99
131
  await new Promise(res => window.WebQit.DOM.ready(res));
@@ -103,9 +135,10 @@ export default function(layout, params) {
103
135
  onHydration: $context.onHydration,
104
136
  network: networkWatch,
105
137
  url: httpInstance.location,
138
+ session,
106
139
  }, { update: true });
107
140
  }
108
- window.document.setState({page: data}, { update: 'merge' });
141
+ window.document.setState({ page: data }, { update: 'merge' });
109
142
  window.document.body.setAttribute('template', 'page/' + requestPath.split('/').filter(a => a).map(a => a + '+-').join('/'));
110
143
  return new Promise(res => {
111
144
  window.document.addEventListener('templatesreadystatechange', () => res(window));
@@ -113,19 +146,13 @@ export default function(layout, params) {
113
146
  res(window);
114
147
  }
115
148
  });
116
- }, []);
149
+ });
117
150
 
118
151
  // --------
119
152
  // Render...
120
153
  // --------
121
154
 
122
- if (_isObject(rendering) && rendering.document) {
123
- //$context.response = window.print();
124
- } else {
125
- //$context.response = rendering;
126
- }
127
-
128
- if (event && _isObject(event.detail) && (event.detail.src instanceof Element) && /* do only on url path change */ _before(event.value, '?') !== _before(event.oldValue, '?')) {
155
+ if (!document.activeElement && event && _isObject(event.detail) && (event.detail.src instanceof Element) && /* do only on url path change */ _before(event.value, '?') !== _before(event.oldValue, '?')) {
129
156
  setTimeout(() => {
130
157
  var urlTarget;
131
158
  if (clientNavigationEvent.url.hash && (urlTarget = document.querySelector(clientNavigationEvent.url.hash))) {
@@ -147,18 +174,20 @@ export default function(layout, params) {
147
174
 
148
175
  }
149
176
 
177
+ return $context.responseClone;
150
178
  });
151
179
 
152
180
  };
153
181
 
154
- const networkWatch = {progress: {}, online: navigator.onLine};
182
+ const networkWatch = { progress: {}, online: navigator.onLine };
155
183
  class RequestHandle {
156
- setActive(state) {
184
+ setActive(state, method = '') {
157
185
  if (this.active === false) {
158
186
  return;
159
187
  }
160
188
  this.active = state;
161
189
  Observer.set(networkWatch, {
190
+ method,
162
191
  error: '',
163
192
  progress: {
164
193
  active: state,
@@ -27,7 +27,7 @@ export default class Http {
27
27
  *
28
28
  * @return void
29
29
  */
30
- static createClient(client, params = {}) {
30
+ static async createClient(client, params = {}) {
31
31
 
32
32
  /**
33
33
  * ----------------
@@ -107,15 +107,22 @@ export default class Http {
107
107
  window.addEventListener('submit', e => {
108
108
  var actionEl = window.document.createElement('a'),
109
109
  form = e.target.closest('form'),
110
- submits = _arrFrom(form.elements).filter(el => el.matches('button,input[type="submit"],input[type="image"]'));
111
- var submitParams = ['action', 'enctype', 'method', 'noValidate', 'target'].reduce((params, prop) => {
110
+ submits = [e.submitter]; //_arrFrom(form.elements).filter(el => el.matches('button,input[type="submit"],input[type="image"]'));
111
+ var submitParams = [ 'action', 'enctype', 'method', 'noValidate', 'target' ].reduce((params, prop) => {
112
112
  params[prop] = submits.reduce((val, el) => val || (el.hasAttribute(`form${prop.toLowerCase()}`) ? el[`form${_toTitle(prop)}`] : null), null) || form[prop];
113
113
  return params;
114
114
  }, {});
115
+ // We support method hacking
116
+ // ---------------
117
+ submitParams.method = e.submitter.dataset.method || form.dataset.method || submitParams.method;
118
+ // ---------------
115
119
  if ((actionEl.href = submitParams.action) && !submitParams.target
116
120
  // Same origin... but...
117
121
  && (!actionEl.origin || actionEl.origin === instance.location.origin)) {
118
122
  var formData = new FormData(form);
123
+ if (e.submitter.name) {
124
+ formData.set(e.submitter.name, e.submitter.value);
125
+ }
119
126
  if (submitParams.method === 'get') {
120
127
  var query = wwwFormUnserialize(actionEl.search);
121
128
  Array.from(formData.entries()).forEach(_entry => {
@@ -129,7 +136,7 @@ export default class Http {
129
136
  }
130
137
  // Publish everything, including hash
131
138
  Observer.set(instance.location, Url.copy(actionEl), {
132
- detail: {type: 'form', src: form, submitParams, data: formData},
139
+ detail: { type: 'form', src: form, submitParams, data: formData },
133
140
  });
134
141
  // URLs with # will cause a natural navigation
135
142
  // even if pointing to a different page, a natural navigation will still happen
@@ -151,7 +158,7 @@ export default class Http {
151
158
  // -----------------------
152
159
  // Syndicate changes to
153
160
  // the browser;s location bar
154
- Observer.observe(instance.location, [['href']], e => {
161
+ Observer.observe(instance.location, [[ 'href' ]], e => {
155
162
  e = e[0];
156
163
  if ((e.detail || {}).src === window.document.location) {
157
164
  // Already from a "popstate" event as above, so don't push again
@@ -173,27 +180,35 @@ export default class Http {
173
180
  var options = {
174
181
  method: (detail.submitParams || detail.src || {}).method || 'get',
175
182
  body: detail.data,
176
- headers: { 'X-Powered-By': '@webqit/webflo', },
183
+ headers: { ...(detail.headers || {}), 'X-Powered-By': '@webqit/webflo', },
177
184
  referrer,
178
185
  };
179
- if (detail.accept) {
180
- options.headers.accept = detail.accept;
181
- }
182
186
  return new StdRequest(url, options);
183
187
  };
188
+ const handleResponse = response => {
189
+ if (response && response.redirected) {
190
+ var actionEl = window.document.createElement('a');
191
+ if ((actionEl.href = response.url) && (!actionEl.origin || actionEl.origin === instance.location.origin)) {
192
+ Observer.set(instance.location, { href: response.url }, {
193
+ detail: { follow: false },
194
+ });
195
+ }
196
+ }
197
+ };
184
198
  // ----------------------------------
185
199
 
186
200
  // Observe location and route
187
201
  Observer.observe(instance.location, [['href']], async e => {
188
202
  e = e[0];
189
203
  var detail = e.detail || {};
204
+ if (detail.follow === false) return;
190
205
  var method = (detail.submitParams || detail.src || {}).method;
191
206
  if ((_before(e.value, '#') !== _before(e.oldValue, '#')) || (method && method.toUpperCase() !== 'GET')) {
192
- return await client.call(instance, createRequest(e.value, e.oldValue, e), e);
207
+ return handleResponse(await client.call(instance, createRequest(e.value, e.oldValue, e), e));
193
208
  }
194
209
  }, {diff: false /* method might be the difference */});
195
210
  // Startup route
196
- client.call(instance, createRequest(window.document.location.href, document.referrer));
211
+ handleResponse(await client.call(instance, createRequest(window.document.location.href, document.referrer)));
197
212
 
198
213
  return instance;
199
214
  }
@@ -0,0 +1,20 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import _NavigationEvent from '../_NavigationEvent.js';
6
+ import _FormData from '../_FormData.js';
7
+
8
+ /**
9
+ * The ClientNavigationEvent class
10
+ */
11
+ export default _NavigationEvent({
12
+ URL,
13
+ Request,
14
+ Response,
15
+ Headers,
16
+ FormData: _FormData(FormData),
17
+ File,
18
+ Blob,
19
+ ReadableStream,
20
+ });
@@ -2,11 +2,8 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
- import _isString from '@webqit/util/js/isString.js';
6
- import _isFunction from '@webqit/util/js/isFunction.js';
7
- import _isArray from '@webqit/util/js/isArray.js';
8
- import _arrFrom from '@webqit/util/arr/from.js';
9
5
  import { path as Path } from '../util.js';
6
+ import _Router from '../Router.js';
10
7
 
11
8
  /**
12
9
  * ---------------------------
@@ -14,108 +11,37 @@ import { path as Path } from '../util.js';
14
11
  * ---------------------------
15
12
  */
16
13
 
17
- export default class Router {
14
+ export default class Router extends _Router {
18
15
 
19
- /**
20
- * Constructs a new Router instance
21
- * over route definitions.
22
- *
23
- * @param string|array path
24
- * @param object layout
25
- * @param object context
26
- *
27
- * @return void
28
- */
29
- constructor(path, layout, context) {
30
- this.path = _isArray(path) ? path : (path + '').split('/').filter(a => a);
31
- this.layout = layout;
32
- this.context = context;
33
- }
34
-
35
- /**
36
- * Performs dynamic routing.
37
- *
38
- * @param array|string target
39
- * @param array argsA
40
- * @param any input
41
- * @param function _default
42
- * @param array argsB
43
- *
44
- * @return object
45
- */
46
- async route(target, argsA, input, _default, argsB = []) {
47
-
48
- target = _arrFrom(target);
16
+ async readTick(thisTick) {
49
17
  var routeTree = this.layout;
50
- var context = this.context;
51
-
52
- // ----------------
53
- // The loop
54
- // ----------------
55
- const next = async function(index, input, path) {
56
-
57
- var exports;
58
- if (index === 0) {
59
- exports = routeTree['/'];
60
- } else if (path[index - 1]) {
61
- var currentHandlerPath = '/' + path.slice(0, index).join('/');
62
- var wildcardCurrentHandlerPath = '/' + path.slice(0, index - 1).concat('-').join('/');
63
- exports = routeTree[currentHandlerPath] || routeTree[wildcardCurrentHandlerPath];
64
- }
18
+ var routePaths = Object.keys(this.layout);
19
+ if (thisTick.trail) {
20
+ thisTick.currentSegment = thisTick.destination[thisTick.trail.length];
21
+ thisTick.currentSegmentOnFile = [ thisTick.currentSegment, '-' ].reduce((_segmentOnFile, _seg) => {
22
+ if (_segmentOnFile.index) return _segmentOnFile;
23
+ var _currentPath = `/${thisTick.trailOnFile.concat(_seg).join('/')}`;
24
+ return routeTree[_currentPath] ? { seg: _seg, index: _currentPath } : (
25
+ routePaths.filter(p => p.startsWith(`${_currentPath}/`)).length ? { seg: _seg, dirExists: true } : _segmentOnFile
26
+ );
27
+ }, { seg: null });
28
+ thisTick.trail.push(thisTick.currentSegment);
29
+ thisTick.trailOnFile.push(thisTick.currentSegmentOnFile.seg);
30
+ thisTick.exports = routeTree[thisTick.currentSegmentOnFile.index];
31
+ } else {
32
+ thisTick.trail = [];
33
+ thisTick.trailOnFile = [];
34
+ thisTick.currentSegmentOnFile = { index: '/' };
35
+ thisTick.exports = routeTree['/'];
36
+ }
37
+ return thisTick;
38
+ }
65
39
 
66
- if (exports) {
67
- const func = _isFunction(exports) && target.includes('default') ? exports : target.reduce((func, name) => func || exports[name], null);
68
- if (func) {
69
- // -------------
70
- // Dynamic response
71
- // -------------
72
- const _next = (..._args) => {
73
- var _index;
74
- if (_args.length > 1) {
75
- if (!_isString(_args[1])) {
76
- throw new Error('Router redirect must be a string!');
77
- }
78
- var newPath = Path.join(path.slice(0, index).join('/'), _args[1]);
79
- if (newPath.startsWith('../')) {
80
- throw new Error('Router redirect cannot traverse beyond the routing directory! (' + _args[1] + ' >> ' + newPath + ')');
81
- }
82
- _args[1] = newPath.split('/').map(a => a.trim()).filter(a => a);
83
- _index = path.slice(0, index).reduce((build, seg, i) => build.length === i && seg === _args[1][i] ? build.concat(seg) : build, []).length;
84
- } else {
85
- _args[1] = path;
86
- _index = index;
87
- }
88
- return next(_index + 1, ..._args);
89
- };
90
- _next.pathname = path.slice(index).join('/');
91
- _next.stepname = _next.pathname.split('/').shift();
92
- // -------------
93
- const _this = {
94
- pathname: '/' + path.slice(0, index).join('/'),
95
- ...context
96
- };
97
- _this.stepname = _this.pathname.split('/').pop();
98
- // -------------
99
- return await func.bind(_this)(...argsA.concat([input, _next/*next*/].concat(argsB)));
100
- } else {
101
- return next(index + 1, input, path);
102
- }
103
- }
40
+ finalizeHandlerContext(context, thisTick) {
41
+ return context.dirname = thisTick.currentSegmentOnFile.index;
42
+ }
104
43
 
105
- if (_default) {
106
- // -------------
107
- // Local file
108
- // -------------
109
- const defaultThis = {pathname: '/' + path.join('/'), ...context};
110
- return await _default.call(defaultThis, input);
111
- }
112
-
113
- // -------------
114
- // Recieved response or undefined
115
- // -------------
116
- return;
117
- };
118
-
119
- return next(0, input, this.path);
120
- }
44
+ pathJoin(...args) {
45
+ return Path.join(...args);
46
+ }
121
47
  };
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
- import { _isTypeObject } from '@webqit/util/js/index.js';
5
+ import { _isTypeObject, _isFunction } from '@webqit/util/js/index.js';
6
6
  import { wwwFormUnserialize, wwwFormSet } from '../util.js';
7
7
 
8
8
  /**
@@ -35,39 +35,40 @@ export default class StdRequest extends Request {
35
35
  }
36
36
 
37
37
  // Payload
38
- parse() {
39
- return new Promise(async resolve => {
40
- var request = this.clone();
41
- var contentType = request.headers['content-type'];
42
- var submits = {
43
- payload: null,
44
- inputs: {},
45
- files: {},
46
- type: contentType === 'application/x-www-form-urlencoded' || contentType.startsWith('multipart/') ? 'form-data'
47
- : (contentType === 'application/json' ? 'json'
48
- : (contentType === 'text/plain' ? 'plain'
49
- : 'other')),
50
- };
51
- if (submits.type === 'form-data') {
52
- submits.payload = await request.formData();
53
- for(var [ name, value ] of submits.payload.entries()) {
54
- if (value instanceof File) {
55
- wwwFormSet(submits.files, name, value);
56
- } else {
57
- wwwFormSet(submits.inputs, name, value);
58
- }
59
- }
60
- } else {
61
- submits.payload = await (submits.type === 'json'
62
- ? request.json() : (
63
- submits.type === 'plain' ? request.text() : request.arrayBuffer()
38
+ parse(force = false) {
39
+ if (!this._submits || force) {
40
+ this._submits = new Promise(async resolve => {
41
+ var request = this.clone();
42
+ var contentType = request.headers['content-type'] || '';
43
+ var submits = {
44
+ inputs: {},
45
+ files: {},
46
+ type: contentType === 'application/x-www-form-urlencoded' || contentType.startsWith('multipart/') || (!contentType && !['get'].includes(request.method.toLowerCase())) ? 'form-data'
47
+ : (contentType === 'application/json' ? 'json'
48
+ : (contentType === 'text/plain' ? 'plain'
49
+ : 'other')),
50
+ };
51
+ if (submits.type === 'form-data') {
52
+ try {
53
+ const payload = await request.formData();
54
+ for(var [ name, value ] of payload.entries()) {
55
+ if (value instanceof File) {
56
+ wwwFormSet(submits.files, name, value);
57
+ } else {
58
+ wwwFormSet(submits.inputs, name, value);
59
+ }
60
+ }
61
+ } catch(e) { }
62
+ } else {
63
+ submits.inputs = await (submits.type === 'json'
64
+ ? request.json() : (
65
+ submits.type === 'plain' ? request.text() : request.arrayBuffer()
66
+ )
64
67
  )
65
- )
66
- if (submits.type === 'json' && _isTypeObject(submits.payload)) {
67
- submits.inputs = inputs;
68
68
  }
69
- }
70
- resolve(submits);
71
- });
69
+ resolve(submits);
70
+ });
71
+ }
72
+ return this._submits;
72
73
  }
73
74
  }
@@ -0,0 +1,56 @@
1
+
2
+
3
+ /**
4
+ * @imports
5
+ */
6
+ import { Observer } from '@webqit/pseudo-browser/index2.js';
7
+ import { _isString, _isUndefined } from '@webqit/util/js/index.js';
8
+
9
+ export default function(persistent = false) {
10
+
11
+ const storeType = persistent ? 'localStorage' : 'sessionStorage';
12
+ if (!window[storeType]) {
13
+ throw new Error(`The specified Web Storage API ${storeType} is invalid or not supported`)
14
+ }
15
+
16
+ const _storage = {};
17
+ Observer.intercept(_storage, (event, received, next) => {
18
+ if (event.type === 'get' && _isString(event.name)) {
19
+ const value = window[storeType].getItem(event.name);
20
+ return !_isUndefined(value) ? JSON.parse(value) : value;
21
+ }
22
+ if (event.type === 'set') {
23
+ window[storeType].setItem(event.name, !_isUndefined(event.value) ? JSON.stringify(event.value) : event.value);
24
+ return true;
25
+ }
26
+ if (event.type === 'deleteProperty') {
27
+ window[storeType].removeItem(event.name);
28
+ return true;
29
+ }
30
+ if (event.type === 'has') {
31
+ for(var i = 0; i < window[storeType].length; i ++){
32
+ if (window[storeType].key(i) === event.name) {
33
+ return true;
34
+ }
35
+ };
36
+ return false;
37
+ }
38
+ if (event.type === 'ownKeys') {
39
+ var keys = [];
40
+ for(var i = 0; i < window[storeType].length; i ++){
41
+ keys.push(window[storeType].key(i));
42
+ };
43
+ return keys;
44
+ }
45
+ if (event.type === 'getOwnPropertyDescriptor') {
46
+ return { enumerable: true, configurable: true };
47
+ }
48
+ return next();
49
+ });
50
+
51
+ return Observer.proxy(_storage);
52
+ }
53
+
54
+ export {
55
+ Observer,
56
+ }
@@ -159,7 +159,7 @@ export default class Url {
159
159
  * @return object
160
160
  */
161
161
  static parseUrl(href) {
162
- var a = window.document.createElement('a');
162
+ var a = document.createElement('a');
163
163
  a.href = href;
164
164
  return this.copy(a);
165
165
  }