Progressive Web Apps in Chrome

Progressive Web Apps (PWA) are HTML based applications that run as though they are desktop applications.  Google Chrome received support for PWAs on Chrome OS in May with the release of Chrome 67.  Linux and Windows received support in August with the release of Chrome 70.  Support for Mac OS X is yet to come.

Download code (415 KB)

siderealLarge

One of the first differences that stands out for PWAs is that they can run in their own application window and are indistinguishable from other applications running on a machine. That difference is largely visual. But the differences extend well beyond what is visible. Resources that are not usually available to an HTML page are available to a PWA such as access to Bluetooth, serial ports, UDP networking, and more.  Chrome PWAs can be installed and have their own icon in your programs menu and function offline.

There are requirements that must be satisfied before an HTML page can be installed as a PWA.  These are the conditions that must be met.

  • The page must be served over SSL/HTTPS.
  • The page must have a service worker with a fetch handler.
  • User engagement requirements must be met (interaction with the domain for at least 30 seconds).
  • A manifest must be present.
    • 192px and 512px icons must be included.
    • Application must have a short name and long name.
    • The display mode must be specified.
    • start_url must be specified.

 

If all of these requirements are met Chrome will trigger a beforeinstallprompt event for the web page. Once this event is triggered your application can present the user with an install prompt.  Depending on the Chrome version your application may be able to suppress this prompt and display it to the user later (allowing you to decide where in the interaction flow that the prompt shows up) or your app might not be allowed to suppress it.

I’ll make a minimilastic application that satisfies the requirements for being a PWA.  The application that I’ll make will calculate sidereal time. Sidereal time is a time tracking system used by astronomers and is always expressed in 24 hour format. The usual system of tracking time was formed around trying to map the time of the day to the position of the sun (solar time, though it is far less than perfect). Sidereal time is based on the position of the stars relative to the observer. I will not talk much about the algorithm behind this calculation much here. I talked about calculating sidereal time in an application I had made for the now defunct Windows Phone 7; while that OS is no more the description I gave on how sidereal time works is still applicable.

Using SVG I’ve made a simple 24 hour clock face. The clock face is really there for aesthetics. Chances are if you try to read the hands of the clock the hour hand will cause confusion since it’s position on a 24 hour clock will not meet expectations that have been formed from being able to read a 12 hour clock.  The digital readout is the part that will actually give the information of interest. Every second the time is updated and the hands animate to their new position. There’s also a gear icon for opening the settings interface.

sampleApp

Satisfying the SSL/HTTP Requirement

A lot of the necessary features are only available if your application is being served over SSL. If you don’t see HTTPS in the address bar then these features simply will not work. To satisfy this requirement for now I’m using Google Firebase and the temporary URL that it has assigned to me. I don’t plan on keeping this URL forever, but at the time of this post you can play with the application over at https://siderealtimepiece.firebaseapp.com.

Satisfying Manifest Resources Requirements

The manifest for my application is in the root directory of the application. It is a JSON formatted file with information on where the program icons can be found, the starting URL, and the name of the application as it should appear on the user’s machine.

{
    "short_name": "Sidereal",
    "name": "Sidereal Time Piece",
    "icons": [
      {
        "src": "./images/sidereal192.png",
        "type": "image/png",
        "sizes": "192x192"
      },
      {
        "src": "./images/sidereal512.png",
        "type": "image/png",
        "sizes": "512x512"
      }
    ],
    "start_url": "index.html?pwa=true",
    "background_color": "#000080",
    "display": "standalone",
    "scope": "./",
    "theme_color": "#FFFFFF"
  }
  

The Service Worker

To satisfy the service worker requirement there’s a JavaScript file in the root of this application’s files named sw.js. The service worker works in the background behind the page. For this application we only want the service worker to do two things; respond to an install event by caching the required files locally and serve up those files when needed. The list of the files that are to be cached are in an array named urlsToCache. When the service worker response to the install event it will pass this list of URLs to a call of the addAll method on the cache object. The cache object will then download the resources at these URLs and save them locally where we can use them offline.

var CACHE_NAME = 'siderealclock-cache';
var urlsToCache = [
  './',
  './styles/main.css',
  './scripts/app.js',
  './scripts/jquery-3.3.1.min.js',
  './images/sidereal192.png',
  './images/sidereal512.png',
  './images/siderealLarge.png',
  './404.html'
];

self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

