From 468845e4cf9a3a04cac8fbb53658555c1b7dc3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 16 Jun 2019 16:32:47 +0200 Subject: [PATCH 01/16] Server-side caching of viewmodels --- .../DotVVMServiceCollectionExtensions.cs | 2 + .../Hosting/DotvvmRequestContextExtensions.cs | 6 + .../Hosting/InterruptReason.cs | 3 +- .../Resources/Scripts/DotVVM.d.ts | 4 + .../Resources/Scripts/DotVVM.js | 206 ++++++++++++----- .../Resources/Scripts/DotVVM.ts | 212 +++++++++++++----- .../DefaultViewModelSerializer.cs | 48 +++- .../DefaultViewModelServerCache.cs | 46 ++++ .../Serialization/IViewModelServerCache.cs | 19 ++ .../Serialization/IViewModelServerStore.cs | 15 ++ .../InMemoryViewModelServerStore.cs | 34 +++ 11 files changed, 468 insertions(+), 127 deletions(-) create mode 100644 src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelServerCache.cs create mode 100644 src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerCache.cs create mode 100644 src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerStore.cs create mode 100644 src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs diff --git a/src/DotVVM.Framework/DependencyInjection/DotVVMServiceCollectionExtensions.cs b/src/DotVVM.Framework/DependencyInjection/DotVVMServiceCollectionExtensions.cs index 851aefd854..55d5bbc9ff 100644 --- a/src/DotVVM.Framework/DependencyInjection/DotVVMServiceCollectionExtensions.cs +++ b/src/DotVVM.Framework/DependencyInjection/DotVVMServiceCollectionExtensions.cs @@ -36,6 +36,8 @@ public static IServiceCollection RegisterDotVVMServices(IServiceCollection servi services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/DotVVM.Framework/Hosting/DotvvmRequestContextExtensions.cs b/src/DotVVM.Framework/Hosting/DotvvmRequestContextExtensions.cs index 9e669b5bc4..a8f5bbc948 100644 --- a/src/DotVVM.Framework/Hosting/DotvvmRequestContextExtensions.cs +++ b/src/DotVVM.Framework/Hosting/DotvvmRequestContextExtensions.cs @@ -131,6 +131,12 @@ public static void RedirectToRoutePermanent(this IDotvvmRequestContext context, public static void SetRedirectResponse(this IDotvvmRequestContext context, string url, int statusCode = (int)HttpStatusCode.Redirect, bool replaceInHistory = false, bool allowSpaRedirect = false) => context.Configuration.ServiceProvider.GetRequiredService().WriteRedirectResponse(context.HttpContext, url, statusCode, replaceInHistory, allowSpaRedirect); + internal static void SetCachedViewModelMissingResponse(this IDotvvmRequestContext context) + { + context.HttpContext.Response.StatusCode = 200; + context.HttpContext.Response.ContentType = "application/json"; + context.HttpContext.Response.Write(DefaultViewModelSerializer.GenerateMissingCachedViewModelResponse()); + } /// /// Ends the request execution when the is not valid and displays the validation errors in control. diff --git a/src/DotVVM.Framework/Hosting/InterruptReason.cs b/src/DotVVM.Framework/Hosting/InterruptReason.cs index 3a8065439f..88d944d33d 100644 --- a/src/DotVVM.Framework/Hosting/InterruptReason.cs +++ b/src/DotVVM.Framework/Hosting/InterruptReason.cs @@ -7,6 +7,7 @@ public enum InterruptReason Redirect, RedirectPermanent, ReturnFile, - ModelValidationFailed + ModelValidationFailed, + CachedViewModelMissing } } \ No newline at end of file diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.d.ts b/src/DotVVM.Framework/Resources/Scripts/DotVVM.d.ts index fd6fbb9cf6..32ac142049 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.d.ts +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.d.ts @@ -247,6 +247,8 @@ interface IDotvvmExtensions { } interface IDotvvmViewModelInfo { viewModel?: any; + viewModelCacheId?: string; + viewModelCache?: any; renderedResources?: string[]; url?: string; virtualDirectory?: string; @@ -333,6 +335,8 @@ declare class DotVVM { private addLeadingSlash; private concatUrl; patch(source: any, patch: any): any; + diff(source: any, modified: any): any; + readonly diffEqual: {}; private updateDynamicPathFragments; private postJSON; private getJSON; diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js index fef02314ca..af998a5dda 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js @@ -970,6 +970,7 @@ var DotVVM = /** @class */ (function () { this.fileUpload = new DotvvmFileUpload(); this.extensions = {}; this.isPostbackRunning = ko.observable(false); + this.diffEqual = {}; } DotVVM.prototype.createWindowSetTimeoutHandler = function (time) { return { @@ -1001,6 +1002,10 @@ var DotVVM = /** @class */ (function () { thisViewModel.renderedResources.forEach(function (r) { return _this.resourceSigns[r] = true; }); } var idFragment = thisViewModel.resultIdFragment; + // store server-side cached viewmodel + if (thisViewModel.viewModelCacheId) { + thisViewModel.viewModelCache = this.viewModels[viewModelName].viewModel; + } var viewModel = thisViewModel.viewModel = this.serialization.deserialize(this.viewModels[viewModelName].viewModel, {}, true); // initialize services this.culture = culture; @@ -1264,7 +1269,6 @@ var DotVVM = /** @class */ (function () { // perform the postback _this.updateDynamicPathFragments(context, path); var data = { - viewModel: _this.serialization.serialize(viewModel, { pathMatcher: function (val) { return context && val == context.$data; } }), currentPath: path, command: command, controlUniqueId: _this.processPassedId(controlUniqueId, context), @@ -1272,70 +1276,111 @@ var DotVVM = /** @class */ (function () { renderedResources: _this.viewModels[viewModelName].renderedResources, commandArgs: commandArgs }; + var completeViewModel = _this.serialization.serialize(viewModel, { pathMatcher: function (val) { return context && val == context.$data; } }); + // if the viewmodel is cached on the server, send only the diff + if (_this.viewModels[viewModelName].viewModelCache) { + data.viewModelDiff = _this.diff(_this.viewModels[viewModelName].viewModelCache, completeViewModel); + data.viewModelCacheId = _this.viewModels[viewModelName].viewModelCacheId; + } + else { + data.viewModel = completeViewModel; + } + var errorAction = function (xhr) { + reject({ type: 'network', options: options, args: new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, xhr, options.postbackId) }); + }; _this.postJSON(_this.viewModels[viewModelName].url, "POST", ko.toJSON(data), function (result) { - dotvvm.events.postbackResponseReceived.trigger({}); - resolve(function () { return new Promise(function (resolve, reject) { - dotvvm.events.postbackCommitInvoked.trigger({}); - var locationHeader = result.getResponseHeader("Location"); - var resultObject = locationHeader != null && locationHeader.length > 0 ? - { action: "redirect", url: locationHeader } : - JSON.parse(result.responseText); - if (!resultObject.viewModel && resultObject.viewModelDiff) { - // TODO: patch (~deserialize) it to ko.observable viewModel - resultObject.viewModel = _this.patch(data.viewModel, resultObject.viewModelDiff); - } - _this.loadResourceList(resultObject.resources, function () { - var isSuccess = false; - if (resultObject.action === "successfulCommand") { - try { - _this.isViewModelUpdating = true; - // remove updated controls - var updatedControls = _this.cleanUpdatedControls(resultObject); - // update the viewmodel - if (resultObject.viewModel) { - ko.delaySync.pause(); - _this.serialization.deserialize(resultObject.viewModel, _this.viewModels[viewModelName].viewModel); - ko.delaySync.resume(); + var resultObject = {}; + var successAction = function (actualResult) { + dotvvm.events.postbackResponseReceived.trigger({}); + resolve(function () { return new Promise(function (resolve, reject) { + dotvvm.events.postbackCommitInvoked.trigger({}); + if (!resultObject.viewModel && resultObject.viewModelDiff) { + // TODO: patch (~deserialize) it to ko.observable viewModel + resultObject.viewModel = _this.patch(completeViewModel, resultObject.viewModelDiff); + } + _this.loadResourceList(resultObject.resources, function () { + var isSuccess = false; + if (resultObject.action === "successfulCommand") { + try { + _this.isViewModelUpdating = true; + // store server-side cached viewmodel + if (resultObject.viewModelCacheId) { + _this.viewModels[viewModelName].viewModelCacheId = resultObject.viewModelCacheId; + _this.viewModels[viewModelName].viewModelCache = resultObject.viewModel; + } + else { + delete _this.viewModels[viewModelName].viewModelCacheId; + delete _this.viewModels[viewModelName].viewModelCache; + } + // remove updated controls + var updatedControls = _this.cleanUpdatedControls(resultObject); + // update the viewmodel + if (resultObject.viewModel) { + ko.delaySync.pause(); + _this.serialization.deserialize(resultObject.viewModel, _this.viewModels[viewModelName].viewModel); + ko.delaySync.resume(); + } + isSuccess = true; + // remove updated controls which were previously hidden + _this.cleanUpdatedControls(resultObject, updatedControls); + // add updated controls + _this.restoreUpdatedControls(resultObject, updatedControls, true); } - isSuccess = true; - // remove updated controls which were previously hidden - _this.cleanUpdatedControls(resultObject, updatedControls); - // add updated controls - _this.restoreUpdatedControls(resultObject, updatedControls, true); + finally { + _this.isViewModelUpdating = false; + } + dotvvm.events.postbackViewModelUpdated.trigger({}); } - finally { - _this.isViewModelUpdating = false; + else if (resultObject.action === "redirect") { + // redirect + _this.handleRedirect(resultObject, viewModelName); + return resolve(); } - dotvvm.events.postbackViewModelUpdated.trigger({}); - } - else if (resultObject.action === "redirect") { - // redirect - _this.handleRedirect(resultObject, viewModelName); - return resolve(); - } - var idFragment = resultObject.resultIdFragment; - if (idFragment) { - if (_this.getSpaPlaceHolder() || location.hash == "#" + idFragment) { - var element = document.getElementById(idFragment); - if (element && "function" == typeof element.scrollIntoView) - element.scrollIntoView(true); + var idFragment = resultObject.resultIdFragment; + if (idFragment) { + if (_this.getSpaPlaceHolder() || location.hash == "#" + idFragment) { + var element = document.getElementById(idFragment); + if (element && "function" == typeof element.scrollIntoView) + element.scrollIntoView(true); + } + else + location.hash = idFragment; } - else - location.hash = idFragment; - } - // trigger afterPostback event - if (!isSuccess) { - reject(new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, result, options.postbackId, resultObject)); - } - else { - var afterPostBackArgs = new DotvvmAfterPostBackEventArgs(options, resultObject, resultObject.commandResult, result); - resolve(afterPostBackArgs); - } - }); - }); }); - }, function (xhr) { - reject({ type: 'network', options: options, args: new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, xhr, options.postbackId) }); - }); + // trigger afterPostback event + if (!isSuccess) { + reject(new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, actualResult, options.postbackId, resultObject)); + } + else { + var afterPostBackArgs = new DotvvmAfterPostBackEventArgs(options, resultObject, resultObject.commandResult, result); + resolve(afterPostBackArgs); + } + }); + }); }); + }; + var parseResultObject = function (actualResult) { + var locationHeader = actualResult.getResponseHeader("Location"); + resultObject = locationHeader != null && locationHeader.length > 0 ? + { action: "redirect", url: locationHeader } : + JSON.parse(actualResult.responseText); + }; + parseResultObject(result); + if (resultObject.action === "viewModelNotCached") { + // repeat request with full viewModel + delete _this.viewModels[viewModelName].viewModelCache; + delete _this.viewModels[viewModelName].viewModelCacheId; + delete data.viewModelDiff; + delete data.viewModelCacheId; + data.viewModel = completeViewModel; + _this.postJSON(_this.viewModels[viewModelName].url, "POST", ko.toJSON(data), function (result2) { + parseResultObject(result2); + successAction(result2); + }, errorAction); + } + else { + // process the response + successAction(result); + } + }, errorAction); }); }; DotVVM.prototype.handleSpaNavigation = function (element) { @@ -1511,6 +1556,13 @@ var DotVVM = /** @class */ (function () { _this.viewModels[viewModelName][p] = resultObject[p]; } } + // store server-side cached viewmodel + if (resultObject.viewModelCacheId) { + _this.viewModels[viewModelName].viewModelCache = resultObject.viewModel; + } + else { + delete _this.viewModels[viewModelName].viewModelCache; + } ko.delaySync.pause(); _this.serialization.deserialize(resultObject.viewModel, _this.viewModels[viewModelName].viewModel); ko.delaySync.resume(); @@ -1650,6 +1702,40 @@ var DotVVM = /** @class */ (function () { return patch; return source; }; + DotVVM.prototype.diff = function (source, modified) { + var _this = this; + if (source instanceof Array && modified instanceof Array) { + return modified.map(function (val, i) { return _this.diff(source[i], val); }); + } + else if (source instanceof Array || modified instanceof Array) { + return modified; + } + else if (typeof source == "object" && typeof modified == "object" && source && modified) { + var result = this.diffEqual; + for (var p in modified) { + var propertyDiff = this.diff(source[p], modified[p]); + if (propertyDiff !== this.diffEqual) { + if (result == this.diffEqual) { + result = {}; + } + result[p] = propertyDiff; + } + else if (p[0] === "$") { + if (result == this.diffEqual) { + result = {}; + } + result[p] = modified[p]; + } + } + return result; + } + else if (source === modified) { + return this.diffEqual; + } + else { + return modified; + } + }; DotVVM.prototype.updateDynamicPathFragments = function (context, path) { for (var i = path.length - 1; i >= 0; i--) { if (path[i].indexOf("[$index]") >= 0) { diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.ts b/src/DotVVM.Framework/Resources/Scripts/DotVVM.ts index 3283ea005d..badcb64e88 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.ts +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.ts @@ -23,6 +23,8 @@ interface IDotvvmExtensions { interface IDotvvmViewModelInfo { viewModel?; + viewModelCacheId?: string; + viewModelCache?; renderedResources?: string[]; url?: string; virtualDirectory?: string; @@ -232,6 +234,12 @@ class DotVVM { thisViewModel.renderedResources.forEach(r => this.resourceSigns[r] = true); } var idFragment = thisViewModel.resultIdFragment; + + // store server-side cached viewmodel + if (thisViewModel.viewModelCacheId) { + thisViewModel.viewModelCache = this.viewModels[viewModelName].viewModel; + } + var viewModel = thisViewModel.viewModel = this.serialization.deserialize(this.viewModels[viewModelName].viewModel, {}, true); // initialize services @@ -503,8 +511,7 @@ class DotVVM { this.lastStartedPostack = options.postbackId // perform the postback this.updateDynamicPathFragments(context, path); - const data = { - viewModel: this.serialization.serialize(viewModel, { pathMatcher(val) { return context && val == context.$data } }), + const data: any = { currentPath: path, command: command, controlUniqueId: this.processPassedId(controlUniqueId, context), @@ -512,75 +519,122 @@ class DotVVM { renderedResources: this.viewModels[viewModelName].renderedResources, commandArgs: commandArgs }; - this.postJSON(this.viewModels[viewModelName].url, "POST", ko.toJSON(data), result => { - dotvvm.events.postbackResponseReceived.trigger({}) - resolve(() => new Promise((resolve, reject) => { - dotvvm.events.postbackCommitInvoked.trigger({}) - const locationHeader = result.getResponseHeader("Location"); - const resultObject = locationHeader != null && locationHeader.length > 0 ? - { action: "redirect", url: locationHeader } : - JSON.parse(result.responseText); + const completeViewModel = this.serialization.serialize(viewModel, { pathMatcher(val) { return context && val == context.$data } }); - if (!resultObject.viewModel && resultObject.viewModelDiff) { - // TODO: patch (~deserialize) it to ko.observable viewModel - resultObject.viewModel = this.patch(data.viewModel, resultObject.viewModelDiff); - } + // if the viewmodel is cached on the server, send only the diff + if (this.viewModels[viewModelName].viewModelCache) { + data.viewModelDiff = this.diff(this.viewModels[viewModelName].viewModelCache, completeViewModel); + data.viewModelCacheId = this.viewModels[viewModelName].viewModelCacheId; + } else { + data.viewModel = completeViewModel; + } - this.loadResourceList(resultObject.resources, () => { - var isSuccess = false; - if (resultObject.action === "successfulCommand") { - try { - this.isViewModelUpdating = true; + const errorAction = xhr => { + reject({ type: 'network', options: options, args: new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, xhr, options.postbackId) }); + }; - // remove updated controls - var updatedControls = this.cleanUpdatedControls(resultObject); + this.postJSON(this.viewModels[viewModelName].url, "POST", ko.toJSON(data), result => { + var resultObject: any = {}; - // update the viewmodel - if (resultObject.viewModel) { - ko.delaySync.pause(); - this.serialization.deserialize(resultObject.viewModel, this.viewModels[viewModelName].viewModel); - ko.delaySync.resume(); - } - isSuccess = true; + const successAction = actualResult => { + dotvvm.events.postbackResponseReceived.trigger({}) + resolve(() => new Promise((resolve, reject) => { + dotvvm.events.postbackCommitInvoked.trigger({}) - // remove updated controls which were previously hidden - this.cleanUpdatedControls(resultObject, updatedControls); + if (!resultObject.viewModel && resultObject.viewModelDiff) { + // TODO: patch (~deserialize) it to ko.observable viewModel + resultObject.viewModel = this.patch(completeViewModel, resultObject.viewModelDiff); + } - // add updated controls - this.restoreUpdatedControls(resultObject, updatedControls, true); + this.loadResourceList(resultObject.resources, () => { + var isSuccess = false; + if (resultObject.action === "successfulCommand") { + try { + this.isViewModelUpdating = true; + + // store server-side cached viewmodel + if (resultObject.viewModelCacheId) { + this.viewModels[viewModelName].viewModelCacheId = resultObject.viewModelCacheId; + this.viewModels[viewModelName].viewModelCache = resultObject.viewModel; + } else { + delete this.viewModels[viewModelName].viewModelCacheId; + delete this.viewModels[viewModelName].viewModelCache; + } + + // remove updated controls + var updatedControls = this.cleanUpdatedControls(resultObject); + + // update the viewmodel + if (resultObject.viewModel) { + ko.delaySync.pause(); + this.serialization.deserialize(resultObject.viewModel, this.viewModels[viewModelName].viewModel); + ko.delaySync.resume(); + } + isSuccess = true; + + // remove updated controls which were previously hidden + this.cleanUpdatedControls(resultObject, updatedControls); + + // add updated controls + this.restoreUpdatedControls(resultObject, updatedControls, true); + } + finally { + this.isViewModelUpdating = false; + } + dotvvm.events.postbackViewModelUpdated.trigger({}) + } else if (resultObject.action === "redirect") { + // redirect + this.handleRedirect(resultObject, viewModelName) + return resolve() } - finally { - this.isViewModelUpdating = false; + + var idFragment = resultObject.resultIdFragment; + if (idFragment) { + if (this.getSpaPlaceHolder() || location.hash == "#" + idFragment) { + var element = document.getElementById(idFragment); + if (element && "function" == typeof element.scrollIntoView) element.scrollIntoView(true); + } + else location.hash = idFragment; } - dotvvm.events.postbackViewModelUpdated.trigger({}) - } else if (resultObject.action === "redirect") { - // redirect - this.handleRedirect(resultObject, viewModelName) - return resolve() - } - var idFragment = resultObject.resultIdFragment; - if (idFragment) { - if (this.getSpaPlaceHolder() || location.hash == "#" + idFragment) { - var element = document.getElementById(idFragment); - if (element && "function" == typeof element.scrollIntoView) element.scrollIntoView(true); + // trigger afterPostback event + if (!isSuccess) { + reject(new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, actualResult, options.postbackId, resultObject)) + } else { + var afterPostBackArgs = new DotvvmAfterPostBackEventArgs(options, resultObject, resultObject.commandResult, result) + resolve(afterPostBackArgs) } - else location.hash = idFragment; - } + }); + })); + } - // trigger afterPostback event - if (!isSuccess) { - reject(new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, result, options.postbackId, resultObject)) - } else { - var afterPostBackArgs = new DotvvmAfterPostBackEventArgs(options, resultObject, resultObject.commandResult, result) - resolve(afterPostBackArgs) - } - }); - })); - }, xhr => { - reject({ type: 'network', options: options, args: new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, xhr, options.postbackId) }); - }); + const parseResultObject = actualResult => { + const locationHeader = actualResult.getResponseHeader("Location"); + resultObject = locationHeader != null && locationHeader.length > 0 ? + { action: "redirect", url: locationHeader } : + JSON.parse(actualResult.responseText); + } + parseResultObject(result); + + if (resultObject.action === "viewModelNotCached") { + // repeat request with full viewModel + delete this.viewModels[viewModelName].viewModelCache; + delete this.viewModels[viewModelName].viewModelCacheId; + delete data.viewModelDiff; + delete data.viewModelCacheId; + data.viewModel = completeViewModel; + + this.postJSON(this.viewModels[viewModelName].url, "POST", ko.toJSON(data), result2 => { + parseResultObject(result2); + successAction(result2); + }, errorAction); + } + else { + // process the response + successAction(result); + } + }, errorAction); }); } @@ -781,6 +835,13 @@ class DotVVM { } } + // store server-side cached viewmodel + if (resultObject.viewModelCacheId) { + this.viewModels[viewModelName].viewModelCache = resultObject.viewModel; + } else { + delete this.viewModels[viewModelName].viewModelCache; + } + ko.delaySync.pause(); this.serialization.deserialize(resultObject.viewModel, this.viewModels[viewModelName].viewModel); ko.delaySync.resume(); @@ -927,6 +988,39 @@ class DotVVM { return source; } + public diff(source: any, modified: any): any { + if (source instanceof Array && modified instanceof Array) { + return modified.map((val, i) => this.diff(source[i], val)); + } + else if (source instanceof Array || modified instanceof Array) { + return modified; + } + else if (typeof source == "object" && typeof modified == "object" && source && modified) { + var result = this.diffEqual; + for (var p in modified) { + var propertyDiff = this.diff(source[p], modified[p]); + if (propertyDiff !== this.diffEqual) { + if (result == this.diffEqual) { + result = {}; + } + result[p] = propertyDiff; + } else if (p[0] === "$") { + if (result == this.diffEqual) { + result = {}; + } + result[p] = modified[p]; + } + } + return result; + } + else if (source === modified) { + return this.diffEqual; + } else { + return modified; + } + } + public readonly diffEqual = {}; + private updateDynamicPathFragments(context: any, path: string[]): void { for (var i = path.length - 1; i >= 0; i--) { if (path[i].indexOf("[$index]") >= 0) { diff --git a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs index 6432811cb4..bc3b652e34 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs @@ -25,20 +25,24 @@ public class DefaultViewModelSerializer : IViewModelSerializer private readonly IViewModelProtector viewModelProtector; private readonly IViewModelSerializationMapper viewModelMapper; + private readonly IViewModelServerCache viewModelServerCache; public bool SendDiff { get; set; } = true; + public bool CacheViewModelsOnServer { get; set; } = true; + public Formatting JsonFormatting { get; set; } /// /// Initializes a new instance of the class. /// - public DefaultViewModelSerializer(DotvvmConfiguration configuration, IViewModelProtector protector, IViewModelSerializationMapper serializationMapper) + public DefaultViewModelSerializer(DotvvmConfiguration configuration, IViewModelProtector protector, IViewModelSerializationMapper serializationMapper, IViewModelServerCache viewModelServerCache) { this.viewModelProtector = protector; this.JsonFormatting = configuration.Debug ? Formatting.Indented : Formatting.None; this.viewModelMapper = serializationMapper; + this.viewModelServerCache = viewModelServerCache; } /// @@ -74,13 +78,20 @@ public void BuildViewModel(IDotvvmRequestContext context) { throw new Exception($"Could not serialize viewModel of type { context.ViewModel.GetType().Name }. Serialization failed at property { writer.Path }. {GeneralViewModelRecommendations}", ex); } + var viewModelToken = writer.Token; + + string viewModelCacheId = null; + if (CacheViewModelsOnServer) + { + viewModelCacheId = viewModelServerCache.StoreViewModel(context, (JObject)viewModelToken); + } // persist CSRF token - writer.Token["$csrfToken"] = context.CsrfToken; + viewModelToken["$csrfToken"] = context.CsrfToken; // persist encrypted values if (viewModelConverter.EncryptedValues.Count > 0) - writer.Token["$encryptedValues"] = viewModelProtector.Protect(viewModelConverter.EncryptedValues.ToString(Formatting.None), context); + viewModelToken["$encryptedValues"] = viewModelProtector.Protect(viewModelConverter.EncryptedValues.ToString(Formatting.None), context); // serialize validation rules bool useClientSideValidation = context.Configuration.ClientSideValidation; @@ -90,7 +101,11 @@ public void BuildViewModel(IDotvvmRequestContext context) // create result object var result = new JObject(); - result["viewModel"] = writer.Token; + result["viewModel"] = viewModelToken; + if (viewModelCacheId != null) + { + result["viewModelCacheId"] = viewModelCacheId; + } result["url"] = context.HttpContext?.Request?.Url?.PathAndQuery; result["virtualDirectory"] = context.HttpContext?.Request?.PathBase?.Value?.Trim('/') ?? ""; if (context.ResultIdFragment != null) @@ -109,7 +124,7 @@ public void BuildViewModel(IDotvvmRequestContext context) } // TODO: do not send on postbacks if (validationRules?.Count > 0) result["validationRules"] = validationRules; - + context.ViewModelJson = result; } @@ -195,6 +210,17 @@ public static string GenerateRedirectActionResponse(string url, bool replace, bo return result.ToString(Formatting.None); } + /// + /// Serializes the missing cached viewmodel action. + /// + internal static string GenerateMissingCachedViewModelResponse() + { + // create result object + var result = new JObject(); + result["action"] = "viewModelNotCached"; + return result.ToString(Formatting.None); + } + /// /// Serializes the validation errors in case the viewmodel was not valid. /// @@ -214,10 +240,18 @@ public string SerializeModelState(IDotvvmRequestContext context) /// public void PopulateViewModel(IDotvvmRequestContext context, string serializedPostData) { - // get properties var data = context.ReceivedViewModelJson = JObject.Parse(serializedPostData); - var viewModelToken = (JObject)data["viewModel"]; + JObject viewModelToken; + if (data["viewModelCacheId"] != null) + { + viewModelToken = viewModelServerCache.TryRestoreViewModel(context, (string)data["viewModelCacheId"], (JObject)data["viewModelDiff"]); + data["viewModel"] = viewModelToken; + } + else + { + viewModelToken = (JObject)data["viewModel"]; + } // load CSRF token context.CsrfToken = viewModelToken["$csrfToken"].Value(); diff --git a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelServerCache.cs b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelServerCache.cs new file mode 100644 index 0000000000..aa0991c6c2 --- /dev/null +++ b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelServerCache.cs @@ -0,0 +1,46 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.Hosting; +using DotVVM.Framework.Utils; +using Newtonsoft.Json.Linq; + +namespace DotVVM.Framework.ViewModel.Serialization +{ + public class DefaultViewModelServerCache : IViewModelServerCache + { + private readonly SHA256 sha256; + private readonly IViewModelServerStore viewModelStore; + + public DefaultViewModelServerCache(IViewModelServerStore viewModelStore) + { + sha256 = SHA256.Create(); + this.viewModelStore = viewModelStore; + } + + public string StoreViewModel(IDotvvmRequestContext context, JObject viewModelToken) + { + var cacheData = viewModelToken.ToString(Newtonsoft.Json.Formatting.None); + var hash = Convert.ToBase64String(sha256.ComputeHash(Encoding.UTF8.GetBytes(cacheData))); + + viewModelStore.Store(hash, cacheData); + return hash; + } + + public JObject TryRestoreViewModel(IDotvvmRequestContext context, string viewModelCacheId, JObject viewModelDiffToken) + { + var cachedData = viewModelStore.Retrieve(viewModelCacheId); + if (cachedData == null) + { + // the client needs to repeat the postback and send the full viewmode + context.SetCachedViewModelMissingResponse(); + throw new DotvvmInterruptRequestExecutionException(InterruptReason.CachedViewModelMissing); + } + + var result = JObject.Parse(cachedData); + JsonUtils.Patch(result, viewModelDiffToken); + return result; + } + } +} diff --git a/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerCache.cs b/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerCache.cs new file mode 100644 index 0000000000..bbf544034d --- /dev/null +++ b/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerCache.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.Hosting; +using Newtonsoft.Json.Linq; + +namespace DotVVM.Framework.ViewModel.Serialization +{ + public interface IViewModelServerCache + { + + string StoreViewModel(IDotvvmRequestContext context, JObject viewModelToken); + + JObject TryRestoreViewModel(IDotvvmRequestContext context, string viewModelCacheId, JObject viewModelDiffToken); + + } +} diff --git a/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerStore.cs b/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerStore.cs new file mode 100644 index 0000000000..b96faa3383 --- /dev/null +++ b/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerStore.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; +using DotVVM.Framework.Runtime.Caching; + +namespace DotVVM.Framework.ViewModel.Serialization +{ + public interface IViewModelServerStore + { + + void Store(string hash, string cacheData); + + string Retrieve(string hash); + + } +} diff --git a/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs b/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs new file mode 100644 index 0000000000..8a27d552a1 --- /dev/null +++ b/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; +using DotVVM.Framework.Runtime.Caching; + +namespace DotVVM.Framework.ViewModel.Serialization +{ + public class InMemoryViewModelServerStore : IViewModelServerStore + { + private readonly IDotvvmCacheAdapter cacheAdapter; + + public TimeSpan CacheLifetime { get; set; } = TimeSpan.FromMinutes(5); + + public InMemoryViewModelServerStore(IDotvvmCacheAdapter cacheAdapter) + { + this.cacheAdapter = cacheAdapter; + } + + public string Retrieve(string hash) + { + return cacheAdapter.Get(BuildKey(hash)); + } + + public void Store(string hash, string cacheData) + { + cacheAdapter.GetOrAdd(BuildKey(hash), k => new DotvvmCachedItem(cacheData, slidingExpiration: CacheLifetime)); + } + + private static string BuildKey(string hash) + { + return nameof(InMemoryViewModelServerStore) + "_" + hash; + } + + } +} From 440b51236160093695fbdcbb299590e3a8d69fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 16 Jun 2019 18:35:34 +0200 Subject: [PATCH 02/16] Service registration issue in tests fixed --- .../DefaultViewModelSerializerTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DotVVM.Framework.Tests.Owin/DefaultViewModelSerializerTests.cs b/src/DotVVM.Framework.Tests.Owin/DefaultViewModelSerializerTests.cs index a92794781c..3979989ed3 100644 --- a/src/DotVVM.Framework.Tests.Owin/DefaultViewModelSerializerTests.cs +++ b/src/DotVVM.Framework.Tests.Owin/DefaultViewModelSerializerTests.cs @@ -18,6 +18,8 @@ using Microsoft.Owin.Security.DataProtection; using Newtonsoft.Json.Linq; using Microsoft.Owin.Infrastructure; +using DotVVM.Framework.Runtime.Caching; +using DotVVM.Framework.Hosting.Owin.Runtime.Caching; namespace DotVVM.Framework.Tests.Runtime { @@ -37,6 +39,7 @@ public void TestInit() services.AddTransient(); services.AddTransient(); services.AddSingleton(); + services.AddSingleton(); }); configuration.Security.SigningKey = Convert.FromBase64String("Uiq1FXs016lC6QaWIREB7H2P/sn4WrxkvFkqaIKpB27E7RPuMipsORgSgnT+zJmUu8zXNSJ4BdL73JEMRDiF6A1ScRNwGyDxDAVL3nkpNlGrSoLNM1xHnVzSbocLFDrdEiZD2e3uKujguycvWSNxYzjgMjXNsaqvCtMu/qRaEGc="); configuration.Security.EncryptionKey = Convert.FromBase64String("jNS9I3ZcxzsUSPYJSwzCOm/DEyKFNlBmDGo9wQ6nxKg="); From 957c4efbe2c50f422017371b4115b835f8b00257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sat, 13 Jul 2019 15:44:32 +0200 Subject: [PATCH 03/16] Feature flag integrated --- .../DotvvmExperimentalFeaturesConfiguration.cs | 4 ++++ .../Serialization/DefaultViewModelSerializer.cs | 9 ++++++--- src/DotVVM.Samples.Common/sampleConfig.json | 12 +++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/DotVVM.Framework/Configuration/DotvvmExperimentalFeaturesConfiguration.cs b/src/DotVVM.Framework/Configuration/DotvvmExperimentalFeaturesConfiguration.cs index 4d710824ac..ab85cbd1d6 100644 --- a/src/DotVVM.Framework/Configuration/DotvvmExperimentalFeaturesConfiguration.cs +++ b/src/DotVVM.Framework/Configuration/DotvvmExperimentalFeaturesConfiguration.cs @@ -8,9 +8,13 @@ public class DotvvmExperimentalFeaturesConfiguration { // Add a property od DotvvmExperimentalFeatureFlag for each experimental feature here + [JsonProperty("lazyCsrfToken", DefaultValueHandling = DefaultValueHandling.Ignore)] public DotvvmExperimentalFeatureFlag LazyCsrfToken { get; private set; } = new DotvvmExperimentalFeatureFlag(); + [JsonProperty("serverSideViewModelCache", DefaultValueHandling = DefaultValueHandling.Ignore)] + public DotvvmExperimentalFeatureFlag ServerSideViewModelCache { get; private set; } = new DotvvmExperimentalFeatureFlag(); + } } diff --git a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs index bc3b652e34..1d2a46c60d 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs @@ -29,8 +29,6 @@ public class DefaultViewModelSerializer : IViewModelSerializer public bool SendDiff { get; set; } = true; - public bool CacheViewModelsOnServer { get; set; } = true; - public Formatting JsonFormatting { get; set; } @@ -81,7 +79,7 @@ public void BuildViewModel(IDotvvmRequestContext context) var viewModelToken = writer.Token; string viewModelCacheId = null; - if (CacheViewModelsOnServer) + if (context.Configuration.ExperimentalFeatures.ServerSideViewModelCache.IsEnabledForRoute(context.Route.RouteName)) { viewModelCacheId = viewModelServerCache.StoreViewModel(context, (JObject)viewModelToken); } @@ -245,6 +243,11 @@ public void PopulateViewModel(IDotvvmRequestContext context, string serializedPo JObject viewModelToken; if (data["viewModelCacheId"] != null) { + if (!context.Configuration.ExperimentalFeatures.ServerSideViewModelCache.IsEnabledForRoute(context.Route.RouteName)) + { + throw new InvalidOperationException("The server-side viewmodel caching is not enabled for the current route!"); + } + viewModelToken = viewModelServerCache.TryRestoreViewModel(context, (string)data["viewModelCacheId"], (JObject)data["viewModelDiff"]); data["viewModel"] = viewModelToken; } diff --git a/src/DotVVM.Samples.Common/sampleConfig.json b/src/DotVVM.Samples.Common/sampleConfig.json index 7bc76be096..c579f21e42 100644 --- a/src/DotVVM.Samples.Common/sampleConfig.json +++ b/src/DotVVM.Samples.Common/sampleConfig.json @@ -1,5 +1,5 @@ { - "activeProfile": "Default", + "activeProfile": "ServerSideViewModelCache", "profiles": [ { "name": "Default", @@ -18,6 +18,16 @@ } } } + }, + { + "name": "ServerSideViewModelCache", + "config": { + "experimentalFeatures": { + "serverSideViewModelCache": { + "enabled": true + } + } + } } ] } From 89835f5cb3b782cb14c4954ead3188158c0d53ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sat, 13 Jul 2019 16:21:02 +0200 Subject: [PATCH 04/16] Unit tests fixed --- .../DefaultViewModelSerializerTests.cs | 5 +++-- src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/DotVVM.Framework.Tests.Owin/DefaultViewModelSerializerTests.cs b/src/DotVVM.Framework.Tests.Owin/DefaultViewModelSerializerTests.cs index 3979989ed3..4ed4b9cab0 100644 --- a/src/DotVVM.Framework.Tests.Owin/DefaultViewModelSerializerTests.cs +++ b/src/DotVVM.Framework.Tests.Owin/DefaultViewModelSerializerTests.cs @@ -20,6 +20,7 @@ using Microsoft.Owin.Infrastructure; using DotVVM.Framework.Runtime.Caching; using DotVVM.Framework.Hosting.Owin.Runtime.Caching; +using DotVVM.Framework.Routing; namespace DotVVM.Framework.Tests.Runtime { @@ -57,11 +58,11 @@ public void TestInit() serializer = configuration.ServiceLocator.GetService() as DefaultViewModelSerializer; - context = new DotvvmRequestContext() - { + context = new DotvvmRequestContext() { Configuration = configuration, HttpContext = contextMock.Object, Presenter = configuration.RouteTable.GetDefaultPresenter(configuration.ServiceProvider), + Route = new DotvvmRoute("TestRoute", "test.dothtml", new { }, p => p.GetService(), configuration) }; } diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js index c18a670dae..b090aad3c4 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js @@ -1 +1 @@ -var __assign=this&&this.__assign||function(){return(__assign=Object.assign||function(e){for(var t,n=1,r=arguments.length;n'");return e},e.prototype.format=function(e){for(var o=this,a=[],t=1;t=e.length)n();else{var o=e[t],a=!1;if("script"==o.tagName.toLowerCase()){var i=document.createElement("script");o.src&&(i.src=o.src,a=!0),o.type&&(i.type=o.type),o.text&&(i.text=o.text),o.id&&(i.id=o.id),o=i}else if("link"==o.tagName.toLowerCase()){var s=document.createElement("link");o.href&&(s.href=o.href),o.rel&&(s.rel=o.rel),o.type&&(s.type=o.type),o=s}a&&(o.onload=function(){return r.loadResourceElements(e,t+1,n)}),document.head.appendChild(o),a||this.loadResourceElements(e,t+1,n)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(i,e,t){var s=this,n=this.viewModels[i].viewModel,r=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(n,i,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var a=this.viewModels[i].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),d=this.addLeadingSlash(this.concatUrl(a,l)),u=this.getSpaPlaceHolder();if(u){t&&t(this.addLeadingSlash(this.concatUrl(a,this.addLeadingSlash(e))));var v=u.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(d,"GET",v,function(o){if(s.isPostBackStillActive(r)){var a=JSON.parse(o.responseText);s.loadResourceList(a.resources,function(){var e=!1;if("successfulCommand"!==a.action&&a.action){if("redirect"===a.action)return void s.handleRedirect(a,i,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(a);for(var n in s.viewModels[i]={},a)a.hasOwnProperty(n)&&(s.viewModels[i][n]=a[n]);ko.delaySync.pause(),s.serialization.deserialize(a.viewModel,s.viewModels[i].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[i](s.viewModels[i].viewModel),s.restoreUpdatedControls(a,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var r=new DotvvmSpaNavigatedEventArgs(s.viewModels[i].viewModel,i,a,o);if(s.events.spaNavigated.trigger(r),!e&&!r.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(r)){var t=new DotvvmErrorEventArgs(void 0,n,i,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=d}},e.prototype.handleRedirect=function(e,t,n){var r;void 0===n&&(n=!1),null!=e.replace&&(n=e.replace),r=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(r="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(r="#!/"),this.fixSpaUrlPrefix(r)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,r,n);this.events.redirect.trigger(o),this.performRedirect(r,n,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,n){if(t)location.replace(e);else if(n)this.handleSpaNavigationCore(e);else{var r=this.fakeRedirectAnchor;r||((r=document.createElement("a")).style.display="none",r.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(r),this.fakeRedirectAnchor=r),r.href=e,r.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var n=t.value;return n!==document.location.pathname&&(""===n&&(n="/"),e=n+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var n="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(n)?this.addLeadingSlash(e.substring(n.length)):e},e.prototype.addLeadingSlash=function(e){return 0'");return e},e.prototype.format=function(e){for(var o=this,i=[],t=1;t=e.length)n();else{var o=e[t],i=!1;if("script"==o.tagName.toLowerCase()){var a=document.createElement("script");o.src&&(a.src=o.src,i=!0),o.type&&(a.type=o.type),o.text&&(a.text=o.text),o.id&&(a.id=o.id),o=a}else if("link"==o.tagName.toLowerCase()){var s=document.createElement("link");o.href&&(s.href=o.href),o.rel&&(s.rel=o.rel),o.type&&(s.type=o.type),o=s}i&&(o.onload=function(){return r.loadResourceElements(e,t+1,n)}),document.head.appendChild(o),i||this.loadResourceElements(e,t+1,n)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(a,e,t){var s=this,n=this.viewModels[a].viewModel,r=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(n,a,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var i=this.viewModels[a].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),d=this.addLeadingSlash(this.concatUrl(i,l)),u=this.getSpaPlaceHolder();if(u){t&&t(this.addLeadingSlash(this.concatUrl(i,this.addLeadingSlash(e))));var v=u.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(d,"GET",v,function(o){if(s.isPostBackStillActive(r)){var i=JSON.parse(o.responseText);s.loadResourceList(i.resources,function(){var e=!1;if("successfulCommand"!==i.action&&i.action){if("redirect"===i.action)return void s.handleRedirect(i,a,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(i);for(var n in s.viewModels[a]={},i)i.hasOwnProperty(n)&&(s.viewModels[a][n]=i[n]);i.viewModelCacheId?s.viewModels[a].viewModelCache=i.viewModel:delete s.viewModels[a].viewModelCache,ko.delaySync.pause(),s.serialization.deserialize(i.viewModel,s.viewModels[a].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[a](s.viewModels[a].viewModel),s.restoreUpdatedControls(i,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var r=new DotvvmSpaNavigatedEventArgs(s.viewModels[a].viewModel,a,i,o);if(s.events.spaNavigated.trigger(r),!e&&!r.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(r)){var t=new DotvvmErrorEventArgs(void 0,n,a,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=d}},e.prototype.handleRedirect=function(e,t,n){var r;void 0===n&&(n=!1),null!=e.replace&&(n=e.replace),r=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(r="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(r="#!/"),this.fixSpaUrlPrefix(r)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,r,n);this.events.redirect.trigger(o),this.performRedirect(r,n,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,n){if(t)location.replace(e);else if(n)this.handleSpaNavigationCore(e);else{var r=this.fakeRedirectAnchor;r||((r=document.createElement("a")).style.display="none",r.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(r),this.fakeRedirectAnchor=r),r.href=e,r.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var n=t.value;return n!==document.location.pathname&&(""===n&&(n="/"),e=n+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var n="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(n)?this.addLeadingSlash(e.substring(n.length)):e},e.prototype.addLeadingSlash=function(e){return 0 Date: Fri, 19 Jul 2019 18:20:13 +0200 Subject: [PATCH 05/16] Various code review fixes --- src/DotVVM.Framework/DotVVM.Framework.csproj | 1 + .../Hosting/DotvvmPresenter.cs | 6 ++++ .../DefaultViewModelSerializer.cs | 4 +-- .../DefaultViewModelServerCache.cs | 34 ++++++++++++++----- .../Serialization/IViewModelServerStore.cs | 4 +-- .../InMemoryViewModelServerStore.cs | 8 ++--- 6 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/DotVVM.Framework/DotVVM.Framework.csproj b/src/DotVVM.Framework/DotVVM.Framework.csproj index b14ceb7686..3bf795287c 100644 --- a/src/DotVVM.Framework/DotVVM.Framework.csproj +++ b/src/DotVVM.Framework/DotVVM.Framework.csproj @@ -101,6 +101,7 @@ + diff --git a/src/DotVVM.Framework/Hosting/DotvvmPresenter.cs b/src/DotVVM.Framework/Hosting/DotvvmPresenter.cs index 64c4f90130..f703115c64 100644 --- a/src/DotVVM.Framework/Hosting/DotvvmPresenter.cs +++ b/src/DotVVM.Framework/Hosting/DotvvmPresenter.cs @@ -271,6 +271,12 @@ public async Task ProcessRequestCore(IDotvvmRequestContext context) foreach (var f in requestFilters) await f.OnPageRenderedAsync(context); } + catch (DotvvmInterruptRequestExecutionException ex) when (ex.InterruptReason == InterruptReason.CachedViewModelMissing) + { + // the client needs to repeat the postback and send the full viewmodel + context.SetCachedViewModelMissingResponse(); + throw; + } catch (DotvvmInterruptRequestExecutionException) { throw; } catch (DotvvmHttpException) { throw; } catch (Exception ex) diff --git a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs index 1d2a46c60d..79703ce29a 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs @@ -122,7 +122,7 @@ public void BuildViewModel(IDotvvmRequestContext context) } // TODO: do not send on postbacks if (validationRules?.Count > 0) result["validationRules"] = validationRules; - + context.ViewModelJson = result; } @@ -254,7 +254,7 @@ public void PopulateViewModel(IDotvvmRequestContext context, string serializedPo else { viewModelToken = (JObject)data["viewModel"]; - } + } // load CSRF token context.CsrfToken = viewModelToken["$csrfToken"].Value(); diff --git a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelServerCache.cs b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelServerCache.cs index aa0991c6c2..03d48362f3 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelServerCache.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelServerCache.cs @@ -1,29 +1,28 @@ using System; +using System.IO; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using DotVVM.Framework.Hosting; using DotVVM.Framework.Utils; +using Newtonsoft.Json.Bson; using Newtonsoft.Json.Linq; namespace DotVVM.Framework.ViewModel.Serialization { public class DefaultViewModelServerCache : IViewModelServerCache { - private readonly SHA256 sha256; private readonly IViewModelServerStore viewModelStore; public DefaultViewModelServerCache(IViewModelServerStore viewModelStore) { - sha256 = SHA256.Create(); this.viewModelStore = viewModelStore; } public string StoreViewModel(IDotvvmRequestContext context, JObject viewModelToken) { - var cacheData = viewModelToken.ToString(Newtonsoft.Json.Formatting.None); - var hash = Convert.ToBase64String(sha256.ComputeHash(Encoding.UTF8.GetBytes(cacheData))); - + var cacheData = PackViewModel(viewModelToken); + var hash = Convert.ToBase64String(SHA256.Create().ComputeHash(cacheData)); viewModelStore.Store(hash, cacheData); return hash; } @@ -33,14 +32,33 @@ public JObject TryRestoreViewModel(IDotvvmRequestContext context, string viewMod var cachedData = viewModelStore.Retrieve(viewModelCacheId); if (cachedData == null) { - // the client needs to repeat the postback and send the full viewmode - context.SetCachedViewModelMissingResponse(); throw new DotvvmInterruptRequestExecutionException(InterruptReason.CachedViewModelMissing); } - var result = JObject.Parse(cachedData); + var result = UnpackViewModel(cachedData); JsonUtils.Patch(result, viewModelDiffToken); return result; } + + protected virtual byte[] PackViewModel(JObject viewModelToken) + { + using (var ms = new MemoryStream()) + using (var bsonWriter = new BsonDataWriter(ms)) + { + viewModelToken.WriteTo(bsonWriter); + bsonWriter.Flush(); + + return ms.ToArray(); + } + } + + protected virtual JObject UnpackViewModel(byte[] cachedData) + { + using (var ms = new MemoryStream(cachedData)) + using (var bsonReader = new BsonDataReader(ms)) + { + return (JObject)JToken.ReadFrom(bsonReader); + } + } } } diff --git a/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerStore.cs b/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerStore.cs index b96faa3383..6d1822524a 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerStore.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/IViewModelServerStore.cs @@ -7,9 +7,9 @@ namespace DotVVM.Framework.ViewModel.Serialization public interface IViewModelServerStore { - void Store(string hash, string cacheData); + void Store(string hash, byte[] cacheData); - string Retrieve(string hash); + byte[] Retrieve(string hash); } } diff --git a/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs b/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs index 8a27d552a1..2b86cf9493 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs @@ -15,14 +15,14 @@ public InMemoryViewModelServerStore(IDotvvmCacheAdapter cacheAdapter) this.cacheAdapter = cacheAdapter; } - public string Retrieve(string hash) + public byte[] Retrieve(string hash) { - return cacheAdapter.Get(BuildKey(hash)); + return cacheAdapter.Get(BuildKey(hash)); } - public void Store(string hash, string cacheData) + public void Store(string hash, byte[] cacheData) { - cacheAdapter.GetOrAdd(BuildKey(hash), k => new DotvvmCachedItem(cacheData, slidingExpiration: CacheLifetime)); + cacheAdapter.GetOrAdd(BuildKey(hash), k => new DotvvmCachedItem(cacheData, slidingExpiration: CacheLifetime)); } private static string BuildKey(string hash) From e8c5b3fb2188053cd017fb41ed578df5e68898ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Tue, 27 Aug 2019 21:19:41 +0200 Subject: [PATCH 06/16] Server-side viewmodel caching options extracted to a separate class --- .../Serialization/InMemoryViewModelServerStore.cs | 9 +++++---- .../ViewModelServerCacheConfiguration.cs | 13 +++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 src/DotVVM.Framework/ViewModel/Serialization/ViewModelServerCacheConfiguration.cs diff --git a/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs b/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs index 2b86cf9493..d907b81ca8 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/InMemoryViewModelServerStore.cs @@ -1,18 +1,19 @@ using System; using System.Threading.Tasks; using DotVVM.Framework.Runtime.Caching; +using Microsoft.Extensions.Options; namespace DotVVM.Framework.ViewModel.Serialization { public class InMemoryViewModelServerStore : IViewModelServerStore { private readonly IDotvvmCacheAdapter cacheAdapter; + private readonly IOptions cacheConfigurationOptions; - public TimeSpan CacheLifetime { get; set; } = TimeSpan.FromMinutes(5); - - public InMemoryViewModelServerStore(IDotvvmCacheAdapter cacheAdapter) + public InMemoryViewModelServerStore(IDotvvmCacheAdapter cacheAdapter, IOptions cacheConfigurationOptions) { this.cacheAdapter = cacheAdapter; + this.cacheConfigurationOptions = cacheConfigurationOptions; } public byte[] Retrieve(string hash) @@ -22,7 +23,7 @@ public byte[] Retrieve(string hash) public void Store(string hash, byte[] cacheData) { - cacheAdapter.GetOrAdd(BuildKey(hash), k => new DotvvmCachedItem(cacheData, slidingExpiration: CacheLifetime)); + cacheAdapter.GetOrAdd(BuildKey(hash), k => new DotvvmCachedItem(cacheData, DotvvmCacheItemPriority.Low, slidingExpiration: cacheConfigurationOptions.Value.CacheLifetime)); } private static string BuildKey(string hash) diff --git a/src/DotVVM.Framework/ViewModel/Serialization/ViewModelServerCacheConfiguration.cs b/src/DotVVM.Framework/ViewModel/Serialization/ViewModelServerCacheConfiguration.cs new file mode 100644 index 0000000000..de7d7f27a5 --- /dev/null +++ b/src/DotVVM.Framework/ViewModel/Serialization/ViewModelServerCacheConfiguration.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace DotVVM.Framework.ViewModel.Serialization +{ + public class ViewModelServerCacheConfiguration + { + + public TimeSpan CacheLifetime { get; set; } = TimeSpan.FromMinutes(5); + + } +} From 4b62348dd126e9f3a02e2ecea8e46a554a9a961b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sat, 9 Nov 2019 18:45:31 +0100 Subject: [PATCH 07/16] UI tests for viewmodel cache miss added Tiny updates in DotVVM.ts script --- .../Resources/Scripts/DotVVM.js | 216 ++++++++++++------ .../Resources/Scripts/DotVVM.min.js | 2 +- .../Resources/Scripts/DotVVM.ts | 8 +- .../DotVVM.Samples.Common.csproj | 1 + .../ViewModelCacheMissViewModel.cs | 19 ++ .../ViewModelCache/ViewModelCacheMiss.dothtml | 34 +++ .../Feature/ViewModelCacheTests.cs | 47 ++++ .../SamplesRouteUrls.designer.cs | 3 +- 8 files changed, 257 insertions(+), 73 deletions(-) create mode 100644 src/DotVVM.Samples.Common/ViewModels/FeatureSamples/ViewModelCache/ViewModelCacheMissViewModel.cs create mode 100644 src/DotVVM.Samples.Common/Views/FeatureSamples/ViewModelCache/ViewModelCacheMiss.dothtml create mode 100644 src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js index 94d0e450f4..8e279152ef 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js @@ -1040,6 +1040,7 @@ var DotVVM = /** @class */ (function () { this.extensions = {}; this.isPostbackRunning = ko.observable(false); this.updateProgressChangeCounter = ko.observable(0); + this.diffEqual = {}; } DotVVM.prototype.createWindowSetTimeoutHandler = function (time) { return { @@ -1071,6 +1072,10 @@ var DotVVM = /** @class */ (function () { thisViewModel.renderedResources.forEach(function (r) { return _this.resourceSigns[r] = true; }); } var idFragment = thisViewModel.resultIdFragment; + // store server-side cached viewmodel + if (thisViewModel.viewModelCacheId) { + thisViewModel.viewModelCache = this.viewModels[viewModelName].viewModel; + } var viewModel = thisViewModel.viewModel = this.serialization.deserialize(this.viewModels[viewModelName].viewModel, {}, true); // initialize services this.culture = culture; @@ -1395,7 +1400,7 @@ var DotVVM = /** @class */ (function () { DotVVM.prototype.postbackCore = function (options, path, command, controlUniqueId, context, commandArgs) { var _this = this; return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { - var viewModelName, viewModel, err_2, data; + var viewModelName, viewModel, err_2, data, completeViewModel, errorAction; var _this = this; return __generator(this, function (_a) { switch (_a.label) { @@ -1418,7 +1423,6 @@ var DotVVM = /** @class */ (function () { // perform the postback this.updateDynamicPathFragments(context, path); data = { - viewModel: this.serialization.serialize(viewModel, { pathMatcher: function (val) { return context && val == context.$data; } }), currentPath: path, command: command, controlUniqueId: this.processPassedId(controlUniqueId, context), @@ -1426,68 +1430,16 @@ var DotVVM = /** @class */ (function () { renderedResources: this.viewModels[viewModelName].renderedResources, commandArgs: commandArgs }; - this.postJSON(this.viewModels[viewModelName].url, "POST", ko.toJSON(data), function (result) { - dotvvm.events.postbackResponseReceived.trigger({}); - resolve(function () { return new Promise(function (resolve, reject) { - dotvvm.events.postbackCommitInvoked.trigger({}); - var locationHeader = result.getResponseHeader("Location"); - var resultObject = locationHeader != null && locationHeader.length > 0 ? - { action: "redirect", url: locationHeader } : - JSON.parse(result.responseText); - if (!resultObject.viewModel && resultObject.viewModelDiff) { - // TODO: patch (~deserialize) it to ko.observable viewModel - resultObject.viewModel = _this.patch(data.viewModel, resultObject.viewModelDiff); - } - _this.loadResourceList(resultObject.resources, function () { - var isSuccess = false; - if (resultObject.action === "successfulCommand") { - try { - _this.isViewModelUpdating = true; - // remove updated controls - var updatedControls = _this.cleanUpdatedControls(resultObject); - // update the viewmodel - if (resultObject.viewModel) { - ko.delaySync.pause(); - _this.serialization.deserialize(resultObject.viewModel, _this.viewModels[viewModelName].viewModel); - ko.delaySync.resume(); - } - isSuccess = true; - // remove updated controls which were previously hidden - _this.cleanUpdatedControls(resultObject, updatedControls); - // add updated controls - _this.restoreUpdatedControls(resultObject, updatedControls, true); - } - finally { - _this.isViewModelUpdating = false; - } - dotvvm.events.postbackViewModelUpdated.trigger({}); - } - else if (resultObject.action === "redirect") { - // redirect - _this.handleRedirect(resultObject, viewModelName); - return resolve(); - } - var idFragment = resultObject.resultIdFragment; - if (idFragment) { - if (_this.getSpaPlaceHolder() || location.hash == "#" + idFragment) { - var element = document.getElementById(idFragment); - if (element && "function" == typeof element.scrollIntoView) - element.scrollIntoView(true); - } - else - location.hash = idFragment; - } - // trigger afterPostback event - if (!isSuccess) { - reject(new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, result, options.postbackId, resultObject)); - } - else { - var afterPostBackArgs = new DotvvmAfterPostBackEventArgs(options, resultObject, resultObject.commandResult, result); - resolve(afterPostBackArgs); - } - }); - }); }); - }, function (xhr) { + completeViewModel = this.serialization.serialize(viewModel, { pathMatcher: function (val) { return context && val == context.$data; } }); + // if the viewmodel is cached on the server, send only the diff + if (this.viewModels[viewModelName].viewModelCache) { + data.viewModelDiff = this.diff(this.viewModels[viewModelName].viewModelCache, completeViewModel); + data.viewModelCacheId = this.viewModels[viewModelName].viewModelCacheId; + } + else { + data.viewModel = completeViewModel; + } + errorAction = function (xhr) { if (/^application\/json(;|$)/.test(xhr.getResponseHeader("Content-Type"))) { var errObject = JSON.parse(xhr.responseText); if (errObject.action === "invalidCsrfToken") { @@ -1499,7 +1451,100 @@ var DotVVM = /** @class */ (function () { } } reject({ type: 'network', options: options, args: new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, xhr, options.postbackId) }); - }); + }; + this.postJSON(this.viewModels[viewModelName].url, "POST", ko.toJSON(data), function (result) { + var resultObject = {}; + var successAction = function (actualResult) { + dotvvm.events.postbackResponseReceived.trigger({}); + resolve(function () { return new Promise(function (resolve, reject) { + dotvvm.events.postbackCommitInvoked.trigger({}); + if (!resultObject.viewModel && resultObject.viewModelDiff) { + // TODO: patch (~deserialize) it to ko.observable viewModel + resultObject.viewModel = _this.patch(completeViewModel, resultObject.viewModelDiff); + } + _this.loadResourceList(resultObject.resources, function () { + var isSuccess = false; + if (resultObject.action === "successfulCommand") { + try { + _this.isViewModelUpdating = true; + // store server-side cached viewmodel + if (resultObject.viewModelCacheId) { + _this.viewModels[viewModelName].viewModelCacheId = resultObject.viewModelCacheId; + _this.viewModels[viewModelName].viewModelCache = resultObject.viewModel; + } + else { + delete _this.viewModels[viewModelName].viewModelCacheId; + delete _this.viewModels[viewModelName].viewModelCache; + } + // remove updated controls + var updatedControls = _this.cleanUpdatedControls(resultObject); + // update the viewmodel + if (resultObject.viewModel) { + ko.delaySync.pause(); + _this.serialization.deserialize(resultObject.viewModel, _this.viewModels[viewModelName].viewModel); + ko.delaySync.resume(); + } + isSuccess = true; + // remove updated controls which were previously hidden + _this.cleanUpdatedControls(resultObject, updatedControls); + // add updated controls + _this.restoreUpdatedControls(resultObject, updatedControls, true); + } + finally { + _this.isViewModelUpdating = false; + } + dotvvm.events.postbackViewModelUpdated.trigger({}); + } + else if (resultObject.action === "redirect") { + // redirect + _this.handleRedirect(resultObject, viewModelName); + return resolve(); + } + var idFragment = resultObject.resultIdFragment; + if (idFragment) { + if (_this.getSpaPlaceHolder() || location.hash == "#" + idFragment) { + var element = document.getElementById(idFragment); + if (element && "function" == typeof element.scrollIntoView) + element.scrollIntoView(true); + } + else + location.hash = idFragment; + } + // trigger afterPostback event + if (!isSuccess) { + reject(new DotvvmErrorEventArgs(options.sender, viewModel, viewModelName, actualResult, options.postbackId, resultObject)); + } + else { + var afterPostBackArgs = new DotvvmAfterPostBackEventArgs(options, resultObject, resultObject.commandResult, result); + resolve(afterPostBackArgs); + } + }); + }); }); + }; + var parseResultObject = function (actualResult) { + var locationHeader = actualResult.getResponseHeader("Location"); + resultObject = locationHeader != null && locationHeader.length > 0 ? + { action: "redirect", url: locationHeader } : + JSON.parse(actualResult.responseText); + }; + parseResultObject(result); + if (resultObject.action === "viewModelNotCached") { + // repeat request with full viewModel + delete _this.viewModels[viewModelName].viewModelCache; + delete _this.viewModels[viewModelName].viewModelCacheId; + delete data.viewModelDiff; + delete data.viewModelCacheId; + data.viewModel = completeViewModel; + return _this.postJSON(_this.viewModels[viewModelName].url, "POST", ko.toJSON(data), function (result2) { + parseResultObject(result2); + successAction(result2); + }, errorAction); + } + else { + // process the response + successAction(result); + } + }, errorAction); return [2 /*return*/]; } }); @@ -1680,6 +1725,13 @@ var DotVVM = /** @class */ (function () { _this.viewModels[viewModelName][p] = resultObject[p]; } } + // store server-side cached viewmodel + if (resultObject.viewModelCacheId) { + _this.viewModels[viewModelName].viewModelCache = resultObject.viewModel; + } + else { + delete _this.viewModels[viewModelName].viewModelCache; + } ko.delaySync.pause(); _this.serialization.deserialize(resultObject.viewModel, _this.viewModels[viewModelName].viewModel); ko.delaySync.resume(); @@ -1819,6 +1871,40 @@ var DotVVM = /** @class */ (function () { return patch; return source; }; + DotVVM.prototype.diff = function (source, modified) { + var _this = this; + if (source instanceof Array && modified instanceof Array) { + return modified.map(function (val, i) { return _this.diff(source[i], val); }); + } + else if (source instanceof Array || modified instanceof Array) { + return modified; + } + else if (typeof source == "object" && typeof modified == "object" && source && modified) { + var result = this.diffEqual; + for (var p in modified) { + var propertyDiff = this.diff(source[p], modified[p]); + if (propertyDiff !== this.diffEqual) { + if (result == this.diffEqual) { + result = {}; + } + result[p] = propertyDiff; + } + else if (p[0] === "$") { + if (result == this.diffEqual) { + result = {}; + } + result[p] = modified[p]; + } + } + return result; + } + else if (source === modified) { + return this.diffEqual; + } + else { + return modified; + } + }; DotVVM.prototype.updateDynamicPathFragments = function (context, path) { for (var i = path.length - 1; i >= 0; i--) { if (path[i].indexOf("[$index]") >= 0) { diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js index e3fda482cf..e247f74241 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js @@ -1 +1 @@ -var __assign=this&&this.__assign||function(){return(__assign=Object.assign||function(e){for(var t,r=1,n=arguments.length;ri[0]&&t[1]'");return e},e.prototype.format=function(e){for(var o=this,a=[],t=1;t=e.length)r();else{var o=e[t],a=!1;if("script"==o.tagName.toLowerCase()){var i=o,s=document.createElement("script");i.src&&(s.src=i.src,a=!0),i.type&&(s.type=i.type),i.text&&(s.text=i.text),o.id&&(s.id=o.id),o=s}else if("link"==o.tagName.toLowerCase()){var l=o,u=document.createElement("link");l.href&&(u.href=l.href),l.rel&&(u.rel=l.rel),l.type&&(u.type=l.type),o=u}a&&(o.addEventListener("load",function(){return n.loadResourceElements(e,t+1,r)}),o.addEventListener("error",function(){return n.loadResourceElements(e,t+1,r)})),document.head.appendChild(o),a||this.loadResourceElements(e,t+1,r)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(i,e,t){var s=this,r=this.viewModels[i].viewModel,n=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(r,i,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var a=this.viewModels[i].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),u=this.addLeadingSlash(this.concatUrl(a,l)),d=this.getSpaPlaceHolder();if(d){t&&t(this.addLeadingSlash(this.concatUrl(a,this.addLeadingSlash(e))));var v=d.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(u,"GET",v,function(o){if(s.isPostBackStillActive(n)){var a=JSON.parse(o.responseText);s.loadResourceList(a.resources,function(){var e=!1;if("successfulCommand"!==a.action&&a.action){if("redirect"===a.action)return void s.handleRedirect(a,i,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(a);for(var r in s.viewModels[i]={},a)a.hasOwnProperty(r)&&(s.viewModels[i][r]=a[r]);ko.delaySync.pause(),s.serialization.deserialize(a.viewModel,s.viewModels[i].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[i](s.viewModels[i].viewModel),s.restoreUpdatedControls(a,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var n=new DotvvmSpaNavigatedEventArgs(s.viewModels[i].viewModel,i,a,o);if(s.events.spaNavigated.trigger(n),!e&&!n.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(n)){var t=new DotvvmErrorEventArgs(void 0,r,i,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=u}},e.prototype.handleRedirect=function(e,t,r){var n;void 0===r&&(r=!1),null!=e.replace&&(r=e.replace),n=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(n="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(n="#!/"),this.fixSpaUrlPrefix(n)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,n,r);this.events.redirect.trigger(o),this.performRedirect(n,r,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,r){if(t)location.replace(e);else if(r)this.handleSpaNavigationCore(e);else{var n=this.fakeRedirectAnchor;n||((n=document.createElement("a")).style.display="none",n.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(n),this.fakeRedirectAnchor=n),n.href=e,n.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var r=t.value;return r!==document.location.pathname&&(""===r&&(r="/"),e=r+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var r="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(r)?this.addLeadingSlash(e.substring(r.length)):e},e.prototype.addLeadingSlash=function(e){return 0a[0]&&t[1]'");return e},e.prototype.format=function(e){for(var o=this,i=[],t=1;t=e.length)r();else{var o=e[t],i=!1;if("script"==o.tagName.toLowerCase()){var a=o,s=document.createElement("script");a.src&&(s.src=a.src,i=!0),a.type&&(s.type=a.type),a.text&&(s.text=a.text),o.id&&(s.id=o.id),o=s}else if("link"==o.tagName.toLowerCase()){var l=o,u=document.createElement("link");l.href&&(u.href=l.href),l.rel&&(u.rel=l.rel),l.type&&(u.type=l.type),o=u}i&&(o.addEventListener("load",function(){return n.loadResourceElements(e,t+1,r)}),o.addEventListener("error",function(){return n.loadResourceElements(e,t+1,r)})),document.head.appendChild(o),i||this.loadResourceElements(e,t+1,r)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(a,e,t){var s=this,r=this.viewModels[a].viewModel,n=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(r,a,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var i=this.viewModels[a].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),u=this.addLeadingSlash(this.concatUrl(i,l)),d=this.getSpaPlaceHolder();if(d){t&&t(this.addLeadingSlash(this.concatUrl(i,this.addLeadingSlash(e))));var v=d.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(u,"GET",v,function(o){if(s.isPostBackStillActive(n)){var i=JSON.parse(o.responseText);s.loadResourceList(i.resources,function(){var e=!1;if("successfulCommand"!==i.action&&i.action){if("redirect"===i.action)return void s.handleRedirect(i,a,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(i);for(var r in s.viewModels[a]={},i)i.hasOwnProperty(r)&&(s.viewModels[a][r]=i[r]);i.viewModelCacheId?s.viewModels[a].viewModelCache=i.viewModel:delete s.viewModels[a].viewModelCache,ko.delaySync.pause(),s.serialization.deserialize(i.viewModel,s.viewModels[a].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[a](s.viewModels[a].viewModel),s.restoreUpdatedControls(i,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var n=new DotvvmSpaNavigatedEventArgs(s.viewModels[a].viewModel,a,i,o);if(s.events.spaNavigated.trigger(n),!e&&!n.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(n)){var t=new DotvvmErrorEventArgs(void 0,r,a,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=u}},e.prototype.handleRedirect=function(e,t,r){var n;void 0===r&&(r=!1),null!=e.replace&&(r=e.replace),n=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(n="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(n="#!/"),this.fixSpaUrlPrefix(n)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,n,r);this.events.redirect.trigger(o),this.performRedirect(n,r,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,r){if(t)location.replace(e);else if(r)this.handleSpaNavigationCore(e);else{var n=this.fakeRedirectAnchor;n||((n=document.createElement("a")).style.display="none",n.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(n),this.fakeRedirectAnchor=n),n.href=e,n.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var r=t.value;return r!==document.location.pathname&&(""===r&&(r="/"),e=r+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var r="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(r)?this.addLeadingSlash(e.substring(r.length)):e},e.prototype.addLeadingSlash=function(e){return 0 new Promise((resolve, reject) => { dotvvm.events.postbackCommitInvoked.trigger({}) - const locationHeader = result.getResponseHeader("Location"); - - resultObject = locationHeader != null && locationHeader.length > 0 ? - { action: "redirect", url: locationHeader } : - JSON.parse(result.responseText); + if (!resultObject.viewModel && resultObject.viewModelDiff) { // TODO: patch (~deserialize) it to ko.observable viewModel resultObject.viewModel = this.patch(completeViewModel, resultObject.viewModelDiff); @@ -704,7 +700,7 @@ class DotVVM { delete data.viewModelCacheId; data.viewModel = completeViewModel; - this.postJSON(this.viewModels[viewModelName].url, "POST", ko.toJSON(data), result2 => { + return this.postJSON(this.viewModels[viewModelName].url, "POST", ko.toJSON(data), result2 => { parseResultObject(result2); successAction(result2); }, errorAction); diff --git a/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj b/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj index 1f01332c32..568cabf478 100644 --- a/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj +++ b/src/DotVVM.Samples.Common/DotVVM.Samples.Common.csproj @@ -58,6 +58,7 @@ + diff --git a/src/DotVVM.Samples.Common/ViewModels/FeatureSamples/ViewModelCache/ViewModelCacheMissViewModel.cs b/src/DotVVM.Samples.Common/ViewModels/FeatureSamples/ViewModelCache/ViewModelCacheMissViewModel.cs new file mode 100644 index 0000000000..a5dcffe6db --- /dev/null +++ b/src/DotVVM.Samples.Common/ViewModels/FeatureSamples/ViewModelCache/ViewModelCacheMissViewModel.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; +using DotVVM.Framework.ViewModel; + +namespace DotVVM.Samples.Common.ViewModels.FeatureSamples.ViewModelCache +{ + public class ViewModelCacheMissViewModel : DotvvmViewModelBase + { + + public int Value { get; set; } + + public void Increment() + { + Value++; + } + + } +} diff --git a/src/DotVVM.Samples.Common/Views/FeatureSamples/ViewModelCache/ViewModelCacheMiss.dothtml b/src/DotVVM.Samples.Common/Views/FeatureSamples/ViewModelCache/ViewModelCacheMiss.dothtml new file mode 100644 index 0000000000..2dcd55cadf --- /dev/null +++ b/src/DotVVM.Samples.Common/Views/FeatureSamples/ViewModelCache/ViewModelCacheMiss.dothtml @@ -0,0 +1,34 @@ +@viewModel DotVVM.Samples.Common.ViewModels.FeatureSamples.ViewModelCache.ViewModelCacheMissViewModel, DotVVM.Samples.Common + + + + + + + + + + +

ViewModel - Server-Side cache - simulate missing entry

+ +

Value: {{value: Value}}

+

Requests:

+ + + + + + + var RequestCount = ko.observable(0); + + var origPostJSON = dotvvm.postJSON; + dotvvm.postJSON = function() { + RequestCount(RequestCount() + 1); + origPostJSON.apply(this, arguments); + }; + + + + + + diff --git a/src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs b/src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs new file mode 100644 index 0000000000..7013cb18b7 --- /dev/null +++ b/src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DotVVM.Samples.Tests.Base; +using DotVVM.Testing.Abstractions; +using Riganti.Selenium.Core; +using Xunit; +using Xunit.Abstractions; + +namespace DotVVM.Samples.Tests.Feature +{ + public class ViewModelCacheTests : AppSeleniumTest + { + public ViewModelCacheTests(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void Feature_ViewModelCache_ViewModelCacheMiss() + { + RunInAllBrowsers(browser => { + browser.NavigateToUrl(SamplesRouteUrls.FeatureSamples_ViewModelCache_ViewModelCacheMiss); + + var result = browser.Single(".result"); + var requestCount = browser.Single(".requestCount"); + AssertUI.TextEquals(result, "0"); + AssertUI.TextEquals(requestCount, "0"); + + // normal postback + browser.ElementAt("input[type=button]", 0).Click().Wait(); + AssertUI.TextEquals(result, "1"); + AssertUI.TextEquals(requestCount, "1"); + + // tamper with viewmodel cache id - it should do two requests but it should still work + browser.ElementAt("input[type=button]", 1).Click().Wait(); + browser.ElementAt("input[type=button]", 0).Click().Wait(); + AssertUI.TextEquals(result, "2"); + AssertUI.TextEquals(requestCount, "3"); + + // normal postback + browser.ElementAt("input[type=button]", 0).Click().Wait(); + AssertUI.TextEquals(result, "3"); + AssertUI.TextEquals(requestCount, "4"); + }); + } + } +} diff --git a/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs b/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs index ceb768b842..32720ed2ec 100644 --- a/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs +++ b/src/DotVVM.Testing.Abstractions/SamplesRouteUrls.designer.cs @@ -252,7 +252,6 @@ public partial class SamplesRouteUrls public const string FeatureSamples_StaticCommand_StaticCommand_ValueAssignmentInControl = "FeatureSamples/StaticCommand/StaticCommand_ValueAssignmentInControl"; public const string FeatureSamples_UsageValidation_OverrideValidation = "FeatureSamples/UsageValidation/OverrideValidation"; public const string FeatureSamples_Validation_ClientSideObservableUpdate = "FeatureSamples/Validation/ClientSideObservableUpdate"; - public const string FeatureSamples_Validation_ClientSideValidationDisabling = "FeatureSamples/Validation/ClientSideValidationDisabling"; public const string FeatureSamples_Validation_CustomValidation = "FeatureSamples/Validation/CustomValidation"; public const string FeatureSamples_Validation_DateTimeValidation = "FeatureSamples/Validation/DateTimeValidation"; public const string FeatureSamples_Validation_DateTimeValidation_NullableDateTime = "FeatureSamples/Validation/DateTimeValidation_NullableDateTime"; @@ -270,11 +269,13 @@ public partial class SamplesRouteUrls public const string FeatureSamples_Validation_ValidationRulesLoadOnPostback = "FeatureSamples/Validation/ValidationRulesLoadOnPostback"; public const string FeatureSamples_Validation_ValidationScopes = "FeatureSamples/Validation/ValidationScopes"; public const string FeatureSamples_Validation_ValidationScopes2 = "FeatureSamples/Validation/ValidationScopes2"; + public const string FeatureSamples_ViewModelCache_ViewModelCacheMiss = "FeatureSamples/ViewModelCache/ViewModelCacheMiss"; public const string FeatureSamples_ViewModelDeserialization_DoesNotDropObject = "FeatureSamples/ViewModelDeserialization/DoesNotDropObject"; public const string FeatureSamples_ViewModelDeserialization_NegativeLongNumber = "FeatureSamples/ViewModelDeserialization/NegativeLongNumber"; public const string FeatureSamples_ViewModelNesting_NestedViewModel = "FeatureSamples/ViewModelNesting/NestedViewModel"; public const string FeatureSamples_ViewModelProtection_ComplexViewModelProtection = "FeatureSamples/ViewModelProtection/ComplexViewModelProtection"; public const string FeatureSamples_ViewModelProtection_SignedNestedInServerToClient = "FeatureSamples/ViewModelProtection/SignedNestedInServerToClient"; public const string FeatureSamples_ViewModelProtection_ViewModelProtection = "FeatureSamples/ViewModelProtection/ViewModelProtection"; + public const string FeatureSamples_Warnings_SelfClosingTags = "FeatureSamples/Warnings/SelfClosingTags"; } } From 0c5c91a19a8dc1bc435fb61b1a161a25b62be90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sun, 10 Nov 2019 18:18:33 +0100 Subject: [PATCH 08/16] ViewModel diffing bug fixed Js and C# tests for JSON diff & patch implemented --- .../ViewModel/JsonPatchTests.cs | 81 + .../package-lock.json | 1572 ++++++++--------- .../tests/DotVVM.Events.Tests.d.ts | 2 +- .../tests/DotVVM.diff.Tests.d.ts | 4 + .../tests/DotVVM.diff.Tests.ts | 66 + .../Resources/Scripts/DotVVM.js | 20 +- .../Resources/Scripts/DotVVM.min.js | 2 +- .../Resources/Scripts/DotVVM.ts | 18 +- src/DotVVM.Framework/Utils/JsonUtils.cs | 1 - .../Serialization/DotvvmDateTimeConverter.cs | 14 +- 10 files changed, 953 insertions(+), 827 deletions(-) create mode 100644 src/DotVVM.Framework.Tests.Common/ViewModel/JsonPatchTests.cs create mode 100644 src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.diff.Tests.d.ts create mode 100644 src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.diff.Tests.ts diff --git a/src/DotVVM.Framework.Tests.Common/ViewModel/JsonPatchTests.cs b/src/DotVVM.Framework.Tests.Common/ViewModel/JsonPatchTests.cs new file mode 100644 index 0000000000..1adad1b0e9 --- /dev/null +++ b/src/DotVVM.Framework.Tests.Common/ViewModel/JsonPatchTests.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using DotVVM.Framework.Utils; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json.Linq; + +namespace DotVVM.Framework.Tests.Common.ViewModel +{ + [TestClass] + public class JsonPatchTests + { + + [DataTestMethod] + [DataRow(null, "['a', 'b']")] + [DataRow("['a', 'd']", "['a', 'd']")] + [DataRow("['a', 'b', 'c']", "['a', 'b', 'c']")] + [DataRow("['a']", "['a']")] + public void JsonPatch_Arrays_Primitive(string modifiedJson, string resultJson) + { + var original = JObject.Parse(WrapArray("['a', 'b']")); + var modified = JObject.Parse(WrapArray(modifiedJson)); + var result = JObject.Parse(WrapArray(resultJson)); + JsonUtils.Patch(original, modified); + Assert.IsTrue(JToken.DeepEquals(original, result)); + } + + [DataTestMethod] + [DataRow("{ }", "{ 'a': 1, 'b': 'a' }")] + [DataRow("{ 'a': 2 }", "{ 'a': 2, 'b': 'a' }")] + [DataRow("{ 'a': 2, c: null }", "{ 'a': 2, 'b': 'a', 'c': null }")] + [DataRow("{ 'a': 2 }", "{ 'a': 2, 'b': 'a' }")] + public void JsonPatch_Objects(string modifiedJson, string resultJson) + { + var original = JObject.Parse("{ 'a': 1, 'b': 'a' }"); + var modified = JObject.Parse(modifiedJson); + var result = JObject.Parse(resultJson); + JsonUtils.Patch(original, modified); + Assert.IsTrue(JToken.DeepEquals(original, result)); + } + + [DataTestMethod] + [DataRow("{ }", "{ 'a': 1, 'bparent': { 'b': 'a', 'c': 1 } }")] + [DataRow("{ 'a': 2, 'b': 'a' }", "{ 'a': 2, 'b': 'a', 'bparent': { 'b': 'a', 'c': 1 } }")] + [DataRow("{ 'bparent': { 'c': 2 } }", "{ 'a': 1, 'bparent': { 'b': 'a', 'c': 2 } }")] + [DataRow("{ 'bparent': { 'c': 2, 'd': 3 } }", "{ 'a': 1, 'bparent': { 'b': 'a', 'c': 2, 'd': 3 } }")] + [DataRow("{ 'bparent': { 'b': 'b' } }", "{ 'a': 1, 'bparent': { 'b': 'b', 'c': 1 } }")] + public void JsonPatch_Objects_Nested(string modifiedJson, string resultJson) + { + var original = JObject.Parse("{ 'a': 1, 'bparent': { 'b': 'a', 'c': 1 } }"); + var modified = JObject.Parse(modifiedJson); + var result = JObject.Parse(resultJson); + JsonUtils.Patch(original, modified); + Assert.IsTrue(JToken.DeepEquals(original, result)); + } + + [DataTestMethod] + [DataRow(null, "[{ 'a': 1 }, { 'a': 2 }]")] + [DataRow("[{ 'a': 3 }, {}]", "[{ 'a': 3 }, { 'a': 2 }]")] + [DataRow("[{}, {}, { 'a': 3 }]", "[{ 'a': 1 }, { 'a': 2 }, { 'a': 3 }]")] + [DataRow("[{ 'a': 2 }]", "[{ 'a': 2 }]")] + [DataRow("[{}]", "[{ 'a': 1 }]")] + public void JsonPatch_ObjectArray(string modifiedJson, string resultJson) + { + var original = JObject.Parse(WrapArray("[{ 'a': 1 }, { 'a': 2 }]")); + var modified = JObject.Parse(WrapArray(modifiedJson)); + var result = JObject.Parse(WrapArray(resultJson)); + JsonUtils.Patch(original, modified); + Assert.IsTrue(JToken.DeepEquals(original, result)); + } + + private string WrapArray(string a) + { + if (a == null) + { + return "{}"; + } + + return "{'array': " + a + "}"; + } + } +} diff --git a/src/DotVVM.Framework.Tests.Javascript/package-lock.json b/src/DotVVM.Framework.Tests.Javascript/package-lock.json index c59f4f705a..649d20d92e 100644 --- a/src/DotVVM.Framework.Tests.Javascript/package-lock.json +++ b/src/DotVVM.Framework.Tests.Javascript/package-lock.json @@ -10,8 +10,8 @@ "integrity": "sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg==", "dev": true, "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "jsonparse": "1.3.1", + "through": "2.3.8" } }, "accepts": { @@ -20,7 +20,7 @@ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "dev": true, "requires": { - "mime-types": "~2.1.18", + "mime-types": "2.1.19", "negotiator": "0.6.1" } }, @@ -36,7 +36,7 @@ "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", "dev": true, "requires": { - "acorn": "^5.0.0" + "acorn": "5.7.1" } }, "acorn-node": { @@ -45,9 +45,9 @@ "integrity": "sha512-krFKvw/d1F17AN3XZbybIUzEY4YEPNiGo05AfP3dBlfVKrMHETKpgjpuZkSF8qDNt9UkQcqj7am8yJLseklCMg==", "dev": true, "requires": { - "acorn": "^5.7.1", - "acorn-dynamic-import": "^3.0.0", - "xtend": "^4.0.1" + "acorn": "5.7.1", + "acorn-dynamic-import": "3.0.0", + "xtend": "4.0.1" } }, "addressparser": { @@ -68,9 +68,8 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", "dev": true, - "optional": true, "requires": { - "es6-promisify": "^5.0.0" + "es6-promisify": "5.0.0" } }, "ajv": { @@ -80,10 +79,10 @@ "dev": true, "optional": true, "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "amqplib": { @@ -93,11 +92,11 @@ "dev": true, "optional": true, "requires": { - "bitsyntax": "~0.0.4", - "bluebird": "^3.4.6", + "bitsyntax": "0.0.4", + "bluebird": "3.5.1", "buffer-more-ints": "0.0.2", - "readable-stream": "1.x >=1.1.9", - "safe-buffer": "^5.0.1" + "readable-stream": "1.1.14", + "safe-buffer": "5.1.2" }, "dependencies": { "isarray": { @@ -114,10 +113,10 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "string_decoder": { @@ -133,8 +132,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true + "dev": true }, "ansi-styles": { "version": "2.2.1", @@ -149,8 +147,8 @@ "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", "dev": true, "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" + "micromatch": "2.3.11", + "normalize-path": "2.1.1" } }, "arr-diff": { @@ -159,7 +157,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "arr-flatten": { @@ -211,7 +209,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "~2.1.0" + "safer-buffer": "2.1.2" } }, "asn1.js": { @@ -220,9 +218,9 @@ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "dev": true, "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "bn.js": "4.11.8", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" } }, "assert": { @@ -255,8 +253,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true + "dev": true }, "ast-types": { "version": "0.11.5", @@ -272,7 +269,7 @@ "dev": true, "optional": true, "requires": { - "lodash": "^4.17.10" + "lodash": "4.17.15" } }, "async-each": { @@ -325,7 +322,7 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.2.0" + "debug": "2.6.9" } } } @@ -367,7 +364,7 @@ "dev": true, "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "better-assert": { @@ -402,7 +399,7 @@ "dev": true, "optional": true, "requires": { - "readable-stream": "~2.0.5" + "readable-stream": "2.0.6" }, "dependencies": { "process-nextick-args": { @@ -419,12 +416,12 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -461,15 +458,15 @@ "dev": true, "requires": { "bytes": "3.0.0", - "content-type": "~1.0.4", + "content-type": "1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "~1.6.3", + "depd": "1.1.2", + "http-errors": "1.6.3", "iconv-lite": "0.4.23", - "on-finished": "~2.3.0", + "on-finished": "2.3.0", "qs": "6.5.2", "raw-body": "2.3.3", - "type-is": "~1.6.16" + "type-is": "1.6.16" } }, "boom": { @@ -477,9 +474,8 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", "dev": true, - "optional": true, "requires": { - "hoek": "2.x.x" + "hoek": "2.16.3" } }, "brace-expansion": { @@ -488,7 +484,7 @@ "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", "dev": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -498,9 +494,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.3" } }, "brorand": { @@ -515,12 +511,12 @@ "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", "dev": true, "requires": { - "JSONStream": "^1.0.3", - "combine-source-map": "~0.8.0", - "defined": "^1.0.0", - "safe-buffer": "^5.1.1", - "through2": "^2.0.0", - "umd": "^3.0.0" + "JSONStream": "1.3.4", + "combine-source-map": "0.8.0", + "defined": "1.0.0", + "safe-buffer": "5.1.2", + "through2": "2.0.3", + "umd": "3.0.3" } }, "browser-resolve": { @@ -546,53 +542,53 @@ "integrity": "sha512-gKfOsNQv/toWz+60nSPfYzuwSEdzvV2WdxrVPUbPD/qui44rAkB3t3muNtmmGYHqrG56FGwX9SUEQmzNLAeS7g==", "dev": true, "requires": { - "JSONStream": "^1.0.3", - "assert": "^1.4.0", - "browser-pack": "^6.0.1", - "browser-resolve": "^1.11.0", - "browserify-zlib": "~0.2.0", - "buffer": "^5.0.2", - "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.1", - "console-browserify": "^1.1.0", - "constants-browserify": "~1.0.0", - "crypto-browserify": "^3.0.0", - "defined": "^1.0.0", - "deps-sort": "^2.0.0", - "domain-browser": "~1.1.0", - "duplexer2": "~0.1.2", - "events": "~1.1.0", - "glob": "^7.1.0", - "has": "^1.0.0", - "htmlescape": "^1.1.0", - "https-browserify": "^1.0.0", - "inherits": "~2.0.1", - "insert-module-globals": "^7.0.0", - "labeled-stream-splicer": "^2.0.0", - "module-deps": "^4.0.8", - "os-browserify": "~0.3.0", - "parents": "^1.0.1", - "path-browserify": "~0.0.0", - "process": "~0.11.0", - "punycode": "^1.3.2", - "querystring-es3": "~0.2.0", - "read-only-stream": "^2.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.4", - "shasum": "^1.0.0", - "shell-quote": "^1.6.1", - "stream-browserify": "^2.0.0", - "stream-http": "^2.0.0", - "string_decoder": "~1.0.0", - "subarg": "^1.0.0", - "syntax-error": "^1.1.1", - "through2": "^2.0.0", - "timers-browserify": "^1.0.1", - "tty-browserify": "~0.0.0", - "url": "~0.11.0", - "util": "~0.10.1", - "vm-browserify": "~0.0.1", - "xtend": "^4.0.0" + "JSONStream": "1.3.4", + "assert": "1.4.1", + "browser-pack": "6.1.0", + "browser-resolve": "1.11.3", + "browserify-zlib": "0.2.0", + "buffer": "5.2.0", + "cached-path-relative": "1.0.1", + "concat-stream": "1.5.2", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.12.0", + "defined": "1.0.0", + "deps-sort": "2.0.0", + "domain-browser": "1.1.7", + "duplexer2": "0.1.4", + "events": "1.1.1", + "glob": "7.1.2", + "has": "1.0.3", + "htmlescape": "1.1.1", + "https-browserify": "1.0.0", + "inherits": "2.0.3", + "insert-module-globals": "7.2.0", + "labeled-stream-splicer": "2.0.1", + "module-deps": "4.1.1", + "os-browserify": "0.3.0", + "parents": "1.0.1", + "path-browserify": "0.0.1", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "read-only-stream": "2.0.0", + "readable-stream": "2.3.6", + "resolve": "1.8.1", + "shasum": "1.0.2", + "shell-quote": "1.6.1", + "stream-browserify": "2.0.1", + "stream-http": "2.8.3", + "string_decoder": "1.0.3", + "subarg": "1.0.0", + "syntax-error": "1.4.0", + "through2": "2.0.3", + "timers-browserify": "1.4.2", + "tty-browserify": "0.0.1", + "url": "0.11.0", + "util": "0.10.4", + "vm-browserify": "0.0.4", + "xtend": "4.0.1" } }, "browserify-aes": { @@ -601,12 +597,12 @@ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "buffer-xor": "1.0.3", + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "inherits": "2.0.3", + "safe-buffer": "5.1.2" } }, "browserify-cipher": { @@ -615,9 +611,9 @@ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "dev": true, "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" + "browserify-aes": "1.2.0", + "browserify-des": "1.0.2", + "evp_bytestokey": "1.0.3" } }, "browserify-des": { @@ -626,10 +622,10 @@ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "dev": true, "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "cipher-base": "1.0.4", + "des.js": "1.0.0", + "inherits": "2.0.3", + "safe-buffer": "5.1.2" } }, "browserify-rsa": { @@ -638,8 +634,8 @@ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" + "bn.js": "4.11.8", + "randombytes": "2.0.6" } }, "browserify-sign": { @@ -648,13 +644,13 @@ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", "dev": true, "requires": { - "bn.js": "^4.1.1", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.2", - "elliptic": "^6.0.0", - "inherits": "^2.0.1", - "parse-asn1": "^5.0.0" + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "elliptic": "6.4.1", + "inherits": "2.0.3", + "parse-asn1": "5.1.1" } }, "browserify-zlib": { @@ -663,7 +659,7 @@ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dev": true, "requires": { - "pako": "~1.0.5" + "pako": "1.0.6" } }, "buffer": { @@ -672,8 +668,8 @@ "integrity": "sha512-nUJyfChH7PMJy75eRDCCKtszSEFokUNXC1hNVSe+o+VdcgvDPLs20k3v8UXI8ruRYAJiYtyRea8mYyqPxoHWDw==", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "1.3.0", + "ieee754": "1.1.12" } }, "buffer-alloc": { @@ -682,8 +678,8 @@ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "buffer-alloc-unsafe": "1.1.0", + "buffer-fill": "1.0.0" } }, "buffer-alloc-unsafe": { @@ -708,8 +704,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz", "integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=", - "dev": true, - "optional": true + "dev": true }, "buffer-xor": { "version": "1.0.3", @@ -771,11 +766,11 @@ "dev": true, "optional": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "chokidar": { @@ -784,15 +779,15 @@ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.2.4", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" } }, "cipher-base": { @@ -801,8 +796,8 @@ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.2" } }, "circular-json": { @@ -830,7 +825,7 @@ "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=", "dev": true, "requires": { - "lodash": "^4.5.0" + "lodash": "4.17.15" } }, "combine-source-map": { @@ -839,10 +834,10 @@ "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", "dev": true, "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" + "convert-source-map": "1.1.3", + "inline-source-map": "0.6.2", + "lodash.memoize": "3.0.4", + "source-map": "0.5.7" }, "dependencies": { "source-map": { @@ -858,9 +853,8 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "dev": true, - "optional": true, "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "commander": { @@ -900,9 +894,9 @@ "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=", "dev": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "~2.0.0", - "typedarray": "~0.0.5" + "inherits": "2.0.3", + "readable-stream": "2.0.6", + "typedarray": "0.0.6" }, "dependencies": { "process-nextick-args": { @@ -917,12 +911,12 @@ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -941,7 +935,7 @@ "requires": { "debug": "2.6.9", "finalhandler": "1.1.0", - "parseurl": "~1.3.2", + "parseurl": "1.3.2", "utils-merge": "1.0.1" } }, @@ -951,7 +945,7 @@ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", "dev": true, "requires": { - "date-now": "^0.1.4" + "date-now": "0.1.4" } }, "constants-browserify": { @@ -996,8 +990,8 @@ "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", "dev": true, "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.0.0" + "bn.js": "4.11.8", + "elliptic": "6.4.1" } }, "create-hash": { @@ -1006,11 +1000,11 @@ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" + "cipher-base": "1.0.4", + "inherits": "2.0.3", + "md5.js": "1.3.4", + "ripemd160": "2.0.2", + "sha.js": "2.4.11" } }, "create-hmac": { @@ -1019,12 +1013,12 @@ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "cipher-base": "1.0.4", + "create-hash": "1.2.0", + "inherits": "2.0.3", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.2", + "sha.js": "2.4.11" } }, "cryptiles": { @@ -1034,7 +1028,7 @@ "dev": true, "optional": true, "requires": { - "boom": "2.x.x" + "boom": "2.10.1" } }, "crypto-browserify": { @@ -1043,17 +1037,17 @@ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", "dev": true, "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" + "browserify-cipher": "1.0.1", + "browserify-sign": "4.0.4", + "create-ecdh": "4.0.3", + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "diffie-hellman": "5.0.3", + "inherits": "2.0.3", + "pbkdf2": "3.0.16", + "public-encrypt": "4.0.2", + "randombytes": "2.0.6", + "randomfill": "1.0.4" } }, "custom-event": { @@ -1069,7 +1063,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "data-uri-to-buffer": { @@ -1126,17 +1120,16 @@ "dev": true, "optional": true, "requires": { - "ast-types": "0.x.x", - "escodegen": "1.x.x", - "esprima": "3.x.x" + "ast-types": "0.11.5", + "escodegen": "1.11.0", + "esprima": "3.1.3" } }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "optional": true + "dev": true }, "depd": { "version": "1.1.2", @@ -1150,10 +1143,10 @@ "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", "dev": true, "requires": { - "JSONStream": "^1.0.3", - "shasum": "^1.0.0", - "subarg": "^1.0.0", - "through2": "^2.0.0" + "JSONStream": "1.3.4", + "shasum": "1.0.2", + "subarg": "1.0.0", + "through2": "2.0.3" } }, "des.js": { @@ -1162,8 +1155,8 @@ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", "dev": true, "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" } }, "detective": { @@ -1172,8 +1165,8 @@ "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", "dev": true, "requires": { - "acorn": "^5.2.1", - "defined": "^1.0.0" + "acorn": "5.7.1", + "defined": "1.0.0" } }, "di": { @@ -1188,9 +1181,9 @@ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" + "bn.js": "4.11.8", + "miller-rabin": "4.0.1", + "randombytes": "2.0.6" } }, "dom-serialize": { @@ -1199,10 +1192,10 @@ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "custom-event": "1.0.1", + "ent": "2.2.0", + "extend": "3.0.2", + "void-elements": "2.0.1" } }, "domain-browser": { @@ -1224,7 +1217,7 @@ "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", "dev": true, "requires": { - "readable-stream": "^2.0.2" + "readable-stream": "2.3.6" } }, "ecc-jsbn": { @@ -1234,8 +1227,8 @@ "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "jsbn": "0.1.1", + "safer-buffer": "2.1.2" } }, "ee-first": { @@ -1250,13 +1243,13 @@ "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "bn.js": "4.11.8", + "brorand": "1.1.0", + "hash.js": "1.1.5", + "hmac-drbg": "1.0.1", + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" } }, "encodeurl": { @@ -1271,13 +1264,13 @@ "integrity": "sha512-D06ivJkYxyRrcEe0bTpNnBQNgP9d3xog+qZlLbui8EsMr/DouQpf5o9FzJnWYHEYE0YsFHllUv2R1dkgYZXHcA==", "dev": true, "requires": { - "accepts": "~1.3.4", + "accepts": "1.3.5", "base64id": "1.0.0", "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "uws": "~9.14.0", - "ws": "~3.3.1" + "debug": "3.1.0", + "engine.io-parser": "2.1.2", + "uws": "9.14.0", + "ws": "3.3.3" }, "dependencies": { "debug": { @@ -1299,14 +1292,14 @@ "requires": { "component-emitter": "1.2.1", "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", + "debug": "3.1.0", + "engine.io-parser": "2.1.2", "has-cors": "1.1.0", "indexof": "0.0.1", "parseqs": "0.0.5", "parseuri": "0.0.5", - "ws": "~3.3.1", - "xmlhttprequest-ssl": "~1.5.4", + "ws": "3.3.3", + "xmlhttprequest-ssl": "1.5.5", "yeast": "0.1.2" }, "dependencies": { @@ -1328,10 +1321,10 @@ "dev": true, "requires": { "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", + "arraybuffer.slice": "0.0.7", "base64-arraybuffer": "0.1.5", "blob": "0.0.4", - "has-binary2": "~1.0.2" + "has-binary2": "1.0.3" } }, "ent": { @@ -1344,17 +1337,15 @@ "version": "4.2.4", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", - "dev": true, - "optional": true + "dev": true }, "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, - "optional": true, "requires": { - "es6-promise": "^4.0.3" + "es6-promise": "4.2.4" } }, "escape-html": { @@ -1377,19 +1368,18 @@ "dev": true, "optional": true, "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "esprima": "3.1.3", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.6.1" } }, "esprima": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true, - "optional": true + "dev": true }, "estraverse": { "version": "4.2.0", @@ -1423,8 +1413,8 @@ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" + "md5.js": "1.3.4", + "safe-buffer": "5.1.2" } }, "expand-braces": { @@ -1433,9 +1423,9 @@ "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=", "dev": true, "requires": { - "array-slice": "^0.2.3", - "array-unique": "^0.2.1", - "braces": "^0.1.2" + "array-slice": "0.2.3", + "array-unique": "0.2.1", + "braces": "0.1.5" }, "dependencies": { "braces": { @@ -1444,7 +1434,7 @@ "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=", "dev": true, "requires": { - "expand-range": "^0.1.0" + "expand-range": "0.1.1" } }, "expand-range": { @@ -1453,8 +1443,8 @@ "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=", "dev": true, "requires": { - "is-number": "^0.1.1", - "repeat-string": "^0.2.2" + "is-number": "0.1.1", + "repeat-string": "0.2.2" } }, "is-number": { @@ -1477,7 +1467,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "expand-range": { @@ -1486,7 +1476,7 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "^2.1.0" + "fill-range": "2.2.4" } }, "extend": { @@ -1501,15 +1491,14 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true, - "optional": true + "dev": true }, "fast-deep-equal": { "version": "1.1.0", @@ -1551,11 +1540,11 @@ "integrity": "sha1-6x53OrsFbc2N8r/favWbizqTZWU=", "dev": true, "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.1.0", + "repeat-element": "1.1.3", + "repeat-string": "1.6.1" } }, "finalhandler": { @@ -1565,12 +1554,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.1", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.2", - "statuses": "~1.3.1", - "unpipe": "~1.0.0" + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.2", + "statuses": "1.3.1", + "unpipe": "1.0.0" }, "dependencies": { "statuses": { @@ -1587,7 +1576,7 @@ "integrity": "sha512-xay/eYZGgdpb3rpugZj1HunNaPcqc6fud/RW7LNEQntvKzuRO4DDLL+MnJIbTHh6t3Kda3v2RvhY2doxUddnig==", "dev": true, "requires": { - "debug": "^3.1.0" + "debug": "3.1.0" }, "dependencies": { "debug": { @@ -1613,7 +1602,7 @@ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } }, "forever-agent": { @@ -1630,9 +1619,9 @@ "dev": true, "optional": true, "requires": { - "asynckit": "^0.4.0", + "asynckit": "0.4.0", "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "mime-types": "2.1.19" } }, "fs-access": { @@ -1641,7 +1630,7 @@ "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, "requires": { - "null-check": "^1.0.0" + "null-check": "1.0.0" } }, "fs.realpath": { @@ -1657,8 +1646,8 @@ "dev": true, "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "2.10.0", + "node-pre-gyp": "0.10.0" }, "dependencies": { "abbrev": { @@ -1670,8 +1659,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -1685,23 +1673,21 @@ "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" } }, "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -1714,20 +1700,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1768,7 +1751,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "fs.realpath": { @@ -1783,14 +1766,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" } }, "glob": { @@ -1799,12 +1782,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-unicode": { @@ -1819,7 +1802,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ignore-walk": { @@ -1828,7 +1811,7 @@ "dev": true, "optional": true, "requires": { - "minimatch": "^3.0.4" + "minimatch": "3.0.4" } }, "inflight": { @@ -1837,15 +1820,14 @@ "dev": true, "optional": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1857,9 +1839,8 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "isarray": { @@ -1872,25 +1853,22 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "minizlib": { @@ -1899,14 +1877,13 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -1930,9 +1907,9 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" } }, "node-pre-gyp": { @@ -1941,16 +1918,16 @@ "dev": true, "optional": true, "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.0", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.7", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" } }, "nopt": { @@ -1959,8 +1936,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "npm-bundled": { @@ -1975,8 +1952,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" } }, "npmlog": { @@ -1985,17 +1962,16 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -2007,9 +1983,8 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { @@ -2030,8 +2005,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "path-is-absolute": { @@ -2052,10 +2027,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -2072,13 +2047,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "rimraf": { @@ -2087,14 +2062,13 @@ "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "safe-buffer": { "version": "5.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -2130,11 +2104,10 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string_decoder": { @@ -2143,16 +2116,15 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { @@ -2167,13 +2139,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "util-deprecate": { @@ -2188,20 +2160,18 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -2212,7 +2182,7 @@ "dev": true, "optional": true, "requires": { - "readable-stream": "1.1.x", + "readable-stream": "1.1.14", "xregexp": "2.0.0" }, "dependencies": { @@ -2230,10 +2200,10 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "string_decoder": { @@ -2265,7 +2235,7 @@ "dev": true, "optional": true, "requires": { - "is-property": "^1.0.0" + "is-property": "1.0.2" } }, "get-assigned-identifiers": { @@ -2281,12 +2251,12 @@ "dev": true, "optional": true, "requires": { - "data-uri-to-buffer": "1", - "debug": "2", - "extend": "3", - "file-uri-to-path": "1", - "ftp": "~0.3.10", - "readable-stream": "2" + "data-uri-to-buffer": "1.2.0", + "debug": "2.6.9", + "extend": "3.0.2", + "file-uri-to-path": "1.0.0", + "ftp": "0.3.10", + "readable-stream": "2.3.6" } }, "getpass": { @@ -2296,7 +2266,7 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "glob": { @@ -2305,12 +2275,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-base": { @@ -2319,8 +2289,8 @@ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" + "glob-parent": "2.0.0", + "is-glob": "2.0.1" } }, "glob-parent": { @@ -2329,7 +2299,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "^2.0.0" + "is-glob": "2.0.1" } }, "graceful-fs": { @@ -2352,8 +2322,8 @@ "dev": true, "optional": true, "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has": { @@ -2362,7 +2332,7 @@ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "^1.1.1" + "function-bind": "1.1.1" } }, "has-ansi": { @@ -2372,7 +2342,7 @@ "dev": true, "optional": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "has-binary2": { @@ -2404,8 +2374,8 @@ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.2" } }, "hash.js": { @@ -2414,8 +2384,8 @@ "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==", "dev": true, "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" + "inherits": "2.0.3", + "minimalistic-assert": "1.0.1" } }, "hawk": { @@ -2425,10 +2395,10 @@ "dev": true, "optional": true, "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" } }, "hipchat-notifier": { @@ -2438,8 +2408,8 @@ "dev": true, "optional": true, "requires": { - "lodash": "^4.0.0", - "request": "^2.0.0" + "lodash": "4.17.15", + "request": "2.88.0" } }, "hmac-drbg": { @@ -2448,17 +2418,16 @@ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", "dev": true, "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" + "hash.js": "1.1.5", + "minimalistic-assert": "1.0.1", + "minimalistic-crypto-utils": "1.0.1" } }, "hoek": { "version": "2.16.3", "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true, - "optional": true + "dev": true }, "htmlescape": { "version": "1.1.1", @@ -2472,10 +2441,10 @@ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { - "depd": "~1.1.2", + "depd": "1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" + "statuses": "1.5.0" } }, "http-proxy": { @@ -2484,9 +2453,9 @@ "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", "dev": true, "requires": { - "eventemitter3": "^3.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "eventemitter3": "3.1.0", + "follow-redirects": "1.5.6", + "requires-port": "1.0.0" } }, "http-proxy-agent": { @@ -2494,9 +2463,8 @@ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", "dev": true, - "optional": true, "requires": { - "agent-base": "4", + "agent-base": "4.2.1", "debug": "3.1.0" }, "dependencies": { @@ -2505,7 +2473,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, - "optional": true, "requires": { "ms": "2.0.0" } @@ -2519,9 +2486,9 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" } }, "httpntlm": { @@ -2529,18 +2496,16 @@ "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz", "integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=", "dev": true, - "optional": true, "requires": { - "httpreq": ">=0.4.22", - "underscore": "~1.7.0" + "httpreq": "0.4.24", + "underscore": "1.7.0" } }, "httpreq": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz", "integrity": "sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=", - "dev": true, - "optional": true + "dev": true }, "https-browserify": { "version": "1.0.0", @@ -2553,10 +2518,9 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", "dev": true, - "optional": true, "requires": { - "agent-base": "^4.1.0", - "debug": "^3.1.0" + "agent-base": "4.2.1", + "debug": "3.1.0" }, "dependencies": { "debug": { @@ -2564,7 +2528,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, - "optional": true, "requires": { "ms": "2.0.0" } @@ -2577,7 +2540,7 @@ "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, "ieee754": { @@ -2605,8 +2568,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -2621,7 +2584,7 @@ "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", "dev": true, "requires": { - "source-map": "~0.5.3" + "source-map": "0.5.7" }, "dependencies": { "source-map": { @@ -2638,16 +2601,16 @@ "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", "dev": true, "requires": { - "JSONStream": "^1.0.3", - "acorn-node": "^1.5.2", - "combine-source-map": "^0.8.0", - "concat-stream": "^1.6.1", - "is-buffer": "^1.1.0", - "path-is-absolute": "^1.0.1", - "process": "~0.11.0", - "through2": "^2.0.0", - "undeclared-identifiers": "^1.1.2", - "xtend": "^4.0.0" + "JSONStream": "1.3.4", + "acorn-node": "1.5.2", + "combine-source-map": "0.8.0", + "concat-stream": "1.6.2", + "is-buffer": "1.1.6", + "path-is-absolute": "1.0.1", + "process": "0.11.10", + "through2": "2.0.3", + "undeclared-identifiers": "1.1.2", + "xtend": "4.0.1" }, "dependencies": { "concat-stream": { @@ -2656,10 +2619,10 @@ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "buffer-from": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" } } } @@ -2668,8 +2631,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true, - "optional": true + "dev": true }, "is-binary-path": { "version": "1.0.1", @@ -2677,7 +2639,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.11.0" } }, "is-buffer": { @@ -2698,7 +2660,7 @@ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { - "is-primitive": "^2.0.0" + "is-primitive": "2.0.0" } }, "is-extendable": { @@ -2719,7 +2681,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-my-ip-valid": { @@ -2736,11 +2698,11 @@ "dev": true, "optional": true, "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "is-my-ip-valid": "1.0.0", + "jsonpointer": "4.0.1", + "xtend": "4.0.1" } }, "is-number": { @@ -2749,7 +2711,7 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "is-posix-bracket": { @@ -2797,7 +2759,7 @@ "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", "dev": true, "requires": { - "buffer-alloc": "^1.2.0" + "buffer-alloc": "1.2.0" } }, "isexe": { @@ -2855,15 +2817,14 @@ "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", "dev": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true, - "optional": true + "dev": true }, "jsonify": { "version": "0.0.0", @@ -2903,34 +2864,34 @@ "integrity": "sha512-K9Kjp8CldLyL9ANSUctDyxC7zH3hpqXj/K09qVf06K3T/kXaHtFZ5tQciK7OzQu68FLvI89Na510kqQ2LCbpIw==", "dev": true, "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "browserify": "^14.5.0", - "chokidar": "^1.4.1", - "colors": "^1.1.0", - "combine-lists": "^1.0.0", - "connect": "^3.6.0", - "core-js": "^2.2.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "expand-braces": "^0.1.1", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.4", - "log4js": "^2.3.9", - "mime": "^1.3.4", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", + "bluebird": "3.5.1", + "body-parser": "1.18.3", + "browserify": "14.5.0", + "chokidar": "1.7.0", + "colors": "1.3.1", + "combine-lists": "1.0.1", + "connect": "3.6.6", + "core-js": "2.5.7", + "di": "0.0.1", + "dom-serialize": "2.2.1", + "expand-braces": "0.1.2", + "glob": "7.1.2", + "graceful-fs": "4.1.11", + "http-proxy": "1.17.0", + "isbinaryfile": "3.0.3", + "lodash": "4.17.15", + "log4js": "2.11.0", + "mime": "1.6.0", + "minimatch": "3.0.4", + "optimist": "0.6.1", + "qjobs": "1.2.0", + "range-parser": "1.2.0", + "rimraf": "2.6.2", + "safe-buffer": "5.1.2", "socket.io": "2.0.4", - "source-map": "^0.6.1", + "source-map": "0.6.1", "tmp": "0.0.33", - "useragent": "^2.1.12" + "useragent": "2.3.0" } }, "karma-chrome-launcher": { @@ -2939,8 +2900,8 @@ "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", "dev": true, "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" + "fs-access": "1.0.1", + "which": "1.3.1" } }, "karma-jasmine": { @@ -2961,10 +2922,10 @@ "integrity": "sha1-3kpz4UNJ7BcU+ByacTEc6rGcFvM=", "dev": true, "requires": { - "dateformat": "^2.0.0", - "mkpath": "^1.0.0", - "node-uuid": "^1.4.7", - "xmlbuilder": "^9.0.0" + "dateformat": "2.2.0", + "mkpath": "1.0.0", + "node-uuid": "1.4.8", + "xmlbuilder": "9.0.4" } }, "kind-of": { @@ -2973,7 +2934,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } }, "labeled-stream-splicer": { @@ -2982,9 +2943,9 @@ "integrity": "sha512-MC94mHZRvJ3LfykJlTUipBqenZz1pacOZEMhhQ8dMGcDHs0SBE5GbsavUXV7YtP3icBW17W0Zy1I0lfASmo9Pg==", "dev": true, "requires": { - "inherits": "^2.0.1", - "isarray": "^2.0.4", - "stream-splicer": "^2.0.0" + "inherits": "2.0.3", + "isarray": "2.0.4", + "stream-splicer": "2.0.0" }, "dependencies": { "isarray": { @@ -3002,23 +2963,21 @@ "dev": true, "optional": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "1.1.2", + "type-check": "0.3.2" } }, "libbase64": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz", "integrity": "sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY=", - "dev": true, - "optional": true + "dev": true }, "libmime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/libmime/-/libmime-3.0.0.tgz", "integrity": "sha1-UaGp50SOy9Ms2lRCFnW7IbwJPaY=", "dev": true, - "optional": true, "requires": { "iconv-lite": "0.4.15", "libbase64": "0.1.0", @@ -3029,8 +2988,7 @@ "version": "0.4.15", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz", "integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=", - "dev": true, - "optional": true + "dev": true } } }, @@ -3038,8 +2996,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=", - "dev": true, - "optional": true + "dev": true }, "lodash": { "version": "4.17.15", @@ -3059,18 +3016,18 @@ "integrity": "sha512-z1XdwyGFg8/WGkOyF6DPJjivCWNLKrklGdViywdYnSKOvgtEBo2UyEMZS5sD2mZrQlU3TvO8wDWLc8mzE1ncBQ==", "dev": true, "requires": { - "amqplib": "^0.5.2", - "axios": "^0.15.3", - "circular-json": "^0.5.4", - "date-format": "^1.2.0", - "debug": "^3.1.0", - "hipchat-notifier": "^1.1.0", - "loggly": "^1.1.0", - "mailgun-js": "^0.18.0", - "nodemailer": "^2.5.0", - "redis": "^2.7.1", - "semver": "^5.5.0", - "slack-node": "~0.2.0", + "amqplib": "0.5.2", + "axios": "0.15.3", + "circular-json": "0.5.5", + "date-format": "1.2.0", + "debug": "3.1.0", + "hipchat-notifier": "1.1.0", + "loggly": "1.1.1", + "mailgun-js": "0.18.1", + "nodemailer": "2.7.2", + "redis": "2.8.0", + "semver": "5.5.1", + "slack-node": "0.2.0", "streamroller": "0.7.0" }, "dependencies": { @@ -3092,9 +3049,9 @@ "dev": true, "optional": true, "requires": { - "json-stringify-safe": "5.0.x", - "request": "2.75.x", - "timespan": "2.3.x" + "json-stringify-safe": "5.0.1", + "request": "2.75.0", + "timespan": "2.3.0" }, "dependencies": { "assert-plus": { @@ -3125,9 +3082,9 @@ "dev": true, "optional": true, "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.11" + "asynckit": "0.4.0", + "combined-stream": "1.0.6", + "mime-types": "2.1.19" } }, "har-validator": { @@ -3137,10 +3094,10 @@ "dev": true, "optional": true, "requires": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "is-my-json-valid": "^2.12.4", - "pinkie-promise": "^2.0.0" + "chalk": "1.1.3", + "commander": "2.17.1", + "is-my-json-valid": "2.19.0", + "pinkie-promise": "2.0.1" } }, "http-signature": { @@ -3150,9 +3107,9 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "0.2.0", + "jsprim": "1.4.1", + "sshpk": "1.14.2" } }, "oauth-sign": { @@ -3176,27 +3133,27 @@ "dev": true, "optional": true, "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "bl": "~1.1.2", - "caseless": "~0.11.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~2.0.0", - "har-validator": "~2.0.6", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "node-uuid": "~1.4.7", - "oauth-sign": "~0.8.1", - "qs": "~6.2.0", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "~0.4.1" + "aws-sign2": "0.6.0", + "aws4": "1.8.0", + "bl": "1.1.2", + "caseless": "0.11.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.0.0", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.19", + "node-uuid": "1.4.8", + "oauth-sign": "0.8.2", + "qs": "6.2.3", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.4.3" } }, "tough-cookie": { @@ -3206,7 +3163,7 @@ "dev": true, "optional": true, "requires": { - "punycode": "^1.4.1" + "punycode": "1.4.1" } }, "tunnel-agent": { @@ -3224,8 +3181,8 @@ "integrity": "sha1-oRdc80lt/IQ2wVbDNLSVWZK85pw=", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "mailcomposer": { @@ -3246,15 +3203,15 @@ "dev": true, "optional": true, "requires": { - "async": "~2.6.0", - "debug": "~3.1.0", - "form-data": "~2.3.0", - "inflection": "~1.12.0", - "is-stream": "^1.1.0", - "path-proxy": "~1.0.0", - "promisify-call": "^2.0.2", - "proxy-agent": "~3.0.0", - "tsscmp": "~1.0.0" + "async": "2.6.1", + "debug": "3.1.0", + "form-data": "2.3.2", + "inflection": "1.12.0", + "is-stream": "1.1.0", + "path-proxy": "1.0.0", + "promisify-call": "2.0.4", + "proxy-agent": "3.0.1", + "tsscmp": "1.0.6" }, "dependencies": { "debug": { @@ -3281,8 +3238,8 @@ "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "hash-base": "3.0.4", + "inherits": "2.0.3" } }, "media-typer": { @@ -3297,19 +3254,19 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" } }, "miller-rabin": { @@ -3318,8 +3275,8 @@ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "dev": true, "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" + "bn.js": "4.11.8", + "brorand": "1.1.0" } }, "mime": { @@ -3340,7 +3297,7 @@ "integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==", "dev": true, "requires": { - "mime-db": "~1.35.0" + "mime-db": "1.35.0" } }, "minimalistic-assert": { @@ -3361,7 +3318,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -3399,21 +3356,21 @@ "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", "dev": true, "requires": { - "JSONStream": "^1.0.3", - "browser-resolve": "^1.7.0", - "cached-path-relative": "^1.0.0", - "concat-stream": "~1.5.0", - "defined": "^1.0.0", - "detective": "^4.0.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.3", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" + "JSONStream": "1.3.4", + "browser-resolve": "1.11.3", + "cached-path-relative": "1.0.1", + "concat-stream": "1.5.2", + "defined": "1.0.0", + "detective": "4.7.1", + "duplexer2": "0.1.4", + "inherits": "2.0.3", + "parents": "1.0.1", + "readable-stream": "2.3.6", + "resolve": "1.8.1", + "stream-combiner2": "1.1.1", + "subarg": "1.0.0", + "through2": "2.0.3", + "xtend": "4.0.1" } }, "ms": { @@ -3464,8 +3421,8 @@ "dev": true, "optional": true, "requires": { - "ip": "^1.1.2", - "smart-buffer": "^1.0.4" + "ip": "1.1.5", + "smart-buffer": "1.1.15" } } } @@ -3485,15 +3442,13 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz", "integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=", - "dev": true, - "optional": true + "dev": true }, "nodemailer-shared": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz", "integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=", "dev": true, - "optional": true, "requires": { "nodemailer-fetch": "1.6.0" } @@ -3526,8 +3481,7 @@ "version": "0.1.10", "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", "integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=", - "dev": true, - "optional": true + "dev": true }, "normalize-path": { "version": "2.1.1", @@ -3535,7 +3489,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "null-check": { @@ -3563,8 +3517,8 @@ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" + "for-own": "0.1.5", + "is-extendable": "0.1.1" } }, "on-finished": { @@ -3582,7 +3536,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "optimist": { @@ -3591,8 +3545,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" }, "dependencies": { "minimist": { @@ -3616,12 +3570,12 @@ "dev": true, "optional": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" } }, "os-browserify": { @@ -3643,14 +3597,14 @@ "dev": true, "optional": true, "requires": { - "agent-base": "^4.2.0", - "debug": "^3.1.0", - "get-uri": "^2.0.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", - "pac-resolver": "^3.0.0", - "raw-body": "^2.2.0", - "socks-proxy-agent": "^3.0.0" + "agent-base": "4.2.1", + "debug": "3.1.0", + "get-uri": "2.0.2", + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.1", + "pac-resolver": "3.0.0", + "raw-body": "2.3.3", + "socks-proxy-agent": "3.0.1" }, "dependencies": { "debug": { @@ -3670,8 +3624,8 @@ "dev": true, "optional": true, "requires": { - "agent-base": "^4.1.0", - "socks": "^1.1.10" + "agent-base": "4.2.1", + "socks": "1.1.10" } } } @@ -3683,11 +3637,11 @@ "dev": true, "optional": true, "requires": { - "co": "^4.6.0", - "degenerator": "^1.0.4", - "ip": "^1.1.5", - "netmask": "^1.0.6", - "thunkify": "^2.1.2" + "co": "4.6.0", + "degenerator": "1.0.4", + "ip": "1.1.5", + "netmask": "1.0.6", + "thunkify": "2.1.2" } }, "pako": { @@ -3702,7 +3656,7 @@ "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", "dev": true, "requires": { - "path-platform": "~0.11.15" + "path-platform": "0.11.15" } }, "parse-asn1": { @@ -3711,11 +3665,11 @@ "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "asn1.js": "4.10.1", + "browserify-aes": "1.2.0", + "create-hash": "1.2.0", + "evp_bytestokey": "1.0.3", + "pbkdf2": "3.0.16" } }, "parse-glob": { @@ -3724,10 +3678,10 @@ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" } }, "parseqs": { @@ -3736,7 +3690,7 @@ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseuri": { @@ -3745,7 +3699,7 @@ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseurl": { @@ -3785,7 +3739,7 @@ "dev": true, "optional": true, "requires": { - "inflection": "~1.3.0" + "inflection": "1.3.8" }, "dependencies": { "inflection": { @@ -3803,11 +3757,11 @@ "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", "dev": true, "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" + "create-hash": "1.2.0", + "create-hmac": "1.1.7", + "ripemd160": "2.0.2", + "safe-buffer": "5.1.2", + "sha.js": "2.4.11" } }, "performance-now": { @@ -3831,15 +3785,14 @@ "dev": true, "optional": true, "requires": { - "pinkie": "^2.0.0" + "pinkie": "2.0.4" } }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "optional": true + "dev": true }, "preserve": { "version": "0.2.0", @@ -3866,7 +3819,7 @@ "dev": true, "optional": true, "requires": { - "with-callback": "^1.0.2" + "with-callback": "1.0.2" } }, "proxy-agent": { @@ -3876,14 +3829,14 @@ "dev": true, "optional": true, "requires": { - "agent-base": "^4.2.0", - "debug": "^3.1.0", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", - "lru-cache": "^4.1.2", - "pac-proxy-agent": "^2.0.1", - "proxy-from-env": "^1.0.0", - "socks-proxy-agent": "^4.0.1" + "agent-base": "4.2.1", + "debug": "3.1.0", + "http-proxy-agent": "2.1.0", + "https-proxy-agent": "2.2.1", + "lru-cache": "4.1.3", + "pac-proxy-agent": "2.0.2", + "proxy-from-env": "1.0.0", + "socks-proxy-agent": "4.0.1" }, "dependencies": { "debug": { @@ -3924,11 +3877,11 @@ "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", "dev": true, "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1" + "bn.js": "4.11.8", + "browserify-rsa": "4.0.1", + "create-hash": "1.2.0", + "parse-asn1": "5.1.1", + "randombytes": "2.0.6" } }, "punycode": { @@ -3967,9 +3920,9 @@ "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", "dev": true, "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" + "is-number": "4.0.0", + "kind-of": "6.0.2", + "math-random": "1.0.1" }, "dependencies": { "is-number": { @@ -3992,7 +3945,7 @@ "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", "dev": true, "requires": { - "safe-buffer": "^5.1.0" + "safe-buffer": "5.1.2" } }, "randomfill": { @@ -4001,8 +3954,8 @@ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "dev": true, "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" + "randombytes": "2.0.6", + "safe-buffer": "5.1.2" } }, "range-parser": { @@ -4029,7 +3982,7 @@ "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", "dev": true, "requires": { - "readable-stream": "^2.0.2" + "readable-stream": "2.3.6" } }, "readable-stream": { @@ -4038,13 +3991,13 @@ "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" }, "dependencies": { "string_decoder": { @@ -4053,7 +4006,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -4064,10 +4017,10 @@ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.6", + "set-immediate-shim": "1.0.1" } }, "redis": { @@ -4077,9 +4030,9 @@ "dev": true, "optional": true, "requires": { - "double-ended-queue": "^2.1.0-0", - "redis-commands": "^1.2.0", - "redis-parser": "^2.6.0" + "double-ended-queue": "2.1.0-0", + "redis-commands": "1.3.5", + "redis-parser": "2.6.0" } }, "redis-commands": { @@ -4102,7 +4055,7 @@ "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", "dev": true, "requires": { - "is-equal-shallow": "^0.1.3" + "is-equal-shallow": "0.1.3" } }, "remove-trailing-separator": { @@ -4130,26 +4083,26 @@ "dev": true, "optional": true, "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "aws-sign2": "0.7.0", + "aws4": "1.8.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.2", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.1.0", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.19", + "oauth-sign": "0.9.0", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.4.3", + "tunnel-agent": "0.6.0", + "uuid": "3.3.2" }, "dependencies": { "uuid": { @@ -4168,10 +4121,10 @@ "dev": true, "optional": true, "requires": { - "extend": "^3.0.0", - "lodash": "^4.15.0", - "request": "^2.74.0", - "when": "^3.7.7" + "extend": "3.0.2", + "lodash": "4.17.15", + "request": "2.88.0", + "when": "3.7.8" } }, "requires-port": { @@ -4186,7 +4139,7 @@ "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "1.0.6" } }, "rimraf": { @@ -4195,7 +4148,7 @@ "integrity": "sha1-LtgVDSShbqhlHm1u8PR8QVjOejY=", "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "ripemd160": { @@ -4204,8 +4157,8 @@ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "hash-base": "3.0.4", + "inherits": "2.0.3" } }, "safe-buffer": { @@ -4244,8 +4197,8 @@ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.2" } }, "shasum": { @@ -4254,8 +4207,8 @@ "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", "dev": true, "requires": { - "json-stable-stringify": "~0.0.0", - "sha.js": "~2.4.4" + "json-stable-stringify": "0.0.1", + "sha.js": "2.4.11" } }, "shell-quote": { @@ -4264,10 +4217,10 @@ "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=", "dev": true, "requires": { - "array-filter": "~0.0.0", - "array-map": "~0.0.0", - "array-reduce": "~0.0.0", - "jsonify": "~0.0.0" + "array-filter": "0.0.1", + "array-map": "0.0.0", + "array-reduce": "0.0.0", + "jsonify": "0.0.0" } }, "simple-concat": { @@ -4283,7 +4236,7 @@ "dev": true, "optional": true, "requires": { - "requestretry": "^1.2.2" + "requestretry": "1.13.0" } }, "smart-buffer": { @@ -4298,7 +4251,6 @@ "resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz", "integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=", "dev": true, - "optional": true, "requires": { "httpntlm": "1.6.1", "nodemailer-shared": "1.1.0" @@ -4311,7 +4263,7 @@ "dev": true, "optional": true, "requires": { - "hoek": "2.x.x" + "hoek": "2.16.3" } }, "socket.io": { @@ -4320,11 +4272,11 @@ "integrity": "sha1-waRZDO/4fs8TxyZS8Eb3FrKeYBQ=", "dev": true, "requires": { - "debug": "~2.6.6", - "engine.io": "~3.1.0", - "socket.io-adapter": "~1.1.0", + "debug": "2.6.9", + "engine.io": "3.1.5", + "socket.io-adapter": "1.1.1", "socket.io-client": "2.0.4", - "socket.io-parser": "~3.1.1" + "socket.io-parser": "3.1.3" } }, "socket.io-adapter": { @@ -4343,14 +4295,14 @@ "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", "component-emitter": "1.2.1", - "debug": "~2.6.4", - "engine.io-client": "~3.1.0", + "debug": "2.6.9", + "engine.io-client": "3.1.6", "has-cors": "1.1.0", "indexof": "0.0.1", "object-component": "0.0.3", "parseqs": "0.0.5", "parseuri": "0.0.5", - "socket.io-parser": "~3.1.1", + "socket.io-parser": "3.1.3", "to-array": "0.1.4" } }, @@ -4361,8 +4313,8 @@ "dev": true, "requires": { "component-emitter": "1.2.1", - "debug": "~3.1.0", - "has-binary2": "~1.0.2", + "debug": "3.1.0", + "has-binary2": "1.0.3", "isarray": "2.0.1" }, "dependencies": { @@ -4390,8 +4342,8 @@ "dev": true, "optional": true, "requires": { - "ip": "^1.1.4", - "smart-buffer": "^1.0.13" + "ip": "1.1.5", + "smart-buffer": "1.1.15" } }, "socks-proxy-agent": { @@ -4401,8 +4353,8 @@ "dev": true, "optional": true, "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" + "agent-base": "4.2.1", + "socks": "2.2.1" }, "dependencies": { "smart-buffer": { @@ -4419,8 +4371,8 @@ "dev": true, "optional": true, "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.0.1" + "ip": "1.1.5", + "smart-buffer": "4.0.1" } } } @@ -4438,15 +4390,15 @@ "dev": true, "optional": true, "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" + "asn1": "0.2.4", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.2", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.2", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "safer-buffer": "2.1.2", + "tweetnacl": "0.14.5" } }, "statuses": { @@ -4461,8 +4413,8 @@ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", "dev": true, "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" + "inherits": "2.0.3", + "readable-stream": "2.3.6" } }, "stream-combiner2": { @@ -4471,8 +4423,8 @@ "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", "dev": true, "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" + "duplexer2": "0.1.4", + "readable-stream": "2.3.6" } }, "stream-http": { @@ -4481,11 +4433,11 @@ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", "dev": true, "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" + "builtin-status-codes": "3.0.0", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "to-arraybuffer": "1.0.1", + "xtend": "4.0.1" } }, "stream-splicer": { @@ -4494,8 +4446,8 @@ "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" + "inherits": "2.0.3", + "readable-stream": "2.3.6" } }, "streamroller": { @@ -4504,10 +4456,10 @@ "integrity": "sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==", "dev": true, "requires": { - "date-format": "^1.2.0", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "readable-stream": "^2.3.0" + "date-format": "1.2.0", + "debug": "3.1.0", + "mkdirp": "0.5.1", + "readable-stream": "2.3.6" }, "dependencies": { "debug": { @@ -4527,7 +4479,7 @@ "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } }, "stringstream": { @@ -4544,7 +4496,7 @@ "dev": true, "optional": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "subarg": { @@ -4553,7 +4505,7 @@ "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", "dev": true, "requires": { - "minimist": "^1.1.0" + "minimist": "1.2.0" } }, "supports-color": { @@ -4569,7 +4521,7 @@ "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", "dev": true, "requires": { - "acorn-node": "^1.2.0" + "acorn-node": "1.5.2" } }, "through": { @@ -4584,8 +4536,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.6", + "xtend": "4.0.1" } }, "thunkify": { @@ -4601,7 +4553,7 @@ "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", "dev": true, "requires": { - "process": "~0.11.0" + "process": "0.11.10" } }, "timespan": { @@ -4617,7 +4569,7 @@ "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "os-tmpdir": "1.0.2" } }, "to-array": { @@ -4639,8 +4591,8 @@ "dev": true, "optional": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "1.1.29", + "punycode": "1.4.1" } }, "tsscmp": { @@ -4663,7 +4615,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -4678,9 +4630,8 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, - "optional": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "1.1.2" } }, "type-is": { @@ -4690,7 +4641,7 @@ "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.18" + "mime-types": "2.1.19" } }, "typedarray": { @@ -4717,18 +4668,17 @@ "integrity": "sha512-13EaeocO4edF/3JKime9rD7oB6QI8llAGhgn5fKOPyfkJbRb6NFv9pYV6dFEmpa4uRjKeBqLZP8GpuzqHlKDMQ==", "dev": true, "requires": { - "acorn-node": "^1.3.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" + "acorn-node": "1.5.2", + "get-assigned-identifiers": "1.2.0", + "simple-concat": "1.0.0", + "xtend": "4.0.1" } }, "underscore": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz", "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=", - "dev": true, - "optional": true + "dev": true }, "unpipe": { "version": "1.0.0", @@ -4760,8 +4710,8 @@ "integrity": "sha1-IX+UOtVAyyEoZYqyP8lg9qiMmXI=", "dev": true, "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" + "lru-cache": "4.1.3", + "tmp": "0.0.33" } }, "util": { @@ -4799,9 +4749,9 @@ "dev": true, "optional": true, "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "vm-browserify": { @@ -4832,7 +4782,7 @@ "integrity": "sha1-pFBD1U9YBTFtqNYvn1CRjT2nCwo=", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "with-callback": { @@ -4861,9 +4811,9 @@ "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "1.0.0", + "safe-buffer": "5.1.2", + "ultron": "1.1.1" } }, "xmlbuilder": { diff --git a/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.Events.Tests.d.ts b/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.Events.Tests.d.ts index b3c44b9841..0bf921e70d 100644 --- a/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.Events.Tests.d.ts +++ b/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.Events.Tests.d.ts @@ -1,4 +1,4 @@ /// -/// +/// /// declare var dotvvm: DotVVM; diff --git a/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.diff.Tests.d.ts b/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.diff.Tests.d.ts new file mode 100644 index 0000000000..0bf921e70d --- /dev/null +++ b/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.diff.Tests.d.ts @@ -0,0 +1,4 @@ +/// +/// +/// +declare var dotvvm: DotVVM; diff --git a/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.diff.Tests.ts b/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.diff.Tests.ts new file mode 100644 index 0000000000..05ea395242 --- /dev/null +++ b/src/DotVVM.Framework.Tests.Javascript/tests/DotVVM.diff.Tests.ts @@ -0,0 +1,66 @@ +/// +/// +/// + +var dotvvm = new DotVVM(); + +describe("DotVVM.diff", () => { + + it("diff on numbers returns target value", () => { + + var orig = 1; + expect(dotvvm.diff(orig, 1)).toEqual(1); + expect(dotvvm.diff(orig, 2)).toEqual(2); + + }); + + it("diff on strings returns target value", () => { + + var orig = "a"; + expect(dotvvm.diff(orig, "a")).toEqual("a"); + expect(dotvvm.diff(orig, "b")).toEqual("b"); + + }); + + it("diff on array of primitive values returns array of primitive values if it is different", () => { + + var orig = ["a", "b"]; + expect(dotvvm.diff(orig, ["a", "b"])).toEqual(dotvvm.diffEqual); + expect(dotvvm.diff(orig, ["a", "b", "c"])).toEqual(["a", "b", "c"]); + expect(dotvvm.diff(orig, ["a"])).toEqual(["a"]); + + }); + + it("diff on objects returns only changed properties", () => { + + var orig = { a: 1, b: "a" }; + expect(dotvvm.diff(orig, { a: 1, b: "a" })).toEqual(dotvvm.diffEqual); + expect(dotvvm.diff(orig, { a: 2, b: "a" })).toEqual({ a: 2 }); + expect(dotvvm.diff(orig, { a: 2, b: "a", c: null })).toEqual({ a: 2, c: null }); + expect(dotvvm.diff(orig, { a: 2 })).toEqual({ a: 2 }); + + }); + + it("diff on objects recursive returns only changed properties", () => { + + var orig = { a: 1, bparent: { b: "a", c: 1 } }; + expect(dotvvm.diff(orig, { a: 1, bparent: { b: "a", c: 1 } })).toEqual(dotvvm.diffEqual); + expect(dotvvm.diff(orig, { a: 2, b: "a" })).toEqual({ a: 2, b: "a" }); + expect(dotvvm.diff(orig, { a: 1, bparent: { b: "a", c: 2 } })).toEqual({ bparent: { c: 2 } }); + expect(dotvvm.diff(orig, { a: 1, bparent: { b: "a", c: 2, d: 3 } })).toEqual({ bparent: { c: 2, d: 3 } }); + expect(dotvvm.diff(orig, { a: 1, bparent: { b: "b" } })).toEqual({ bparent: { b: "b" } }); + + }); + + it("diff on array of objects", () => { + + var orig = [{ a: 1 }, { a: 2 }]; + expect(dotvvm.diff(orig, [{ a: 1 }, { a: 2 }])).toEqual(dotvvm.diffEqual); + expect(dotvvm.diff(orig, [{ a: 3 }, { a: 2 }])).toEqual([{ a: 3 }, {}]); + expect(dotvvm.diff(orig, [{ a: 1 }, { a: 2 }, { a: 3 }])).toEqual([{}, {}, { a: 3 }]); + expect(dotvvm.diff(orig, [{ a: 2 }])).toEqual([{ a: 2 }]); + expect(dotvvm.diff(orig, [{ a: 1 }])).toEqual([{}]); + + }); + +}); diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js index 8e279152ef..419da0f682 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js @@ -1874,7 +1874,14 @@ var DotVVM = /** @class */ (function () { DotVVM.prototype.diff = function (source, modified) { var _this = this; if (source instanceof Array && modified instanceof Array) { - return modified.map(function (val, i) { return _this.diff(source[i], val); }); + var diffArray = modified.map(function (el, index) { return _this.diff(source[index], el); }); + if (source.length === modified.length + && diffArray.every(function (el, index) { return el === _this.diffEqual || source[index] === modified[index]; })) { + return this.diffEqual; + } + else { + return diffArray; + } } else if (source instanceof Array || modified instanceof Array) { return modified; @@ -1883,8 +1890,8 @@ var DotVVM = /** @class */ (function () { var result = this.diffEqual; for (var p in modified) { var propertyDiff = this.diff(source[p], modified[p]); - if (propertyDiff !== this.diffEqual) { - if (result == this.diffEqual) { + if (propertyDiff !== this.diffEqual && source[p] !== modified[p]) { + if (result === this.diffEqual) { result = {}; } result[p] = propertyDiff; @@ -1899,7 +1906,12 @@ var DotVVM = /** @class */ (function () { return result; } else if (source === modified) { - return this.diffEqual; + if (typeof source == "object") { + return this.diffEqual; + } + else { + return source; + } } else { return modified; diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js index e247f74241..c000d8b87f 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js @@ -1 +1 @@ -var __assign=this&&this.__assign||function(){return(__assign=Object.assign||function(e){for(var t,r=1,n=arguments.length;ra[0]&&t[1]'");return e},e.prototype.format=function(e){for(var o=this,i=[],t=1;t=e.length)r();else{var o=e[t],i=!1;if("script"==o.tagName.toLowerCase()){var a=o,s=document.createElement("script");a.src&&(s.src=a.src,i=!0),a.type&&(s.type=a.type),a.text&&(s.text=a.text),o.id&&(s.id=o.id),o=s}else if("link"==o.tagName.toLowerCase()){var l=o,u=document.createElement("link");l.href&&(u.href=l.href),l.rel&&(u.rel=l.rel),l.type&&(u.type=l.type),o=u}i&&(o.addEventListener("load",function(){return n.loadResourceElements(e,t+1,r)}),o.addEventListener("error",function(){return n.loadResourceElements(e,t+1,r)})),document.head.appendChild(o),i||this.loadResourceElements(e,t+1,r)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(a,e,t){var s=this,r=this.viewModels[a].viewModel,n=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(r,a,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var i=this.viewModels[a].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),u=this.addLeadingSlash(this.concatUrl(i,l)),d=this.getSpaPlaceHolder();if(d){t&&t(this.addLeadingSlash(this.concatUrl(i,this.addLeadingSlash(e))));var v=d.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(u,"GET",v,function(o){if(s.isPostBackStillActive(n)){var i=JSON.parse(o.responseText);s.loadResourceList(i.resources,function(){var e=!1;if("successfulCommand"!==i.action&&i.action){if("redirect"===i.action)return void s.handleRedirect(i,a,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(i);for(var r in s.viewModels[a]={},i)i.hasOwnProperty(r)&&(s.viewModels[a][r]=i[r]);i.viewModelCacheId?s.viewModels[a].viewModelCache=i.viewModel:delete s.viewModels[a].viewModelCache,ko.delaySync.pause(),s.serialization.deserialize(i.viewModel,s.viewModels[a].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[a](s.viewModels[a].viewModel),s.restoreUpdatedControls(i,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var n=new DotvvmSpaNavigatedEventArgs(s.viewModels[a].viewModel,a,i,o);if(s.events.spaNavigated.trigger(n),!e&&!n.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(n)){var t=new DotvvmErrorEventArgs(void 0,r,a,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=u}},e.prototype.handleRedirect=function(e,t,r){var n;void 0===r&&(r=!1),null!=e.replace&&(r=e.replace),n=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(n="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(n="#!/"),this.fixSpaUrlPrefix(n)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,n,r);this.events.redirect.trigger(o),this.performRedirect(n,r,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,r){if(t)location.replace(e);else if(r)this.handleSpaNavigationCore(e);else{var n=this.fakeRedirectAnchor;n||((n=document.createElement("a")).style.display="none",n.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(n),this.fakeRedirectAnchor=n),n.href=e,n.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var r=t.value;return r!==document.location.pathname&&(""===r&&(r="/"),e=r+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var r="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(r)?this.addLeadingSlash(e.substring(r.length)):e},e.prototype.addLeadingSlash=function(e){return 0a[0]&&t[1]'");return e},e.prototype.format=function(e){for(var o=this,i=[],t=1;t=e.length)r();else{var o=e[t],i=!1;if("script"==o.tagName.toLowerCase()){var a=o,s=document.createElement("script");a.src&&(s.src=a.src,i=!0),a.type&&(s.type=a.type),a.text&&(s.text=a.text),o.id&&(s.id=o.id),o=s}else if("link"==o.tagName.toLowerCase()){var l=o,u=document.createElement("link");l.href&&(u.href=l.href),l.rel&&(u.rel=l.rel),l.type&&(u.type=l.type),o=u}i&&(o.addEventListener("load",function(){return n.loadResourceElements(e,t+1,r)}),o.addEventListener("error",function(){return n.loadResourceElements(e,t+1,r)})),document.head.appendChild(o),i||this.loadResourceElements(e,t+1,r)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(a,e,t){var s=this,r=this.viewModels[a].viewModel,n=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(r,a,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var i=this.viewModels[a].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),u=this.addLeadingSlash(this.concatUrl(i,l)),d=this.getSpaPlaceHolder();if(d){t&&t(this.addLeadingSlash(this.concatUrl(i,this.addLeadingSlash(e))));var v=d.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(u,"GET",v,function(o){if(s.isPostBackStillActive(n)){var i=JSON.parse(o.responseText);s.loadResourceList(i.resources,function(){var e=!1;if("successfulCommand"!==i.action&&i.action){if("redirect"===i.action)return void s.handleRedirect(i,a,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(i);for(var r in s.viewModels[a]={},i)i.hasOwnProperty(r)&&(s.viewModels[a][r]=i[r]);i.viewModelCacheId?s.viewModels[a].viewModelCache=i.viewModel:delete s.viewModels[a].viewModelCache,ko.delaySync.pause(),s.serialization.deserialize(i.viewModel,s.viewModels[a].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[a](s.viewModels[a].viewModel),s.restoreUpdatedControls(i,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var n=new DotvvmSpaNavigatedEventArgs(s.viewModels[a].viewModel,a,i,o);if(s.events.spaNavigated.trigger(n),!e&&!n.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(n)){var t=new DotvvmErrorEventArgs(void 0,r,a,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=u}},e.prototype.handleRedirect=function(e,t,r){var n;void 0===r&&(r=!1),null!=e.replace&&(r=e.replace),n=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(n="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(n="#!/"),this.fixSpaUrlPrefix(n)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,n,r);this.events.redirect.trigger(o),this.performRedirect(n,r,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,r){if(t)location.replace(e);else if(r)this.handleSpaNavigationCore(e);else{var n=this.fakeRedirectAnchor;n||((n=document.createElement("a")).style.display="none",n.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(n),this.fakeRedirectAnchor=n),n.href=e,n.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var r=t.value;return r!==document.location.pathname&&(""===r&&(r="/"),e=r+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var r="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(r)?this.addLeadingSlash(e.substring(r.length)):e},e.prototype.addLeadingSlash=function(e){return 0 this.diff(source[i], val)); + var diffArray = modified.map((el, index) => this.diff(source[index], el)); + if (source.length === modified.length + && diffArray.every((el, index) => el === this.diffEqual || source[index] === modified[index])) { + return this.diffEqual; + } else { + return diffArray; + } } else if (source instanceof Array || modified instanceof Array) { return modified; @@ -1076,8 +1082,8 @@ class DotVVM { var result = this.diffEqual; for (var p in modified) { var propertyDiff = this.diff(source[p], modified[p]); - if (propertyDiff !== this.diffEqual) { - if (result == this.diffEqual) { + if (propertyDiff !== this.diffEqual && source[p] !== modified[p]) { + if (result === this.diffEqual) { result = {}; } result[p] = propertyDiff; @@ -1091,7 +1097,11 @@ class DotVVM { return result; } else if (source === modified) { - return this.diffEqual; + if (typeof source == "object") { + return this.diffEqual; + } else { + return source; + } } else { return modified; } diff --git a/src/DotVVM.Framework/Utils/JsonUtils.cs b/src/DotVVM.Framework/Utils/JsonUtils.cs index 08febcd8ec..512d00ba7d 100644 --- a/src/DotVVM.Framework/Utils/JsonUtils.cs +++ b/src/DotVVM.Framework/Utils/JsonUtils.cs @@ -181,7 +181,6 @@ public static IEnumerable ZipOverhang(this IEnumerable a, IEnumerable Date: Sun, 10 Nov 2019 19:36:30 +0100 Subject: [PATCH 09/16] ClientToServer diff fixed --- src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts | 2 +- src/DotVVM.Framework/Resources/Scripts/DotVVM.js | 2 +- src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts b/src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts index 16de9d918c..87f72bbc42 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts @@ -116,7 +116,7 @@ class DotvvmSerialization { if (!ko.isObservable(value) && typeof (value) === "function") { continue; } - const options = viewModel[prop + "$options"]; + const options = viewModel[prop + "$options"] || target[prop + "$options"]; if (!deserializeAll && options && options.doNotUpdate) { continue; } diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js index 419da0f682..e8ebdbe66e 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js @@ -606,7 +606,7 @@ var DotvvmSerialization = /** @class */ (function () { if (!ko.isObservable(value) && typeof (value) === "function") { continue; } - var options = viewModel[prop + "$options"]; + var options = viewModel[prop + "$options"] || target[prop + "$options"]; if (!deserializeAll && options && options.doNotUpdate) { continue; } diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js index c000d8b87f..fedb36a10c 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js @@ -1 +1 @@ -var __assign=this&&this.__assign||function(){return(__assign=Object.assign||function(e){for(var t,r=1,n=arguments.length;ra[0]&&t[1]'");return e},e.prototype.format=function(e){for(var o=this,i=[],t=1;t=e.length)r();else{var o=e[t],i=!1;if("script"==o.tagName.toLowerCase()){var a=o,s=document.createElement("script");a.src&&(s.src=a.src,i=!0),a.type&&(s.type=a.type),a.text&&(s.text=a.text),o.id&&(s.id=o.id),o=s}else if("link"==o.tagName.toLowerCase()){var l=o,u=document.createElement("link");l.href&&(u.href=l.href),l.rel&&(u.rel=l.rel),l.type&&(u.type=l.type),o=u}i&&(o.addEventListener("load",function(){return n.loadResourceElements(e,t+1,r)}),o.addEventListener("error",function(){return n.loadResourceElements(e,t+1,r)})),document.head.appendChild(o),i||this.loadResourceElements(e,t+1,r)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(a,e,t){var s=this,r=this.viewModels[a].viewModel,n=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(r,a,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var i=this.viewModels[a].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),u=this.addLeadingSlash(this.concatUrl(i,l)),d=this.getSpaPlaceHolder();if(d){t&&t(this.addLeadingSlash(this.concatUrl(i,this.addLeadingSlash(e))));var v=d.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(u,"GET",v,function(o){if(s.isPostBackStillActive(n)){var i=JSON.parse(o.responseText);s.loadResourceList(i.resources,function(){var e=!1;if("successfulCommand"!==i.action&&i.action){if("redirect"===i.action)return void s.handleRedirect(i,a,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(i);for(var r in s.viewModels[a]={},i)i.hasOwnProperty(r)&&(s.viewModels[a][r]=i[r]);i.viewModelCacheId?s.viewModels[a].viewModelCache=i.viewModel:delete s.viewModels[a].viewModelCache,ko.delaySync.pause(),s.serialization.deserialize(i.viewModel,s.viewModels[a].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[a](s.viewModels[a].viewModel),s.restoreUpdatedControls(i,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var n=new DotvvmSpaNavigatedEventArgs(s.viewModels[a].viewModel,a,i,o);if(s.events.spaNavigated.trigger(n),!e&&!n.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(n)){var t=new DotvvmErrorEventArgs(void 0,r,a,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=u}},e.prototype.handleRedirect=function(e,t,r){var n;void 0===r&&(r=!1),null!=e.replace&&(r=e.replace),n=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(n="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(n="#!/"),this.fixSpaUrlPrefix(n)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,n,r);this.events.redirect.trigger(o),this.performRedirect(n,r,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,r){if(t)location.replace(e);else if(r)this.handleSpaNavigationCore(e);else{var n=this.fakeRedirectAnchor;n||((n=document.createElement("a")).style.display="none",n.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(n),this.fakeRedirectAnchor=n),n.href=e,n.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var r=t.value;return r!==document.location.pathname&&(""===r&&(r="/"),e=r+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var r="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(r)?this.addLeadingSlash(e.substring(r.length)):e},e.prototype.addLeadingSlash=function(e){return 0a[0]&&t[1]'");return e},e.prototype.format=function(e){for(var o=this,i=[],t=1;t=e.length)r();else{var o=e[t],i=!1;if("script"==o.tagName.toLowerCase()){var a=o,s=document.createElement("script");a.src&&(s.src=a.src,i=!0),a.type&&(s.type=a.type),a.text&&(s.text=a.text),o.id&&(s.id=o.id),o=s}else if("link"==o.tagName.toLowerCase()){var l=o,u=document.createElement("link");l.href&&(u.href=l.href),l.rel&&(u.rel=l.rel),l.type&&(u.type=l.type),o=u}i&&(o.addEventListener("load",function(){return n.loadResourceElements(e,t+1,r)}),o.addEventListener("error",function(){return n.loadResourceElements(e,t+1,r)})),document.head.appendChild(o),i||this.loadResourceElements(e,t+1,r)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(a,e,t){var s=this,r=this.viewModels[a].viewModel,n=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(r,a,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var i=this.viewModels[a].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),u=this.addLeadingSlash(this.concatUrl(i,l)),d=this.getSpaPlaceHolder();if(d){t&&t(this.addLeadingSlash(this.concatUrl(i,this.addLeadingSlash(e))));var v=d.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(u,"GET",v,function(o){if(s.isPostBackStillActive(n)){var i=JSON.parse(o.responseText);s.loadResourceList(i.resources,function(){var e=!1;if("successfulCommand"!==i.action&&i.action){if("redirect"===i.action)return void s.handleRedirect(i,a,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(i);for(var r in s.viewModels[a]={},i)i.hasOwnProperty(r)&&(s.viewModels[a][r]=i[r]);i.viewModelCacheId?s.viewModels[a].viewModelCache=i.viewModel:delete s.viewModels[a].viewModelCache,ko.delaySync.pause(),s.serialization.deserialize(i.viewModel,s.viewModels[a].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[a](s.viewModels[a].viewModel),s.restoreUpdatedControls(i,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var n=new DotvvmSpaNavigatedEventArgs(s.viewModels[a].viewModel,a,i,o);if(s.events.spaNavigated.trigger(n),!e&&!n.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(n)){var t=new DotvvmErrorEventArgs(void 0,r,a,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=u}},e.prototype.handleRedirect=function(e,t,r){var n;void 0===r&&(r=!1),null!=e.replace&&(r=e.replace),n=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(n="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(n="#!/"),this.fixSpaUrlPrefix(n)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,n,r);this.events.redirect.trigger(o),this.performRedirect(n,r,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,r){if(t)location.replace(e);else if(r)this.handleSpaNavigationCore(e);else{var n=this.fakeRedirectAnchor;n||((n=document.createElement("a")).style.display="none",n.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(n),this.fakeRedirectAnchor=n),n.href=e,n.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var r=t.value;return r!==document.location.pathname&&(""===r&&(r="/"),e=r+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var r="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(r)?this.addLeadingSlash(e.substring(r.length)):e},e.prototype.addLeadingSlash=function(e){return 0 Date: Sun, 10 Nov 2019 20:42:44 +0100 Subject: [PATCH 10/16] IfInPostbackPath and ServerToClient serialization fixed --- .../Resources/Scripts/DotVVM.Serialization.ts | 2 +- .../Resources/Scripts/DotVVM.js | 2 +- .../Resources/Scripts/DotVVM.min.js | 2 +- src/DotVVM.Framework/Utils/JsonUtils.cs | 24 +++++++++++++++++-- .../DefaultViewModelSerializer.cs | 2 +- .../ViewModelSerializationMap.cs | 4 ++++ 6 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts b/src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts index 87f72bbc42..b20506bf65 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.Serialization.ts @@ -116,7 +116,7 @@ class DotvvmSerialization { if (!ko.isObservable(value) && typeof (value) === "function") { continue; } - const options = viewModel[prop + "$options"] || target[prop + "$options"]; + const options = viewModel[prop + "$options"] || (unwrappedTarget && unwrappedTarget[prop + "$options"]); if (!deserializeAll && options && options.doNotUpdate) { continue; } diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js index e8ebdbe66e..beaf3afba0 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.js @@ -606,7 +606,7 @@ var DotvvmSerialization = /** @class */ (function () { if (!ko.isObservable(value) && typeof (value) === "function") { continue; } - var options = viewModel[prop + "$options"] || target[prop + "$options"]; + var options = viewModel[prop + "$options"] || (unwrappedTarget && unwrappedTarget[prop + "$options"]); if (!deserializeAll && options && options.doNotUpdate) { continue; } diff --git a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js index fedb36a10c..74813d0ed5 100644 --- a/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js +++ b/src/DotVVM.Framework/Resources/Scripts/DotVVM.min.js @@ -1 +1 @@ -var __assign=this&&this.__assign||function(){return(__assign=Object.assign||function(e){for(var t,r=1,n=arguments.length;ra[0]&&t[1]'");return e},e.prototype.format=function(e){for(var o=this,i=[],t=1;t=e.length)r();else{var o=e[t],i=!1;if("script"==o.tagName.toLowerCase()){var a=o,s=document.createElement("script");a.src&&(s.src=a.src,i=!0),a.type&&(s.type=a.type),a.text&&(s.text=a.text),o.id&&(s.id=o.id),o=s}else if("link"==o.tagName.toLowerCase()){var l=o,u=document.createElement("link");l.href&&(u.href=l.href),l.rel&&(u.rel=l.rel),l.type&&(u.type=l.type),o=u}i&&(o.addEventListener("load",function(){return n.loadResourceElements(e,t+1,r)}),o.addEventListener("error",function(){return n.loadResourceElements(e,t+1,r)})),document.head.appendChild(o),i||this.loadResourceElements(e,t+1,r)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(a,e,t){var s=this,r=this.viewModels[a].viewModel,n=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(r,a,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var i=this.viewModels[a].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),u=this.addLeadingSlash(this.concatUrl(i,l)),d=this.getSpaPlaceHolder();if(d){t&&t(this.addLeadingSlash(this.concatUrl(i,this.addLeadingSlash(e))));var v=d.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(u,"GET",v,function(o){if(s.isPostBackStillActive(n)){var i=JSON.parse(o.responseText);s.loadResourceList(i.resources,function(){var e=!1;if("successfulCommand"!==i.action&&i.action){if("redirect"===i.action)return void s.handleRedirect(i,a,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(i);for(var r in s.viewModels[a]={},i)i.hasOwnProperty(r)&&(s.viewModels[a][r]=i[r]);i.viewModelCacheId?s.viewModels[a].viewModelCache=i.viewModel:delete s.viewModels[a].viewModelCache,ko.delaySync.pause(),s.serialization.deserialize(i.viewModel,s.viewModels[a].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[a](s.viewModels[a].viewModel),s.restoreUpdatedControls(i,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var n=new DotvvmSpaNavigatedEventArgs(s.viewModels[a].viewModel,a,i,o);if(s.events.spaNavigated.trigger(n),!e&&!n.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(n)){var t=new DotvvmErrorEventArgs(void 0,r,a,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=u}},e.prototype.handleRedirect=function(e,t,r){var n;void 0===r&&(r=!1),null!=e.replace&&(r=e.replace),n=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(n="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(n="#!/"),this.fixSpaUrlPrefix(n)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,n,r);this.events.redirect.trigger(o),this.performRedirect(n,r,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,r){if(t)location.replace(e);else if(r)this.handleSpaNavigationCore(e);else{var n=this.fakeRedirectAnchor;n||((n=document.createElement("a")).style.display="none",n.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(n),this.fakeRedirectAnchor=n),n.href=e,n.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var r=t.value;return r!==document.location.pathname&&(""===r&&(r="/"),e=r+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var r="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(r)?this.addLeadingSlash(e.substring(r.length)):e},e.prototype.addLeadingSlash=function(e){return 0a[0]&&t[1]'");return e},e.prototype.format=function(e){for(var o=this,i=[],t=1;t=e.length)r();else{var o=e[t],i=!1;if("script"==o.tagName.toLowerCase()){var a=o,s=document.createElement("script");a.src&&(s.src=a.src,i=!0),a.type&&(s.type=a.type),a.text&&(s.text=a.text),o.id&&(s.id=o.id),o=s}else if("link"==o.tagName.toLowerCase()){var l=o,u=document.createElement("link");l.href&&(u.href=l.href),l.rel&&(u.rel=l.rel),l.type&&(u.type=l.type),o=u}i&&(o.addEventListener("load",function(){return n.loadResourceElements(e,t+1,r)}),o.addEventListener("error",function(){return n.loadResourceElements(e,t+1,r)})),document.head.appendChild(o),i||this.loadResourceElements(e,t+1,r)}},e.prototype.getSpaPlaceHolder=function(){var e=document.getElementsByName("__dot_SpaContentPlaceHolder");return 1==e.length?e[0]:null},e.prototype.navigateCore=function(a,e,t){var s=this,r=this.viewModels[a].viewModel,n=this.backUpPostBackConter(),o=new DotvvmSpaNavigatingEventArgs(r,a,e);if(this.events.spaNavigating.trigger(o),!o.cancel){var i=this.viewModels[a].virtualDirectory||"",l="/___dotvvm-spa___"+this.addLeadingSlash(e),u=this.addLeadingSlash(this.concatUrl(i,l)),d=this.getSpaPlaceHolder();if(d){t&&t(this.addLeadingSlash(this.concatUrl(i,this.addLeadingSlash(e))));var v=d.attributes["data-dotvvm-spacontentplaceholder"].value;this.getJSON(u,"GET",v,function(o){if(s.isPostBackStillActive(n)){var i=JSON.parse(o.responseText);s.loadResourceList(i.resources,function(){var e=!1;if("successfulCommand"!==i.action&&i.action){if("redirect"===i.action)return void s.handleRedirect(i,a,!0)}else try{s.isViewModelUpdating=!0;var t=s.cleanUpdatedControls(i);for(var r in s.viewModels[a]={},i)i.hasOwnProperty(r)&&(s.viewModels[a][r]=i[r]);i.viewModelCacheId?s.viewModels[a].viewModelCache=i.viewModel:delete s.viewModels[a].viewModelCache,ko.delaySync.pause(),s.serialization.deserialize(i.viewModel,s.viewModels[a].viewModel),ko.delaySync.resume(),e=!0,s.viewModelObservables[a](s.viewModels[a].viewModel),s.restoreUpdatedControls(i,t,!0),s.isSpaReady(!0)}finally{s.isViewModelUpdating=!1}var n=new DotvvmSpaNavigatedEventArgs(s.viewModels[a].viewModel,a,i,o);if(s.events.spaNavigated.trigger(n),!e&&!n.isHandled)throw"Invalid response from server!"})}},function(e){if(s.isPostBackStillActive(n)){var t=new DotvvmErrorEventArgs(void 0,r,a,e,-1,void 0,!0);s.events.error.trigger(t),t.handled||alert(e.responseText)}})}else document.location.href=u}},e.prototype.handleRedirect=function(e,t,r){var n;void 0===r&&(r=!1),null!=e.replace&&(r=e.replace),n=this.getSpaPlaceHolder()&&!this.useHistoryApiSpaNavigation&&e.url.indexOf("//")<0&&e.allowSpa?("#!"===(n="#!"+this.removeVirtualDirectoryFromUrl(e.url,t))&&(n="#!/"),this.fixSpaUrlPrefix(n)):e.url;var o=new DotvvmRedirectEventArgs(dotvvm.viewModels[t],t,n,r);this.events.redirect.trigger(o),this.performRedirect(n,r,e.allowSpa&&this.useHistoryApiSpaNavigation)},e.prototype.performRedirect=function(e,t,r){if(t)location.replace(e);else if(r)this.handleSpaNavigationCore(e);else{var n=this.fakeRedirectAnchor;n||((n=document.createElement("a")).style.display="none",n.setAttribute("data-dotvvm-fake-id","dotvvm_fake_redirect_anchor_87D7145D_8EA8_47BA_9941_82B75EE88CDB"),document.body.appendChild(n),this.fakeRedirectAnchor=n),n.href=e,n.click()}},e.prototype.fixSpaUrlPrefix=function(e){var t=this.getSpaPlaceHolder().attributes["data-dotvvm-spacontentplaceholder-urlprefix"];if(!t)return e;var r=t.value;return r!==document.location.pathname&&(""===r&&(r="/"),e=r+e),e},e.prototype.removeVirtualDirectoryFromUrl=function(e,t){var r="/"+this.viewModels[t].virtualDirectory;return 0==e.indexOf(r)?this.addLeadingSlash(e.substring(r.length)):e},e.prototype.addLeadingSlash=function(e){return 0() == true || options["doNotPost"]?.Value() == true) + { + // IfInPostbackPath and ServerToClient items should be sent every time because we might not have received them from the client and we still remember their value so they look unchanged + diff[item.Key] = item.Value; + continue; + } + } + var sourceItem = source[item.Key]; if (sourceItem == null) { @@ -97,14 +107,24 @@ public static JObject Diff(JObject source, JObject target, bool nullOnRemoved = } } - // remove abandoned $options foreach (var item in Enumerable.ToArray>(diff)) { if (item.Key.EndsWith("$options", StringComparison.Ordinal)) { - if (diff[item.Key.Remove(item.Key.Length - "$options".Length)] == null) + var propertyName = item.Key.Remove(item.Key.Length - "$options".Length); + + // remove abandoned $options + if (diff[propertyName] == null) { diff.Remove(item.Key); + continue; + } + + // remove firstRequest data + var options = (JObject)item.Value; + if (options["firstRequest"]?.Value() == true) + { + diff.Remove(propertyName); } } } diff --git a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs index dbac2012a2..fc1f8740f3 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/DefaultViewModelSerializer.cs @@ -50,7 +50,7 @@ public string SerializeViewModel(IDotvvmRequestContext context) { if (SendDiff && context.ReceivedViewModelJson != null && context.ViewModelJson["viewModel"] != null) { - context.ViewModelJson["viewModelDiff"] = JsonUtils.Diff((JObject)context.ReceivedViewModelJson["viewModel"], (JObject)context.ViewModelJson["viewModel"], true); + context.ViewModelJson["viewModelDiff"] = JsonUtils.Diff((JObject)context.ReceivedViewModelJson["viewModel"], (JObject)context.ViewModelJson["viewModel"], false); context.ViewModelJson.Remove("viewModel"); } return context.ViewModelJson.ToString(JsonFormatting); diff --git a/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMap.cs b/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMap.cs index fe649cdd11..0fb573ab4a 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMap.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMap.cs @@ -399,6 +399,10 @@ public WriterDelegate CreateWriterFactory() { options["pathOnly"] = true; } + if (!property.TransferAfterPostback) + { + options["firstRequest"] = true; + } } else if (property.TransferToServer) { From 979784fa5f9296c5d36cb6a0881e6b9f8c90dfff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Mon, 11 Nov 2019 12:39:55 +0100 Subject: [PATCH 11/16] ViewModel cache miss - test timing increased --- src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs b/src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs index 7013cb18b7..ad812eba12 100644 --- a/src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs +++ b/src/DotVVM.Samples.Tests/Feature/ViewModelCacheTests.cs @@ -27,18 +27,18 @@ public void Feature_ViewModelCache_ViewModelCacheMiss() AssertUI.TextEquals(requestCount, "0"); // normal postback - browser.ElementAt("input[type=button]", 0).Click().Wait(); + browser.ElementAt("input[type=button]", 0).Click().Wait(1000); AssertUI.TextEquals(result, "1"); AssertUI.TextEquals(requestCount, "1"); // tamper with viewmodel cache id - it should do two requests but it should still work - browser.ElementAt("input[type=button]", 1).Click().Wait(); - browser.ElementAt("input[type=button]", 0).Click().Wait(); + browser.ElementAt("input[type=button]", 1).Click().Wait(1000); + browser.ElementAt("input[type=button]", 0).Click().Wait(1000); AssertUI.TextEquals(result, "2"); AssertUI.TextEquals(requestCount, "3"); // normal postback - browser.ElementAt("input[type=button]", 0).Click().Wait(); + browser.ElementAt("input[type=button]", 0).Click().Wait(1000); AssertUI.TextEquals(result, "3"); AssertUI.TextEquals(requestCount, "4"); }); From e21388e9363a29049c9c0193555c684e929c9a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Mon, 11 Nov 2019 15:20:41 +0100 Subject: [PATCH 12/16] Added relaiable non-forgetting viewmodel store for testing purposes --- .../CommonConfiguration.cs | 3 ++ .../TestingInMemoryViewModelServerStore.cs | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/DotVVM.Samples.Common/Utilities/TestingInMemoryViewModelServerStore.cs diff --git a/src/DotVVM.Samples.Common/CommonConfiguration.cs b/src/DotVVM.Samples.Common/CommonConfiguration.cs index 788e4c7c84..6cf9e1b623 100644 --- a/src/DotVVM.Samples.Common/CommonConfiguration.cs +++ b/src/DotVVM.Samples.Common/CommonConfiguration.cs @@ -2,6 +2,7 @@ using DotVVM.Framework.Compilation.Javascript.Ast; using DotVVM.Framework.Configuration; using DotVVM.Framework.ResourceManagement; +using DotVVM.Framework.ViewModel.Serialization; using DotVVM.Samples.BasicSamples; using DotVVM.Samples.BasicSamples.Controls; using DotVVM.Samples.BasicSamples.ViewModels.FeatureSamples.StaticCommand; @@ -36,6 +37,8 @@ public static void ConfigureServices(IDotvvmServiceCollection dotvvmServices) services.AddScoped(); services.AddSingleton(); services.AddSingleton(); + + services.AddSingleton(); } private static void RegisterResources(DotvvmResourceRepository resources) diff --git a/src/DotVVM.Samples.Common/Utilities/TestingInMemoryViewModelServerStore.cs b/src/DotVVM.Samples.Common/Utilities/TestingInMemoryViewModelServerStore.cs new file mode 100644 index 0000000000..6148e21821 --- /dev/null +++ b/src/DotVVM.Samples.Common/Utilities/TestingInMemoryViewModelServerStore.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using DotVVM.Framework.ViewModel.Serialization; + +namespace DotVVM.Samples.Common.Utilities +{ + /// + /// Server-side viewmodel cache that never removes cached items. Do not use this in production environment - this class should be used only for the test purposes. + /// + public class TestingInMemoryViewModelServerStore : IViewModelServerStore + { + private readonly ConcurrentDictionary cache = new ConcurrentDictionary(); + + public byte[] Retrieve(string hash) + { + return cache.TryGetValue(BuildKey(hash), out var data) ? data : null; + } + + public void Store(string hash, byte[] cacheData) + { + cache.GetOrAdd(BuildKey(hash), cacheData); + } + + private static string BuildKey(string hash) + { + return hash; + } + + } +} From 75b3643843ca057fc323ecaa86bd1d37185b10c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Mon, 11 Nov 2019 16:23:34 +0100 Subject: [PATCH 13/16] GitHub API test timing fixes --- src/DotVVM.Samples.Tests/Feature/ApiTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DotVVM.Samples.Tests/Feature/ApiTests.cs b/src/DotVVM.Samples.Tests/Feature/ApiTests.cs index 20e844f1e8..8e442a9443 100644 --- a/src/DotVVM.Samples.Tests/Feature/ApiTests.cs +++ b/src/DotVVM.Samples.Tests/Feature/ApiTests.cs @@ -27,6 +27,7 @@ public void Feature_Api_GithubRepoApi() }, 10000); // check dotvvm repo issues + browser.Wait(2000); var dotvvmIssues = browser.First("table").FindElements("tr").Skip(1).ToList(); Assert.True(dotvvmIssues.Count > 10); From 63ff1213596fed289916fef32e83119f320363a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Mon, 11 Nov 2019 16:23:48 +0100 Subject: [PATCH 14/16] ASP.NET Core 3.0 - server-side viewmodel fixes --- src/DotVVM.Framework/Hosting/DotvvmPresenter.cs | 2 +- .../Hosting/DotvvmRequestContextExtensions.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/DotVVM.Framework/Hosting/DotvvmPresenter.cs b/src/DotVVM.Framework/Hosting/DotvvmPresenter.cs index 4a505d2e3c..27edc07dde 100644 --- a/src/DotVVM.Framework/Hosting/DotvvmPresenter.cs +++ b/src/DotVVM.Framework/Hosting/DotvvmPresenter.cs @@ -294,7 +294,7 @@ public async Task ProcessRequestCore(IDotvvmRequestContext context) catch (DotvvmInterruptRequestExecutionException ex) when (ex.InterruptReason == InterruptReason.CachedViewModelMissing) { // the client needs to repeat the postback and send the full viewmodel - context.SetCachedViewModelMissingResponse(); + await context.SetCachedViewModelMissingResponse(); throw; } catch (DotvvmInterruptRequestExecutionException) { throw; } diff --git a/src/DotVVM.Framework/Hosting/DotvvmRequestContextExtensions.cs b/src/DotVVM.Framework/Hosting/DotvvmRequestContextExtensions.cs index b622fbe079..41bfdec2f0 100644 --- a/src/DotVVM.Framework/Hosting/DotvvmRequestContextExtensions.cs +++ b/src/DotVVM.Framework/Hosting/DotvvmRequestContextExtensions.cs @@ -14,6 +14,7 @@ using DotVVM.Framework.ViewModel.Serialization; using Microsoft.Extensions.DependencyInjection; using System.Threading; +using System.Threading.Tasks; using DotVVM.Framework.Routing; using DotVVM.Framework.Hosting; @@ -132,11 +133,11 @@ public static void RedirectToRoutePermanent(this IDotvvmRequestContext context, public static void SetRedirectResponse(this IDotvvmRequestContext context, string url, int statusCode = (int)HttpStatusCode.Redirect, bool replaceInHistory = false, bool allowSpaRedirect = false) => context.Configuration.ServiceProvider.GetRequiredService().WriteRedirectResponse(context.HttpContext, url, statusCode, replaceInHistory, allowSpaRedirect); - internal static void SetCachedViewModelMissingResponse(this IDotvvmRequestContext context) + internal static Task SetCachedViewModelMissingResponse(this IDotvvmRequestContext context) { context.HttpContext.Response.StatusCode = 200; context.HttpContext.Response.ContentType = "application/json"; - context.HttpContext.Response.Write(DefaultViewModelSerializer.GenerateMissingCachedViewModelResponse()); + return context.HttpContext.Response.WriteAsync(DefaultViewModelSerializer.GenerateMissingCachedViewModelResponse()); } /// From 6477241bfe65920d9d2bfe4b5114e6c8ea67e292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Sat, 9 Nov 2019 19:15:35 +0100 Subject: [PATCH 15/16] Bug in RouteLink query param tests fixed --- src/DotVVM.Samples.Tests/Control/RouteLinkTests.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/DotVVM.Samples.Tests/Control/RouteLinkTests.cs b/src/DotVVM.Samples.Tests/Control/RouteLinkTests.cs index dfe9c94f2b..fb581f299d 100644 --- a/src/DotVVM.Samples.Tests/Control/RouteLinkTests.cs +++ b/src/DotVVM.Samples.Tests/Control/RouteLinkTests.cs @@ -102,7 +102,9 @@ public void Control_RouteLink_QueryParameters_DefaultValue() browser.NavigateToUrl(SamplesRouteUrls.ControlSamples_RouteLink_RouteLinkQueryParameters); browser.First(".link").Click(); - AssertUI.Url(browser, "/ControlSamples/RouteLink/RouteLinkQueryParameters?int=5&string=default", UrlKind.Relative, UriComponents.PathAndQuery); + AssertUI.Url(browser, + u => u.EndsWith("/ControlSamples/RouteLink/RouteLinkQueryParameters?int=5&string=default") + || u.EndsWith("/ControlSamples/RouteLink/RouteLinkQueryParameters?string=default&int=5")); }); } @@ -116,7 +118,9 @@ public void Control_RouteLink_QueryParameters_CommandChangedValue() browser.First(".command").Click(); browser.First(".link").Click(); - AssertUI.Url(browser, "/ControlSamples/RouteLink/RouteLinkQueryParameters?int=7&string=change", UrlKind.Relative, UriComponents.PathAndQuery); + AssertUI.Url(browser, + u => u.EndsWith("/ControlSamples/RouteLink/RouteLinkQueryParameters?int=7&string=change") + || u.EndsWith("/ControlSamples/RouteLink/RouteLinkQueryParameters?string=change&int=7")); }); } @@ -130,7 +134,8 @@ public void Control_RouteLink_QueryParameters_StaticCommandChangedValue() browser.First(".static-command").Click(); browser.First(".link").Click(); - AssertUI.Url(browser, "/ControlSamples/RouteLink/RouteLinkQueryParameters?int=6&string=change_static", UrlKind.Relative, UriComponents.PathAndQuery); + AssertUI.Url(browser, u => u.EndsWith("/ControlSamples/RouteLink/RouteLinkQueryParameters?int=6&string=change_static") + || u.EndsWith("/ControlSamples/RouteLink/RouteLinkQueryParameters?string=change_static&int=6")); }); } From 7d7289a97f295723e00b7a879ba45445c15c987c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg?= Date: Tue, 26 Nov 2019 18:53:56 +0100 Subject: [PATCH 16/16] Serializer - $options.firstRequest annotation added only when server-side viewmodels are enabled --- .../ViewModel/ViewModelSerializationMapperTests.cs | 4 +++- .../ViewModel/Serialization/ViewModelSerializationMap.cs | 8 ++++++-- .../Serialization/ViewModelSerializationMapper.cs | 6 ++++-- .../ViewModel/Validation/ValidationErrorFactory.cs | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/DotVVM.Framework.Tests.Common/ViewModel/ViewModelSerializationMapperTests.cs b/src/DotVVM.Framework.Tests.Common/ViewModel/ViewModelSerializationMapperTests.cs index 4bd66d8de7..124e461245 100644 --- a/src/DotVVM.Framework.Tests.Common/ViewModel/ViewModelSerializationMapperTests.cs +++ b/src/DotVVM.Framework.Tests.Common/ViewModel/ViewModelSerializationMapperTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using DotVVM.Framework.Configuration; using DotVVM.Framework.ViewModel; using DotVVM.Framework.ViewModel.Serialization; using DotVVM.Framework.ViewModel.Validation; @@ -18,7 +19,8 @@ public void ViewModelSerializationMapper_Name_JsonPropertyVsBindAttribute() { var mapper = new ViewModelSerializationMapper(new ViewModelValidationRuleTranslator(), new AttributeViewModelValidationMetadataProvider(), - new DefaultPropertySerialization()); + new DefaultPropertySerialization(), + DotvvmConfiguration.CreateDefault()); var map = mapper.GetMap(typeof(JsonPropertyVsBindAttribute)); Assert.AreEqual("NoAttribute", map.Property("NoAttribute").Name); diff --git a/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMap.cs b/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMap.cs index 0fb573ab4a..0dd2f3b349 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMap.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMap.cs @@ -5,6 +5,7 @@ using DotVVM.Framework.Utils; using Newtonsoft.Json; using System.Reflection; +using DotVVM.Framework.Configuration; namespace DotVVM.Framework.ViewModel.Serialization { @@ -13,6 +14,8 @@ namespace DotVVM.Framework.ViewModel.Serialization /// public class ViewModelSerializationMap { + private readonly DotvvmConfiguration configuration; + public delegate void ReaderDelegate(JsonReader reader, JsonSerializer serializer, object value, EncryptedValuesReader encryptedValuesReader); public delegate void WriterDelegate(JsonWriter writer, object obj, JsonSerializer serializer, EncryptedValuesWriter evWriter, bool isPostback); @@ -29,8 +32,9 @@ public class ViewModelSerializationMap /// /// Initializes a new instance of the class. /// - public ViewModelSerializationMap(Type type, IEnumerable properties) + public ViewModelSerializationMap(Type type, IEnumerable properties, DotvvmConfiguration configuration) { + this.configuration = configuration; Type = type; Properties = properties.ToList(); } @@ -399,7 +403,7 @@ public WriterDelegate CreateWriterFactory() { options["pathOnly"] = true; } - if (!property.TransferAfterPostback) + if (configuration.ExperimentalFeatures.ServerSideViewModelCache.IsEnabledForAnyRoute() && !property.TransferAfterPostback) { options["firstRequest"] = true; } diff --git a/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMapper.cs b/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMapper.cs index 48e4987a4f..df5094344d 100644 --- a/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMapper.cs +++ b/src/DotVVM.Framework/ViewModel/Serialization/ViewModelSerializationMapper.cs @@ -19,13 +19,15 @@ public class ViewModelSerializationMapper : IViewModelSerializationMapper private readonly IValidationRuleTranslator validationRuleTranslator; private readonly IViewModelValidationMetadataProvider validationMetadataProvider; private readonly IPropertySerialization propertySerialization; + private readonly DotvvmConfiguration configuration; public ViewModelSerializationMapper(IValidationRuleTranslator validationRuleTranslator, IViewModelValidationMetadataProvider validationMetadataProvider, - IPropertySerialization propertySerialization) + IPropertySerialization propertySerialization, DotvvmConfiguration configuration) { this.validationRuleTranslator = validationRuleTranslator; this.validationMetadataProvider = validationMetadataProvider; this.propertySerialization = propertySerialization; + this.configuration = configuration; } private readonly ConcurrentDictionary serializationMapCache = new ConcurrentDictionary(); @@ -36,7 +38,7 @@ public ViewModelSerializationMapper(IValidationRuleTranslator validationRuleTran ///
protected virtual ViewModelSerializationMap CreateMap(Type type) { - return new ViewModelSerializationMap(type, GetProperties(type)); + return new ViewModelSerializationMap(type, GetProperties(type), configuration); } /// diff --git a/src/DotVVM.Framework/ViewModel/Validation/ValidationErrorFactory.cs b/src/DotVVM.Framework/ViewModel/Validation/ValidationErrorFactory.cs index bab1ea4b67..2b58231244 100644 --- a/src/DotVVM.Framework/ViewModel/Validation/ValidationErrorFactory.cs +++ b/src/DotVVM.Framework/ViewModel/Validation/ValidationErrorFactory.cs @@ -44,7 +44,7 @@ public static ValidationResult CreateValidationResult(this T vm, string error private static JavascriptTranslator defaultJavaScriptTranslator = new JavascriptTranslator( Options.Create(new JavascriptTranslatorConfiguration()), - new ViewModelSerializationMapper(new ViewModelValidationRuleTranslator(), new AttributeViewModelValidationMetadataProvider(), new DefaultPropertySerialization())); + new ViewModelSerializationMapper(new ViewModelValidationRuleTranslator(), new AttributeViewModelValidationMetadataProvider(), new DefaultPropertySerialization(), DotvvmConfiguration.CreateDefault())); public static ValidationResult CreateValidationResult(ValidationContext validationContext, string error, params Expression>[] expressions) {