/// <summary>
        /// If the current user-agent in the request is eligible for ES5 fallback, return true
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public static bool BrowserNeedES5Fallback(this HttpRequest request)
        {
            BlazorPolyfillOptions _options = BlazorPolyfillMiddlewareExtensions.GetOptions();

            if (_options.ForceES5Fallback)
            {
                return(true);
            }

            //In this case, the user is responsible from any delegate crash.
            //As it does have control over the delegate, the error will be visible for him.
            //If this call does not return true, we must continue the regular checking workflow
            if (_options.ES5FallbackValidation != null && _options.ES5FallbackValidation(request))
            {
                return(true);
            }

            string userAgent = request.Headers["User-Agent"];

            if (userAgent == null)
            {
                return(false);
            }

            return(IsInternetExplorer(userAgent) ||
                   IsEdgeHTML(userAgent));
        }
Example #2
0
        public static IApplicationBuilder UseBlazorPolyfill(
            this IApplicationBuilder builder, Action <BlazorPolyfillOptions> configureOptions)
        {
            if (builder is null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (configureOptions is null)
            {
                throw new ArgumentNullException(nameof(configureOptions));
            }

            BlazorPolyfillOptions options = new BlazorPolyfillOptions();

            //Let the user configure the options in it's delegate method
            configureOptions(options);

            return(UseBlazorPolyfill(builder, options));
        }
Example #3
0
        private static FileContentReference GetPatchedBlazorServerFile()
        {
            if (_patchedBlazorServerFile == null)
            {
                BlazorPolyfillOptions option = GetOptions();

                if (option.UsePackagedBlazorServerLibrary)
                {
                    //Get packaged blazor.server.js
                    var assembly = GetBlazorPolyfillAssembly();

                    var resources    = assembly.GetManifestResourceNames();
                    var resourceName = resources.Single(str => str.EndsWith("blazor.server.packaged.js"));

                    using (Stream stream = assembly.GetManifestResourceStream(resourceName))
                    {
                        using (StreamReader reader = new StreamReader(stream))
                        {
                            string js = reader.ReadToEnd();

                            string Etag = CryptographyHelper.CreateSHA256(js);

                            //We should rely on ETag and not on LastModifed informations
                            DateTime buildTime = GetAssemblyCreationDate(assembly);

                            _patchedBlazorServerFile = new FileContentReference()
                            {
                                Value         = js,
                                ETag          = Etag,
                                LastModified  = buildTime,
                                ContentLength = System.Text.UTF8Encoding.UTF8.GetByteCount(js).ToString(CultureInfo.InvariantCulture)
                            };
                        }
                    }
                }
                else
                {
                    var assembly = GetAspNetCoreComponentsServerAssembly();

                    var resources    = assembly.GetManifestResourceNames();
                    var resourceName = resources.Single(str => str.EndsWith("blazor.server.js"));

                    using (Stream stream = assembly.GetManifestResourceStream(resourceName))
                    {
                        using (StreamReader reader = new StreamReader(stream))
                        {
                            string js = reader.ReadToEnd();


                            #region Patch Regex

                            //Patch Descriptor Regex as it make Babel crash during transform
                            js = js.Replace("/\\W*Blazor:[^{]*(?<descriptor>.*)$/;", @"/[\0-\/:-@\[-\^`\{-\uFFFF]*Blazor:(?:(?!\{)[\s\S])*(.*)$/;");

                            js = js.Replace("/^\\s*Blazor-Component-State:(?<state>[a-zA-Z0-9\\+\\/=]+)$/", @"/^[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*Blazor\x2DComponent\x2DState:([\+\/-9=A-Za-z]+)$/");

                            js = js.Replace("/^\\s*Blazor:[^{]*(?<descriptor>.*)$/", @"/^[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*Blazor:(?:(?!\{)[\s\S])*(.*)$/");

                            #endregion Patch Regex

                            //Transpile code to ES5 for IE11 before manual patching
                            js = BabelHelper.Transform(js, "blazor.server.js");

                            #region Regex named groups fix

                            //At this point, Babel has unminified the code, and fixed IE11 issues, like 'import' method calls.

                            //We still need to fix 'descriptor' regex evaluation code, as it was expecting a named capture group.
                            js = Regex.Replace(js, "([_a-zA-Z0-9]+)(.groups[ ]*&&[ ]*[_a-zA-Z0-9]+.groups.descriptor)", "$1[1]");

                            //We still need to fix 'state' regex evaluation code, as it was expecting a named capture group.
                            js = Regex.Replace(js, "([_a-zA-Z0-9]+)(.groups[ ]*&&[ ]*[_a-zA-Z0-9]+.groups.state)", "$1[1]");

                            //Here we fix invalids interopRequireWildcard(require(''.concat(n))) to _interopRequireWildcard(''.concat(n)) (works for '' or "")
                            //Warning: " is written "" here but must be read as " from the regex logic: We are in a verbatim string
                            js = Regex.Replace(js, @"(require\((['""]['""].concat\([a-zA-Z]+\))\))", "$2");

                            #endregion Regex named groups fix

                            //Minify with AjaxMin (we don't want an additional external tool with NPM or else for managing this
                            //kind of thing here...
                            js = Uglify.Js(js).Code;

                            //Computing ETag. Should be computed last !
                            string Etag = CryptographyHelper.CreateSHA256(js);

                            //Computing Build time for the Last-Modified Http Header
                            //We should rely on the creation date of the Microsoft API
                            //not the Blazor.Polyfill.Server one as the Microsoft.AspNetCore.Components.Server
                            //assembly may be updated in time. We will rely on the current creation/modification date on disk
                            DateTime buildTime = GetAssemblyCreationDate(assembly);

                            _patchedBlazorServerFile = new FileContentReference()
                            {
                                Value         = js,
                                ETag          = Etag,
                                LastModified  = buildTime,
                                ContentLength = System.Text.UTF8Encoding.UTF8.GetByteCount(js).ToString(CultureInfo.InvariantCulture)
                            };
                        }
                    }
                }
            }

            return(_patchedBlazorServerFile);
        }
Example #4
0
        public static IApplicationBuilder UseBlazorPolyfill(
            this IApplicationBuilder builder, BlazorPolyfillOptions options)
        {
            if (builder is null)
            {
                throw new ArgumentNullException(nameof(builder));
            }

            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            SetOptions(options);

            InitReact(builder);

            //This is a kind of hack for caching the data at boot
            //It's better to prevent anything at first request by caching instead of
            //making the user wait the file generation
            builder.Use((context, next) =>
            {
                if (!IsBlazorPolyfillLibCached())
                {
                    //Avoiding first client to await the file generation
                    CacheBlazorPolyfillLib();
                }

                //Normal behavior
                return(next());
            });

            //This is used in order to transpile dynamically StaticFiles to ES5 when needed
            builder.UseMiddleware <ECMAScript5Middleware>();

            //BrowserNeedES5Fallback is written hiere and not in the builder
            //because we only want to use MapWhen when we need ES5 fallback.
            //If this is false, the request will be redirected to the Microsoft
            //default request management for this file.
            builder.MapWhen(ctx =>
                            ECMAScript5Middleware.RequestedFileIsBlazorServerJS(ctx.Request) &&
                            ctx.Request.BrowserNeedES5Fallback(),
                            subBuilder =>
            {
                subBuilder.Run(async(context) =>
                {
                    var fileContent = GetPatchedBlazorServerFile();
                    await HttpRequestManager.ManageRequest(context, fileContent);
                });
            });

            //As blazor.polyfill.js files does not exist really on theses path and
            //does not have a real fallback request mangement (as for blazor.server.js)
            //we should intercept them at any time with MapWhen, but change the result
            //behavior lately in the builder. Otherwise this would return a 404 error.
            builder.MapWhen(ctx =>
                            ECMAScript5Middleware.RequestedFileIsBlazorPolyfill(ctx.Request, out bool isMinified),
                            subBuilder =>
            {
                subBuilder.Run(async(context) =>
                {
                    ECMAScript5Middleware.RequestedFileIsBlazorPolyfill(context.Request, out bool isMinified);

                    //Eval if the requested file is the minified version or not
                    var fileContent = GetBlazorPolyfillFile(context.Request.BrowserNeedES5Fallback(), isMinified);
                    await HttpRequestManager.ManageRequest(context, fileContent);
                });
            });

            return(builder);
        }
Example #5
0
 private static void SetOptions(BlazorPolyfillOptions options)
 {
     _options = options;
 }
        private static FileContentReference GetPatchedBlazorServerFile()
        {
            if (_patchedBlazorServerFile == null)
            {
                BlazorPolyfillOptions option = GetOptions();

                if (option.UsePackagedBlazorServerLibrary)
                {
                    //Get packaged blazor.server.js
                    var assembly = GetBlazorPolyfillAssembly();

                    var resources    = assembly.GetManifestResourceNames();
                    var resourceName = resources.Single(str => str.EndsWith("blazor.server.packaged.js"));

                    using (Stream stream = assembly.GetManifestResourceStream(resourceName))
                    {
                        using (StreamReader reader = new StreamReader(stream))
                        {
                            string js = reader.ReadToEnd();

                            string Etag = EtagGenerator.GenerateEtagFromString(js);

                            //Computing Build time for the Last-Modified Http Header
                            //We should rely on the creation date of the Microsoft API
                            //not the Blazor.Polyfill.Server one as the Microsoft.AspNetCore.Components.Server
                            //assembly may be updated in time. We will rely on the current creation/modification date on disk
                            DateTime buildTime = GetAssemblyCreationDate(assembly);

                            _patchedBlazorServerFile = new FileContentReference()
                            {
                                Value         = js,
                                ETag          = Etag,
                                LastModified  = buildTime,
                                ContentLength = System.Text.UTF8Encoding.UTF8.GetByteCount(js).ToString(CultureInfo.InvariantCulture)
                            };
                        }
                    }
                }
                else
                {
                    var assembly = GetAspNetCoreComponentsServerAssembly();

                    var resources    = assembly.GetManifestResourceNames();
                    var resourceName = resources.Single(str => str.EndsWith("blazor.server.js"));

                    using (Stream stream = assembly.GetManifestResourceStream(resourceName))
                    {
                        using (StreamReader reader = new StreamReader(stream))
                        {
                            string js = reader.ReadToEnd();

                            //Patch Descriptor Regex as it make Babel crash during transform
                            js = js.Replace("/\\W*Blazor:[^{]*(?<descriptor>.*)$/;", @"/[\0-\/:-@\[-\^`\{-\uFFFF]*Blazor:(?:(?!\{)[\s\S])*(.*)$/;");

                            //Transpile code to ES5 for IE11 before manual patching
                            js = Transform(js, "blazor.server.js", "{\"plugins\":[\"proposal-class-properties\",\"proposal-object-rest-spread\"],\"presets\":[[\"env\",{\"targets\":{\"browsers\":[\"ie 11\"]}}], \"es2015\",\"es2016\",\"es2017\",\"stage-3\"], \"sourceType\": \"script\"}");

                            //At this point, Babel has unminified the code, and fixed IE11 issues, like 'import' method calls.
                            //We still need to fix 'descriptor' regex evaluation code, as it was expecting a named capture group.
                            js = Regex.Replace(js, "([a-zA-Z]+)(.groups[ ]*&&[ ]*[a-zA-Z]+.groups.descriptor)", "$1[1]");

                            //Minify with AjaxMin (we don't want an additional external tool with NPM or else for managing this
                            //kind of thing here...
                            js = Uglify.Js(js).Code;

                            //Computing ETag. Should be computed last !
                            string Etag = EtagGenerator.GenerateEtagFromString(js);

                            //Computing Build time for the Last-Modified Http Header
                            //We should rely on the creation date of the Microsoft API
                            //not the Blazor.Polyfill.Server one as the Microsoft.AspNetCore.Components.Server
                            //assembly may be updated in time. We will rely on the current creation/modification date on disk
                            DateTime buildTime = GetAssemblyCreationDate(assembly);

                            _patchedBlazorServerFile = new FileContentReference()
                            {
                                Value         = js,
                                ETag          = Etag,
                                LastModified  = buildTime,
                                ContentLength = System.Text.UTF8Encoding.UTF8.GetByteCount(js).ToString(CultureInfo.InvariantCulture)
                            };
                        }
                    }
                }
            }

            return(_patchedBlazorServerFile);
        }