For the fetch event I’m using code from a Google recommendation. This handler will serve the contents from the cache when there is a cache hit and also add new files to the cache when a request is made for a file that isn’t already there.

self.addEventListener('fetch', function(event) {
    event.respondWith(
      caches.match(event.request)
        .then(function(response) {
          // Cache hit - return response
          if (response) {
            return response;
          }
  
          // IMPORTANT: Clone the request. A request is a stream and
          // can only be consumed once. Since we are consuming this
          // once by cache and once by the browser for fetch, we need
          // to clone the response.
          var fetchRequest = event.request.clone();
  
          return fetch(fetchRequest).then(
            function(response) {
              // Check if we received a valid response
              if(!response || response.status !== 200 || response.type !== 'basic') {
                return response;
              }
  
              // IMPORTANT: Clone the response. A response is a stream
              // and because we want the browser to consume the response
              // as well as the cache consuming the response, we need
              // to clone it so we have two streams.
              var responseToCache = response.clone();
  
              caches.open(CACHE_NAME)
                .then(function(cache) {
                  cache.put(event.request, responseToCache);
                });
  
              return response;
            }
          );
        })
      );
  });
  

This file must be registered as the service worker for it to be able to do anything. In one of the JavaScript files loaded by the page I check the navigator object to ensure there is a serviceWorker member (if there isn’t then the browser in which the code is running currently doesn’t support service workers). If it is there then the service worker can be registered with navigator.serviceWorker.register(path_to_service_worker).

if('serviceWorker' in navigator) {
    navigator.serviceWorker
             .register('./sw.js')
             .then(function() { console.log("Service Worker Registered"); });
  }

Handling the Install Prompt

If your code is running on a Chrome implementation that supports it you can defer the presentation of the installation prompt. In my case I’ve decided to defer it and make a button available in the settings UI. The variable installPrompt will hold the reference to the event object that when activated will present the user with the Chrome install UI. When the event is raised the variable is populated with the event object and the install button within my settings UI is made visible.

var installPrompt;


function beforeInstall(e) { 
    console.log('beforeInstallPrompt()')
    e.preventDefault();
    installPrompt = e;
    $('.installUI').show();
}


window.addEventListener('beforeinstallprompt', beforeInstall);
    $('.installButton').on('click', function(){
        installPrompt.prompt();
        installPrompt.userChoice
          .then((choiceResult) => {
            $('.installUI').hide();
            installPrompt = null;
          });
      
    });

Testing the application on Chrome on Ubuntu Linux when I select my install button the Chrome install prompt shows.

Chrome Desktop Install Prompt
The Install Prompt that shows on Google Chrome on a desktop

Program Launchers on the Desktop

On the desktop once installed the icon for the PWA shows up in the computer’s program launcher.  It also shows up in the Chrome app list. When launched since this application was made to run in standalone mode the application runs in it’s own window with the OS appropriate buttons for going full screen, minimizing, and closing the window. My test application uses location services to acquire the longitude at which the sidereal time is being calculated. When run in a regular browser window I’m prompted each time I visit the page to give permission for location information. This gets a little annoying after a while. When the application is running in stand alone mode the application’s border shows an icon indicating that the location is being detected. Clicking on the icon gives the user the ability to change the location permissions for the application.

Samsung Internet Compatibility

Samsung Internet, the default browser for a long period on many Samsung phones, also supports PWAs. (Samsung Internet can also be installed on non-Samsung phones). Samsung Internet is a Chromium based browser and Samsung is one of the contributors to the Chromium project.  It may come as no surprise that no code changes are necessary for this application to work on  The UI it presents for installing PWAs is different than what Chrome presents. When Samsung Internet detects that a page can be installed as a PWA an icon is shown in the address bar that resembles a house with a plus in the center. Selecting it will add the icon to the home screen. The icon shows with a smaller image of the Samsung Internet icon indicating that it is a PWA.  The beforeinstallprompt event will never be triggered. Since the presentation of the custom install button was driven by this event it simply will not show.

SamsungPWACentered

Adding iOS Compatibility

If you saw the original iPhone announcement back in 2007 Steve Jobs had announced that making apps for the iPhone could be done with HTML; at the time there was no SDK available to developers and if they wanted to target the iPhone they were making a web app that had an icon on the home screen. From 2007 to 2018 Apple didn’t do much to advance the platform. It wasn’t until March 2018 that Apple made significant updates to their support to HTML based applications. Apple added support for web manifest, and services workers, web assembly, and other features.

