Slide 1

Slide 1 text

Offlinefähige Progressive Web Apps Immer & überall Thomas Hilzendegen @hilzendegent Consultant Manuel Rauber @ManuelRauber Consultant

Slide 2

Slide 2 text

Thomas Hilzendegen Speakers Manuel Rauber [email protected] @hilzendegent [email protected] @manuelrauber https://manuel-rauber.com Microsoft MVP

Slide 3

Slide 3 text

Timetable Time Doing 09:00 - 10:30 Part I 10:30 - 11:00 ☕ 11:00 - 12:30 Part II 12:30 - 13:30 13:30 - 15:00 Part III 15:00 - 15:30 ☕ 15:30 - 17:00 Part IV

Slide 4

Slide 4 text

• Progressive Web Apps • Offline availability • Offline synchronization • Security considerations • Pitfalls Agenda

Slide 5

Slide 5 text

Intro

Slide 6

Slide 6 text

Offline… but why?

Slide 7

Slide 7 text

• Just because the phone has a connection,
 e.g. Edge, it does not mean we are online • Depends on the use case which connection
 quality is required to determine, if the 
 app is reliable online • Connection quality could be measured
 by the time an exclusive request takes • Duration < 150 ms: online • Duration !>=150 ms: bad connection What does “offline” mean?

Slide 8

Slide 8 text

• A lot of reasons to be offline • Traveling, Train, Flights (bad or no signal) • Server is not available • Routing problems • Roaming • Costs Motivation

Slide 9

Slide 9 text

Demo Application

Slide 10

Slide 10 text

• A little application to manage boardgames • Angular, .NET Core, MS SQL Server • Brownfield application, started as a pure online application • As typical for any good demo: no security • Available on Azure: https://tt-boardist.azurewebsites.net • GitHub: https://github.com/thinktecture/thinktecture-boardist Demo Application “Thinktecture Boardist”

Slide 11

Slide 11 text

Demo Application Database Scheme

Slide 12

Slide 12 text

Live Demo

Slide 13

Slide 13 text

Now let’s go offline!

Slide 14

Slide 14 text

Duh! And now?!

Slide 15

Slide 15 text

Progressive Web Apps

Slide 16

Slide 16 text

• Motivation: Get rid of app stores! • Web App = App App • Support native features like: Push notifications, offline availability, installable • Backwards-compatibility: runs in non-PWA browsers, with a reduced feature set Progressive Web Apps

Slide 17

Slide 17 text

PWA Technology Overview Progressive Web Apps HTML5, JavaScript, CSS3 Service Worker API Web App Manifest HTTPS Fetch API Web Notifications Web Workers Push API

Slide 18

Slide 18 text

PWA Platform Support 40 44 11.1 17 4.1 11.3

Slide 19

Slide 19 text

Progressive Web Apps 
 are not a technology , 
 but a collection of features an application must/should support

Slide 20

Slide 20 text

PWA Collection Of Features Responsive Linkable Discoverable Installable App-Like Connectivity
 Independent Fresh Safe Re-engageable Progressive

Slide 21

Slide 21 text

• A JavaScript running in its own thread • No access to the DOM • Communicates via postMessage-API • Acts as a controller/proxy/interceptor • Can perform background tasks • Has to be installed before usage • Runs whenever the browser is running PWA ServiceWorker

Slide 22

Slide 22 text

PWA ServiceWorker Website Internet Cache Service Worker

Slide 23

Slide 23 text

PWA ServiceWorker Lifecycle Parse Installing Error Activated Idle Terminated fetch/message

Slide 24

Slide 24 text

PWA Connectivity Independent Website Internet Cache storage Server Remote storage ServiceWorker

Slide 25

Slide 25 text

• Cache only • Network only • Cache falling back to network • Cache & network race • Network falling back to cache • Cache then network • Generic fallback • ServiceWorker-side templating PWA Caching Strategies

Slide 26

Slide 26 text

PWA Cache Then Network Website Internet Cache storage Server Remote storage ServiceWorker HTTP 1. Lookup 2. fetch

Slide 27

Slide 27 text

• “One-size-fits-all” ServiceWorker implementation • Instrumented via ngsw-config.json • Restricted feature set (e.g. no communication between app and SW) • Initial caching of static content • Caching external content • Dynamic data caching • Push notifications PWA Angular ServiceWorker

Slide 28

Slide 28 text

Are we offline now?