There’s not 100% parity between iOS and Android for available features in PWA. On iOS storage is limited to 50MB per app. On Android the application can request more storage. Android BWAs also have access to Bluetooth features, speech recognition, background sync, and other features. For my sample application none of these mentioned differences matter. While the Android implementations have UI notifications that let the user know that the app can be installed on iOS there’s no visual notification. To install the application the user must select the share option and add the page to their home screen.

Safari ignores most of the attributes of the manifest. It also doesn’t save state if the user leaves the application. So the developers must make their own implementation to save state as the user jumps in and out of the application. If you want a custom icon to show in Safari for your application Apple has a document on specifying the icon using the link tag. An icon can be specified like the following.

    <link rel="apple-touch-icon"  href="./images/icons/apple-icon-57x57.png">

If you want to specify multiple icons and allow the phone to select the most appropriate one for the user’s resolution add a sizes attribute to the tag.

   <link rel="apple-touch-icon" sizes="57x57" href="./images/icons/apple-icon-57x57.png">
    <link rel="apple-touch-icon" sizes="60x60" href="./images/icons/apple-icon-60x60.png">
    <link rel="apple-touch-icon" sizes="72x72" href="./images/icons/apple-icon-72x72.png">

My clock icon for the program shows up in the iPhone favourites list as the following.

FavouriteIcon

Offline Functionality

This application doesn’t need the internet for any functionality. It’s only inputs are the current local time and the user’s longitude. With the lack of need for any network resources and the service worker caching the required files for the application it will work just fine offline after it has been installed. If you make an application that requires network access you will want to give some thought to what to do when there is no data connection. Even if the application can’t do anything without a connection it would be better to show a friendly message than to just let the application not work.

An Alternative to the App Store

PWAs longtime might turn out to be a good alternative to app stores for some types of applications. Whether or not it is a good fit for the needs that you have will depend on the functionality that your applications require and what is available on the devices that you need to target. Apple appears to be behind on this front at the moment. But I hope that the attention that they’ve put on the platform this year to be indicative of future efforts. I’m personally am interested in what could be done when PWAs and WebAssembly are combined together. These are topics to which I hope to give a good bit of attention over the following months.

Advertisements

Raspberry Pi 4 Announced

Raspberry Pi 4
Raspberry Pi 4

The fourth generation of the Raspberry Pi has been announced. Each generation of the Raspberry Pi is primarily identified by its specifications. (Not including the Raspberry Pi Compute module because it generally is not used by hobbyist). With the Raspberry Pi 4, this isn’t the case. There are three variations available. The new Raspberry Pi 4 comes with a 1.5 GHz ARM Cortex-A72 quad-core processor.  With that processor the Raspberry Pi 4 can decode 4K video at 60 FPS or two 4K videos at 30 FPS. The amount of RAM available to the unit depends on the version. The smallest amount of RAM, 1 gig, is available for $35 USD. The next size, 2 gigs, can be purchased for $45 USD. The largest unit, 4 gigs, is $55 USD.

At first glance, the unit will be recognized as a Raspberry PI but a closer look at the ports will show some immediate differences. The Pi has converted from a micro-USB port to USB-C. The full sized HDMI port is gone and has been replaced with two micro-HDMI ports. The unit can drive two displays at once.  A couple of the 4 USB ports have been upgraded to USB 3 while the other two are still USB 2. The wireless capabilities are upgraded to use USB 5.0 and dual-band 802.11ac Wi-Fi.

 

The unit is available for purchase from Raspberry Pi’s site now.  A new case for the Pi 4 and a USB-C power supply of appropriate wattage are both available through the site as well.

 

https://www.raspberrypi.org/products/raspberry-pi-4-model-b/

 

Rotation Notations

I was writing some code to perform some celestial calculations.  A lot of it handled changes in positions from certain rotations (orbits, revolutions).  There are also instances where time is treated as a rotation (ex: 1 hour of rotation is 15 degrees).  The best notation for the rotation depends on what is being done.  Here are the rotation notations that might be used.

  • Radians
  • Degrees
    • Decimal Degrees
    • Degrees, Minutes, Seconds
  • Hours
    • Decimal Hours
    • Hours, Minutes, Seconds

Conversion from one to another is easy.  What I did find challenging is ensuring that the right conversion had been performed before working with it.  The trig functions expect to always receive radians.  More than once I made the mistake of converting to the wrong unit before performing a calculation.  Rather then continue forward on a path that has many opportunities for mistakes I made a single class to represent rotations that can be used in various scenarios.  It will always internally represent rotations in degrees.  If I want to explicitly convert the class to a specific type there are methods to explicitly convert to any of the other rotation types.

Instances of this custom type also can be assigned a preferred notation. This preferred notation is used when printing it to the output stream. This allows a preferred format to be assigned without risking making any conversion mistakes.

The interface for the class and the support class follows.

#include <stdio.h>
#include <cmath>
#include <iostream>

typedef double Degree;
typedef double  Hour;
typedef double Minute;
typedef double Second;
typedef double Radian;

enum RotationNotation {
    NOTATION_DEGREES, 
    NOTATION_DMS, 
    NOTATION_HOURS, 
    NOTATION_HMS, 
    NOTATION_RADIANS
};

class Rotation;

struct HMS {
    Hour H;
    Minute M;
    Second S;
};


struct DMS {
    Degree D;
    Minute M;
    Second S;
} ;

std::ostream& operator << (std::ostream& o, const HMS& h);
std::ostream& operator << (std::ostream& o, const DMS& d);
std::ostream& operator << (std::ostream& o, const Rotation a);


double sin(const Rotation& source);
double cos(const Rotation& source);

Hour RadToHour(const Radian );
Hour HMSToHour(const HMS& hms);
Hour DMSToHour(const DMS&);
Hour DegToHour(const Degree degrees);

DMS RadToDMS(const Radian);
DMS DegToDMS(const Degree degrees);
DMS HourToDMS(const Hour hour);
DMS HMSToDMS(const HMS&);

HMS RadToHMS(const Radian);
HMS DegToHMS(const Degree degrees);
HMS DMSToHMS(const DMS&);
HMS HourToHMS(const Hour);

Degree RadToDeg(const Radian);
Degree DMSToDeg(const DMS& );
Degree HMSToDeg(const HMS&);
Degree HourToDeg(const Hour hour);

Radian HourToRad(const Hour);
Radian HMSToRad(const HMS& );
Radian DMSToRad(const DMS& );
Radian DegToRad(const Degree);


class Rotation { 
    private:
        Degree _degrees;
        RotationNotation _notation;
    public:
        Rotation();
        Rotation(const Rotation& source);
        
        RotationNotation getNotation() const;
        void setNotation(RotationNotation);

        const Degree getDegrees() const;
        const Hour getHours() const ;
        const Minute getRadians() const ;
        const DMS getDMS() const ;
        const HMS getHMS() const ;


        void setDegrees(const Degree degree) ;
        void setHours(const Hour hour)  ;
        void setRadians(const Radian rad)  ;
        void setDMS(const DMS& dms)  ;
        void setHMS(const HMS& hms)  ;
};

Download Code 2.0 KB

-30-

Future of Microsoft Edge

Faster Updates, Cross Platform, 4 Channels

Edge

 

Microsoft had made an announcement some time ago stating that they were adopting Chromium as the foundation of the Edge browser. They are making more information available about their plans. A computer will be able to have up to 4 Edge. In addition to the general release version there will be a beta , development, and canary channel where canary is the most experimental channel.

Microsoft is going to make a web view control available. This is a feature I wish had been available a couple of years ago. I worked on a project in which it was necessary to insert a Chromium based web view and that required making modifications to the Chromium source. Compiling Chromium can take hours!

Microsoft Edge is the only browser that has achieved a 100% score in accessibility evaluation. Microsoft is planning to make contributions to Chromium which should help improve the scores of Chromium based browsers across the board.  One example of a change coming from Microsoft is a media query for detecting when a user has turned on high contrast in either their computer or browser. The web developer can choose to change the appearance of a page accordingly.

Windows Biometric sign in is also going to be available to web sites (see navigator.credentials.get). This allows for a more secure way of logging in that doesn’t require a password. Edge also has a hardware backed feature named “Play Ready” that allows for secure playback of premium media.

The user agent-string for Edge Chromium will be Edg (that’s not a type, there is no E on the end). Note that the non-chromium version of edge uses the user token Edge instead. But it is recommended that instead of using the user agent string for enabling or disabling functionality in a web page it is better to use feature detection; new features are being added to browsers all the time and relying on the user agent alone can lead to a page not using a feature that was actually available within a browser.  Edge gets supports for module import and much better much better speech synthesis.