Slide 29

Slide 29 text

Nope.

Slide 30

Slide 30 text

Real offline availability

Slide 31

Slide 31 text

• ServiceWorker is only able to take data offline which has been requested by the application • If all the URLs are known beforehand, the ServiceWorker could cache them all • Data which was not requested, is not available offline (no real offline synchronisation) • But what about … the Background Sync API? What’s the problem?

Slide 32

Slide 32 text

• Name is misleading • It does not offer any data synchronisation possibilities, but offers the possibility to start a (periodic) “sync” event • It’s totally up to the developer what to do in the “sync” event • In case the user agent is offline, the sync will be automatically scheduled to be sent, when the user agent is online again • Since the sync is done in the ServiceWorker, the page can be closed, the sync will be fulfilled anyway • It does not help anything with syncing your actual data! Background Sync API

Slide 33

Slide 33 text

Background Sync API

Slide 34

Slide 34 text

• Cookies (“old & outdated”, not meant for large data or binaries) • Web Storage API (Session Storage, Local Storage, not meant for large data or binaries) • IndexedDB • Cache Storage Storage capabilities

Slide 35

Slide 35 text

User can clear anything at any time by “Remove temporary internet files”

Slide 36

Slide 36 text

• Key-Value database within the browser • Stores data permanently • ServiceWorker and Web App share access to the same IndexedDB • Possibility of scenarios, where the ServiceWorker (or Web App) stores synchronised data in the IndexedDB and the Web App reads the data IndexedDB

Slide 37

Slide 37 text

IndexedDB Availability 11 4 7.1 10 4.4 8

Slide 38

Slide 38 text

• The standard API of IndexedDB is inconvenient to use (lots of callback) • Dexie.js is a minimalistic wrapper for IndexedDB • Operations are promise-based instead of callback • Near native performance (even for bulk inserts) • Open Source @ GitHub: https://github.com/dfahlander/Dexie.js IndexedDB API

Slide 39

Slide 39 text

Offline Sync

Slide 40

Slide 40 text

• Offline Sync means to download all data available to client into a persistent offline storage, without the user having to explicitly request the data • Depending on the scenario, client can do CRUD on the offline data • Data will be synced back to the server, whenever a connection is possible Offline Sync Basics

Slide 41

Slide 41 text

• Online/offline recognition • Conflict management • Binary data • Local changes • Update interval (incoming new data) • Error handling Offline Sync Challenges

Slide 42

Slide 42 text

• Client needs to be online for write operations • Locks the data, so no other client can overwrite it • Data stays locked, until the client either saves or discards changes • Last One (Write) Wins • Visual conflict management (diffing like in Git, SVN, etc.) Offline Sync Conflict Management Scenarios

Slide 43

Slide 43 text

Backend Preparation

Slide 44

Slide 44 text

• All syncable entities need to have rowversion column • rowversion is updated by MS SQL Server automatically whenever the row is changed (created & updated) • For deleted entities • Either set a IsDeleted flag to true (never delete any rows physically) • Or save the deleted IDs of the entities somewhere else (by trigger) Backend Preparation - MS SQL Server

Slide 45

Slide 45 text

Backend Preparation - MS SQL Server public class Syncable : ISyncable { public Guid Id { get; set; } public byte[] RowVersion { get; set; } } public abstract class SyncableEntityTypeConfiguration : IEntityTypeConfiguration where T : class, ISyncable { public virtual void Configure(EntityTypeBuilder builder) { builder.HasKey(p !=> p.Id); builder.Property(p !=> p.RowVersion).IsRowVersion(); } }

Slide 46

Slide 46 text

Backend Preparation - MS SQL Server public async Task> SyncAsync(string timestamp) where TSource : Syncable where TResult : SyncableDto { var rowVersion = Convert.FromBase64String(timestamp !?? string.Empty); var baseQuery = _context.Set() .Where(p !=> (ulong)(object)p.RowVersion !>= (ulong)(object)rowVersion); var changed = await _mapper.ProjectTo(baseQuery.WithoutDeleted()).ToListAsync(); var deleted = await baseQuery.Where(p !=> p.IsDeleted).Select(p !=> p.Id).ToListAsync(); return new SyncDto() { Timestamp = await _context.GetMinActiveRowVersionAsync(), Changed = changed, Deleted = deleted }; }

Slide 47

Slide 47 text