What’s In My Bag? Windows To Go: Windows on USB

When I’m travelling for work there are a number of items that I make sure are in my travel bag.  These include a USB-C charger (almost all of my electronics can charge over USB-C now); a copy of any recent projects I’ve worked on (sometimes I need to hop in to help a team member); and a computer.

The operating system on that computer may vary.  Sometimes I travel with a Windows machine, sometimes a Linux machine, and other times a Mac.  Regardless of the operating system, I usually always have a Windows To Go drive.

The last item is something that is probably a little more obscure.  Since Windows 8, there have been a special type of USB drives that are different in one aspect: they appear as a fixed drive to the computer, even though they are connected to a USB port.  These drives were specifically made for making a portable Windows experience on a USB drive.

It is possible to make bootable Windows environments on other USB drive, but there are some differences.  If you have a Windows ISO you can make a bootable Windows USB drive with a number of tools.  I recommend using Rufus to make the drive.  Though there are other options (including one that is a part of Windows Enterprise Edition), Rufus doesn’t care much about the drive properties.  It will just write the data to the drive in a bootable format.

With any type of USB drive you’ll be able to boot up with little to no trouble and do initial setup on the drive.  The difference will show up when you start installing programs.  Some programs will only install to a fixed drive.  Visual Studio is one such program.  If you have a USB drive that isn’t Windows To Go certified, then chances are that it will appear as a removable drive to the computer.  Visual Studio will not install to a removable drive.

With a non-certified drive it will generally refuse to install.  If you know that the programs of interest to you don’t care about the drive type, there’s a couple of other reasons why you still may want to consider a Windows To Go certified drive.  One is performance. There was a minimum performance requirement that these drives had to achieve as a part of their certification.  However, now there are other solid state drives available that are much faster than the available Windows To Go drive (such as the Thunderbolt 3 only Samsung X5 drives).  Another consideration is security.  Some of the Windows To Go drives have hardware implemented encryption and include the option of voiding the contents of the drive under some conditions that you can define (such as the wrong password being entered at bootup too many times).

The best practice, if you plan to work with any sensitive data, is to not store it on a portable drive, if possible. But if you must, then encryption is an uncompromising need. Whether or not a Windows To Go drive is necessary for you may only be known after you review your needs.

One significant drawback of Windows To Go drives is you cannot perform a major Windows Update on it. The installation can receive Windows security updates though.  When there is a major Windows Update if you want to install it, it’s necessary to format the entire drive and start from scratch.

For my needs, I have a Super Talent 128 GB USB 3.0 drive (for speed) and a Western Digital 500 GB mechanical drive (much slower, but I can work with larger projects using it).  If you choose to do this with a certified drive, make sure you read the drive’s instructions, before you begin writing your Windows Image to it.  Some drives come with their own software that must be used for making the image and if you start off formatting the drive then you’ve already destroyed the software that you need (and it may not be readily available for download from the company’s website).

If your project needs call for a Windows To Go certified drive, I’ve found 4 available on Amazon.  Here are the links to them (affiliate links).

//ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=qf_sp_asin_til&ad_type=product_link&tracking_id=j2inet-20&marketplace=amazon&region=US&placement=B00CFI402O&asins=B00CFI402O&linkId=bc4f9901d4fa29a7092cb52f6eb0ea41&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066C0&bg_color=FFFFFF //ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=tf_til&ad_type=product_link&tracking_id=j2inet-20&marketplace=amazon&region=US&placement=B00A48LB6K&asins=B00A48LB6K&linkId=669d2905a2a87f408df07b5c89e7cfec&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff //ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=tf_til&ad_type=product_link&tracking_id=j2inet-20&marketplace=amazon&region=US&placement=B00DWCPDHS&asins=B00DWCPDHS&linkId=0e21eefd6195691755311fa96a5ddc4c&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff //ws-na.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=US&source=ac&ref=tf_til&ad_type=product_link&tracking_id=j2inet-20&marketplace=amazon&region=US&placement=B015GGR14A&asins=B015GGR14A&linkId=9f8fb3d7f7a3bb009fda0934eaf181e1&show_border=false&link_opens_in_new_window=false&price_color=333333&title_color=0066c0&bg_color=ffffff

-30-

SignageLive + BrightSign

Signangelive is a platform for managing content on digital signage.  It works on a variety of devices, including BrightSign, Chrome OS, LG webOS, Samsung Tizen TVs, and Windows.  I recently used evaluated Signagelive for use with a BrightSign project.

One of the units of deployent on Signangelive is an HTML Widget.  An HTML Widget is a zip file of HTML assets with a manifest and a .wgt file extension.  Prior to now, the only place I’ve really seen Widgets used is on Samsung’s Tizen based platforms.  The Samsung smart watches, TVs, and the Tizen powered phones support HTML applications through Widgets.  The Widgets and other presentation items (such as videos, pictures, or other displayable elements) can be scheduled to run on a device and the platform will take care of the rest.

For the solution I am working on, I packaged it as a widget.  The only additional file that I had to make to do this was a config.xml along with a PNG image to use as an icon.  I updated the WGT into the signage live system, scheduled it, and sometime later the widget was running on the BrightSign.  This deployment process would work great for a production environment, but it doesn’t work as well for development where you might want to make a quick change and refresh.  I found two solutions for this.

IFRAME

One solution was to deploy an IFRAME whose address pointed back to my development machine.  With this solution, if I want to make a quick change, I can make the change on my local file system and then refresh my view on the device.  Refreshing the video on the device could be done by pressing the reset button, but that takes too long.  If you have Chrome installed, you can connect to port 2999 of the BrightSign unit; connect to the browser instance; and then press the refresh menu option on your local browser.  It results in the BrightSign refreshing too.

Remote File System Browsing

You can also upload files directly to the BrightSign. Connect to the BrightSign’s IP address (without the port specified).  There’s a tab labeled “SD” (for the SD card). From there you can upload content to any place on the file system.  After your files are copied, you can either reset the device (which in my opinion takes too long) or connect to the devices IP and refresh the view as described in the IFRAME section.

bscardlisting

Accessing Node

I initially ran into another problem with running my code in a widget.  I mentioned in another blog posts that BrightSign does not support the HTML5 APIs for persistent storage.  The solution that I suggest for this is using NodeJS within BrightSign.  Signagelive runs the code that was packaged inside of a widget in an IFRAME.  As it turns out IFRAMES on BrightSign does not support NodeJS functionality, but that is easily overcome.  The IFRAME that contains the widget has access to the parent window.  The child window cannot call NodeJS functions directly, but it can grab a reference to its parent and invoke the parent’s NodeJS functionality.  To minimize the difference between code run within and outside of the WGT and IFRAME we can coalesce the possible locations of a needed function. To get access to the required function, I used the following.

window.require = window.require || window.parent.require;

File System Access

My attempts to access the file system would initially fail while hosted in Signagelive. To fix this it was necessary to modify AutoRun.brs. When the BrightSign version of Signagelive creates its HTML window (of type roHtmlWidget in BrightScript) it does not configure the window for file system access. To fix this the WebWindowHTML function in Autorun.brs needs a couple of items added, storage_path and storage_quota.

Function WebWindowHTML(index% as integer, url$ as string, RectX as integer, RectY as integer, RecWidth as integer, RecHeight as integer) as Object
	
	webRect=CreateObject("roRectangle", RectX, RectY, RecWidth, RecHeight)
    is = {
        port: 3000
    }
    webPageConfig = {
        nodejs_enabled: true,
        storage_path: "SD:"
        storage_quota: 1073741824        
        inspector_server: is,
        brightsign_js_objects_enabled: false,
        javascript_enabled: true,
        mouse_enabled: true,
        scrollbar_enabled: false,
        storage_path: "SD:",
        storage_quota: 1073741824,   
		port: m.msgPort,
        security_params: {
            websecurity: false,
            camera_enabled: true,
            insecure_https_enabled: false
        },
		url: url$
    }
	
    webhtmlWidget = CreateObject("roHtmlWidget", webRect, webPageConfig)
    webhtmlWidget.Show()

	return webhtmlWidget
	
End Function


I talked to an engineer at Signagelive about addressing this issue.  I do not know how frequently Signagelive makes updates., but this change may appear in future versions of the software making it unnecessary.  If you happen to read this close to the time that the post was made, the change might not have rolled out yet.  You can make the change yourself but beware of a possible risk.  There is a possibility that an update will be made and pushed out to your device that does not yet contain this change. If that happens you would want your code to fail gracefully instead of simply crashing.

-30-