Backend Preparation - MS SQL Server public class Context : DbContext { private DbQuery DbQueryValue { get; set; } public async Task GetMinActiveRowVersionAsync() { return await DbQueryValue .FromSql("SELECT MIN_ACTIVE_ROWVERSION() AS Value") .Select(p !=> p.Value) .FirstAsync(); } }

Slide 48

Slide 48 text

• Use equivalents of rowversion and triggers • Manual implement mechanism in business logic (error-prone!) • Update tracking column manually by incrementing a database global number (during one transaction!) • Will be very hard für multi-row updates/inserts • Manual implement mechanism in triggers (if available) Backend Preparation - Other Database Systems

Slide 49

Slide 49 text

Frontend Preparation

Slide 50

Slide 50 text

• Choose storage area for data (e.g. IndexedDB) • Write all the code • Periodic data synchronization • Binary synchronization when data changes • Tracking of timestamps Frontend Preparation

Slide 51

Slide 51 text

Security

Slide 52

Slide 52 text

Typical token-based security Security API level Browser Identity Provider Web API

Slide 53

Slide 53 text

• User is only able to see data based on this security level • Permissions • Roles • Policies • What happens, if the user rights change leading to different data visible to the user? • What about data, which he does not see due to rights, but is connected to other data? Security Data Level

Slide 54

Slide 54 text

Pitfalls

Slide 55

Slide 55 text

• Highest rowversion within result • @@DBTS • MIN_ACTIVE_ROWVERSION() Change Tracking - MS SQL Server’s ROWVERSION

Slide 56

Slide 56 text

• Not reliable – a later committed (but started earlier) transaction results in skipped values Change Tracking - Highest ROWVERSION within result Timestamp Transaction #1 Transaction #2 Sync T1 MAX !=> ROWVERSION(1) T2 INSERT !=> ROWVERSION(2) UPDATE !=> ROWVERSION(3) MAX !=> ROWVERSION(1) T3 COMMIT MAX !=> ROWVERSION(3) … Tn COMMIT ROW LOST!

Slide 57

Slide 57 text

• Not reliable – represents the maximum rowversion of the database (including not yet committed transactions ) Change Tracking - @@DBTS Timestamp Transaction #1 Transaction #2 Sync T1 @@DBTS !=> ROWVERSION(1) T2 INSERT !=> ROWVERSION(2) UPDATE !=> ROWVERSION(3) @@DBTS !=> ROWVERSION(3) T3 COMMIT @@DBTS !=> ROWVERSION(3) … Tn COMMIT ROW LOST!

Slide 58

Slide 58 text

• Reliable – represents the minimum rowversion of the database (including not yet committed transactions) • Small drawback: could result in repetitive selection of same data Change Tracking - MIN_ACTIVE_ROWVERSION() Timestamp Transaction #1 Transaction #2 Sync T1 SELECT !=> ROWVERSION(2) T2 INSERT !=> ROWVERSION(2) UPDATE !=> ROWVERSION(3) SELECT !=> ROWVERSION(2) T3 COMMIT SELECT !=> ROWVERSION(2) … Tn COMMIT SELECT !=> ROWVERSION(4)

Slide 59

Slide 59 text

• De-normalize the relational data (document style) • Results in multiple rowversion for one entry (use most recent one) • Multiplies the data (more traffic) • Consistent data for one entry • Keep the relations up to the frontend • Explicit rowversion for each entry • Partial consistency (related data may not be synced yet) Relational Data

Slide 60

Slide 60 text

• Keep some relational data and de-normalize some of it • De-normalize many-to-many relations • Needs trigger or business logic to change main entry’s rowversion when relation changes • Partial consistency (related data may not be synced yet) Relational Data

Slide 61

Slide 61 text

• Native Cordova app out-of-the box possible • Native features could be used • Store restrictions apply • What about Electron? Native Packaging??

Slide 62

Slide 62 text

• PWA helps bringing offline the application, but not the data • Online !== Edge is available • Offline data is “temporary” (“Remove temporary internet files”) • Use MIN_ACTIVE_ROWVERSION and “greater than” operator • RxJS helps building a sync engine, but a lot knowledge is needed • Think about data level security (permissions, roles, etc.) • Think about conflict management • Depends heavily on your use case Summary

Slide 63

Slide 63 text

And… now? • Slides: https://speakerdeck.com/manuelrauber • Repository: https://github.com/thinktecture/thinktecture-boardist