')
155 | modal.modal('show')
156 | modal.find('.modal-title').text( url )
157 |
158 | // request back to main for the file content
159 | const response = await askMain( "show-file", "path", url )
160 | card.append( makeJsonElement(response) )
161 | modal.find('.modal-body').append(card)
162 | showSpinner( modal, false )
163 | })
164 |
165 | $(document).on( "click", "a.fa-cloud-download", (event) =>
166 | {
167 | const anchor = $(event.target)
168 | const url = anchor.data("url")
169 | const mime = anchor.data("content")
170 |
171 | // request back to main for the file content
172 | askMain( "show-file", "url", url, mime )
173 | })
174 |
175 | $('#ModalDialog').on('show.bs.modal', async (event) =>
176 | {
177 | // The modal dialog is about to open, but it's generic.
178 | // So we need to set its title, and build a table with the right content.
179 | const button = $(event.relatedTarget)
180 | const modal = $("#ModalDialog")
181 | modal.find('.modal-title').text( button.text() )
182 | var table = $('
')
183 | modal.find('.modal-body').append(table)
184 | await setModalContent( table, button.data('set'), button.data('ask') )
185 | showSpinner( modal, false )
186 | })
187 |
188 | $('#ModalDialog').on('hidden.bs.modal', (event) =>
189 | {
190 | const modal = $("#ModalDialog")
191 | modal.find('.table').remove()
192 | modal.find('.card-body').remove()
193 | showSpinner( modal, true )
194 | })
195 |
196 | $('#ModalDialogAccept').on('click', function (event)
197 | {
198 | var button = $(event.relatedTarget) // Button that triggered the modal
199 | // do whatever you want after the user accepts OK
200 | })
201 |
202 | ipc.on( 'app-ready', () =>
203 | {
204 | // hide the opening spinner and show the contents
205 | $("#main-window-loading").addClass("d-none")
206 | $("#main-window-content").removeClass("d-none")
207 |
208 | // populate the folder list
209 | setFolderList()
210 | })
211 | }
212 |
--------------------------------------------------------------------------------
/scripts/indexpage_style.css:
--------------------------------------------------------------------------------
1 | a.nav-link:hover:not(.active) {
2 | color: #F00F00 !important;
3 | cursor: pointer;
4 | }
5 | .tab-content {
6 | overflow-y: scroll;
7 | }
8 |
--------------------------------------------------------------------------------
/scripts/logincomplete.js:
--------------------------------------------------------------------------------
1 | // logincomplete.js
2 | // after login, redirect to this page, and we probably want to close it.
3 |
4 | window.close()
5 |
--------------------------------------------------------------------------------
/scripts/loginstart.js:
--------------------------------------------------------------------------------
1 | /**
2 | * loginstart.js
3 | * this is not a module, but is included in other HTML pages
4 | */
5 |
6 | /* For definition of firebaseUI, see: https://github.com/firebase/firebaseui-web
7 | *
8 | * This page does not use ipc to contact the main process because it will call
9 | * external authentication pages, so for security this page should run without
10 | * nodeIntegration enabled. Therefore communication between this Renderer
11 | * process and the Main process happens through API calls over HTTPS.
12 | */
13 |
14 | const idpConfig = {
15 | 'google.com': {
16 | provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
17 | scopes: [
18 | 'profile',
19 | 'email',
20 | 'openid',
21 | 'https://www.googleapis.com/auth/devstorage.full_control'
22 | ],
23 | customParameters: { prompt: 'select_account' }
24 | },
25 | 'facebook.com': {
26 | provider: firebase.auth.FacebookAuthProvider.PROVIDER_ID,
27 | scopes: [
28 | 'public_profile',
29 | 'email'
30 | ],
31 | customParameters: { auth_type: 'reauthenticate' }
32 | },
33 | 'twitter.com': {
34 | provider: firebase.auth.TwitterAuthProvider.PROVIDER_ID
35 | },
36 | 'github.com': {
37 | provider: firebase.auth.GithubAuthProvider.PROVIDER_ID
38 | },
39 | 'password': {
40 | provider: firebase.auth.EmailAuthProvider.PROVIDER_ID
41 | },
42 | 'phone': {
43 | provider: firebase.auth.PhoneAuthProvider.PROVIDER_ID
44 | }
45 | }
46 |
47 | async function startApplication( fbConfig )
48 | {
49 | try {
50 | // initializeApp must be called before any other Firebase APIs
51 | firebase.initializeApp( fbConfig )
52 |
53 | // a signout request may be passed in the browser querystring
54 | await checkForSignout()
55 |
56 | // whether or not the user is forced to log in every time
57 | await setUserPersistence( getTrueQueryParam( "persistentUser" ) )
58 |
59 | // we must make a decision: if there is a persistent user then do not start the login workflow
60 | const foundUser = await checkForPersistentUser()
61 | if ( !foundUser ) {
62 | throw( "NO USER" )
63 | }
64 |
65 | // we found a user defined in the IndexedDB object store, so wait for the login
66 | firebase.auth().onAuthStateChanged( postAuthResult )
67 | }
68 | catch (error) {
69 | // no persisted user or some error, so start the login UI workflow from firebaseUI
70 | const response = await showLoginWindow()
71 | firebaseAuthUIStart( fbConfig, idpConfig )
72 | }
73 | }
74 |
75 | // when the document is loaded, call Main process to get the app config parameters
76 | // and then we can start firebaseui
77 | document.onreadystatechange = function ()
78 | {
79 | if ( document.readyState !== 'complete' ) return
80 |
81 | // ask the Main process to get the configuration parameters for firebase authentication
82 | api( 'GET', getQueryParam( "firebaseconfig" ), null )
83 | .then( startApplication )
84 | .catch( (error) => {
85 | alert( "ERROR getting app configuration, please restart application" )
86 | })
87 | }
88 |
--------------------------------------------------------------------------------
/scripts/setmodule_off.js:
--------------------------------------------------------------------------------
1 | if (typeof module === 'object') {window.module = module; module = undefined;}
--------------------------------------------------------------------------------
/scripts/setmodule_on.js:
--------------------------------------------------------------------------------
1 | if (window.module) module = window.module;
--------------------------------------------------------------------------------
/scripts/splashpage_style.css:
--------------------------------------------------------------------------------
1 | html, body {height:100%;}
2 | html {display:table; width:100%;}
3 | body {display:table-cell; text-align:center; vertical-align:middle;}
4 | .logobox {
5 | width: 50%;
6 | }
7 | .loader {
8 | border: 16px solid #f3f3f3;
9 | border-top: 16px solid #3498db;
10 | border-radius: 50%;
11 | width: 80px;
12 | height: 80px;
13 | animation: spin 2s linear infinite;
14 | margin-right: auto;
15 | margin-left: auto;
16 | }
17 | @keyframes spin {
18 | 0% { transform: rotate(0deg); }
19 | 100% { transform: rotate(360deg); }
20 | }
--------------------------------------------------------------------------------
/scripts/weblocal.js:
--------------------------------------------------------------------------------
1 | // weblocal.js
2 | // this is not a module, but is included in other HTML pages
3 | // provides an interface to web localStorage
4 |
5 | // communication between Main and Renderer processes
6 | var ipc = require('electron').ipcRenderer
7 |
8 | // implements the browser interface to localstorage.js
9 | ipc.on( 'localStorage', ( event, command, key, value ) =>
10 | {
11 | switch( command ) {
12 | case "setItem":
13 | localStorage.setItem( key, value )
14 | break
15 | case "removeItem":
16 | localStorage.removeItem( key )
17 | break
18 | case "getItem":
19 | ipc.send( 'localStorage:' + key, localStorage.getItem( key ) )
20 | break
21 | }
22 | })
23 |
24 | function askMain( topic, request, ...parameters )
25 | {
26 | return new Promise( (resolve, reject) => {
27 | ipc.once( topic, ( event, response ) => {
28 | resolve( response )
29 | })
30 | ipc.send( topic, request, parameters[0], parameters[1], parameters[2] )
31 | })
32 | }
33 |
34 | ipc.send( 'about-browser', {
35 | language: navigator.language,
36 | userAgent: navigator.userAgent,
37 | platform: navigator.platform,
38 | dateFormat: Intl.DateTimeFormat().resolvedOptions(),
39 | numberFormat: Intl.NumberFormat().resolvedOptions(),
40 | startUTC: (new Date()).toUTCString(),
41 | started: Date.now().toString(),
42 | online: navigator.onLine
43 | })
44 |
--------------------------------------------------------------------------------
/scripts/weblogin.js:
--------------------------------------------------------------------------------
1 | // weblogin.js
2 | // this is not a module, but is included in the loginstart.html page
3 |
4 | // These specify the local storage location for persistent user credentials,
5 | // which we can use to determine if there is a persistent credential that is
6 | // used for auto-login.
7 | const fbDatabaseName = "firebaseLocalStorageDb"
8 | const fbStorageName = "firebaseLocalStorage"
9 | const fbStoredUserKey = "firebase:authUser"
10 |
11 | const queryParams = new URLSearchParams( location.search )
12 |
13 | function getQueryParam( paramName )
14 | {
15 | return queryParams.get( paramName )
16 | }
17 |
18 | function getTrueQueryParam( paramName )
19 | {
20 | const value = queryParams.get( paramName ) || ""
21 | return ( value === true ) || ( value.toLowerCase() === 'true' )
22 | }
23 |
24 | function openObjectStore( dataStoreName, dataObjectName, openMode )
25 | {
26 | var dbReq, fbdb, transaction
27 | return new Promise( (resolve, reject) =>
28 | {
29 | dbReq = indexedDB.open( dataStoreName, 1 )
30 | dbReq.onerror = (error) => {
31 | reject( "openObjectStore ERROR opening database, ", error )
32 | }
33 | dbReq.onsuccess = (event) => {
34 | fbdb = event.target.result
35 | transaction = fbdb.transaction( dataObjectName, openMode || "readonly" )
36 | transaction.onerror = (error) => {
37 | reject( "openObjectStore transaction ERROR, ", error )
38 | }
39 | resolve( transaction.objectStore( dataObjectName ) )
40 | }
41 | })
42 | }
43 |
44 | function findKeyInStore( dbStore, dbKeyName )
45 | {
46 | var keyReq, keyList, foundKey
47 | return new Promise( (resolve, reject) =>
48 | {
49 | keyReq = dbStore.getAllKeys()
50 | keyReq.onerror = (error) => {
51 | reject( "findKeyInStore ERROR getting all keys, ", error )
52 | }
53 | keyReq.onsuccess = (event) => {
54 | keyList = event.target.result
55 | foundKey = keyList.find( (element, index) => {
56 | return ( element.indexOf( dbKeyName ) == 0 )
57 | })
58 | if ( !foundKey ) {
59 | return reject( "findKeyInStore: NO KEY" )
60 | }
61 | resolve( foundKey )
62 | }
63 | })
64 | }
65 |
66 | function getObjectFromStore( dbStore, dbKeyName )
67 | {
68 | var keyValueReq
69 | return new Promise( (resolve, reject) =>
70 | {
71 | findKeyInStore( dbStore, dbKeyName )
72 | .then( (foundKey) => {
73 | keyValueReq = dbStore.get( foundKey )
74 | keyValueReq.onerror = (error) => {
75 | reject( "getObjectFromStore ERROR getting value, ", error )
76 | }
77 | keyValueReq.onsuccess = (event) => {
78 | resolve( event.target.result )
79 | }
80 | })
81 | .catch( (error) => {
82 | reject( "getObjectFromStore ERROR finding key, ", error )
83 | })
84 | })
85 | }
86 |
87 | function deleteObjectFromStore( dbStore, dbKeyName )
88 | {
89 | var keyValueReq
90 | return new Promise( (resolve, reject) =>
91 | {
92 | findKeyInStore( dbStore, dbKeyName )
93 | .then( (foundKey) => {
94 | keyValueReq = dbStore.delete( foundKey )
95 | keyValueReq.onerror = (error) => {
96 | reject( "deleteObjectFromStore ERROR deleting key, ", error )
97 | }
98 | keyValueReq.onsuccess = (event) => {
99 | resolve( "user deleted" )
100 | }
101 | })
102 | .catch( (error) => {
103 | reject( "deleteObjectFromStore ERROR finding key, ", error )
104 | })
105 | })
106 | }
107 |
108 | function postAuthResult( authResult )
109 | {
110 | // authResult is https://firebase.google.com/docs/reference/js/firebase.auth#.UserCredential
111 | // the user's credentials get sent back to Main process via API
112 | const appUpdate = {
113 | user: firebase.auth().currentUser.toJSON(),
114 | operationType: authResult.operationType || null,
115 | additionalUserInfo: authResult.additionalUserInfo,
116 | refreshToken: authResult.refreshToken || authResult.user.refreshToken,
117 | credential: authResult.credential || null
118 | }
119 | return api( 'POST', getQueryParam( "logintoken" ), appUpdate )
120 | .then( (status) => {
121 | // redirect to the login complete page which should close this window
122 | window.location.assign( getQueryParam( "loginRedirect" ) )
123 | })
124 | .catch( (error) => {
125 | alert( "ERROR in sign-in process, please restart this application: ", stringifyJSON(error) )
126 | window.close()
127 | })
128 | }
129 |
130 | async function showLoginWindow()
131 | {
132 | var responseCode = null
133 |
134 | await api( 'GET', getQueryParam( "loginready" ), null )
135 | .then( (response) => {
136 | responseCode = response.status
137 | })
138 | .catch( (error ) => {
139 | console.error( "showLoginWindow ERROR: ", error )
140 | })
141 | return responseCode
142 | }
143 |
144 | function firebaseAuthUIStart( fbConfig, idpConfig )
145 | {
146 | var uiConfig = {
147 | callbacks: {
148 | signInSuccessWithAuthResult: ( authResult, redirectUrl ) => {
149 | // send the authentication event and result back to the main app
150 | postAuthResult( authResult )
151 | // return false to prevent redirection
152 | return false
153 | },
154 | signInFailure: ( error, credential ) => {
155 | alert( "Sign in failure: " + error + ". Please restart the app" )
156 | window.close()
157 | },
158 | uiShown: () => {
159 | // The widget is rendered. Hide the loader.
160 | document.getElementById('loader').style.display = 'none';
161 | }
162 | },
163 | signInOptions: [
164 | // fill this in dynamically based on configuration
165 | ],
166 | // see: https://github.com/firebase/firebaseui-web#credential-helper
167 | credentialHelper: firebaseui.auth.CredentialHelper.ACCOUNT_CHOOSER_COM,
168 | // tosUrl and privacyPolicyUrl accept either url string or a callback function.
169 | tosUrl: fbConfig.tosUrl,
170 | privacyPolicyUrl: fbConfig.privacyUrl
171 | }
172 |
173 | // Initialize the FirebaseUI Widget
174 | // set up the configuration for each identity provider that was specified in uiConfig
175 | fbConfig.providers.forEach( ( provider ) => {
176 | uiConfig.signInOptions.push( idpConfig[ provider ] )
177 | })
178 | var ui = new firebaseui.auth.AuthUI( firebase.auth() )
179 | ui.start('#firebaseui-auth-container', uiConfig)
180 | }
181 |
182 | async function checkForPersistentUser()
183 | {
184 | try {
185 | const dbStore = await openObjectStore( fbDatabaseName, fbStorageName )
186 | return await getObjectFromStore( dbStore, fbStoredUserKey )
187 | }
188 | catch (error) {
189 | throw( "checkForPersistentUser ERROR, ", error )
190 | }
191 | }
192 |
193 | async function setUserPersistence( allowPersistence )
194 | {
195 | var persistence = firebase.auth.Auth.Persistence.NONE
196 |
197 | switch ( allowPersistence ) {
198 | case false:
199 | case 'false':
200 | case 'SESSION':
201 | case 'session':
202 | persistence = firebase.auth.Auth.Persistence.SESSION
203 | break;
204 | case true:
205 | case 'true':
206 | case 'LOCAL':
207 | case 'local':
208 | persistence = firebase.auth.Auth.Persistence.LOCAL
209 | break;
210 | }
211 | await firebase.auth().setPersistence( persistence )
212 | }
213 |
214 | async function checkForSignout()
215 | {
216 | try {
217 | // normal case is no signout, just leave
218 | if ( !getTrueQueryParam( "signoutuser" ) ) return true
219 | // signout, clear persistence and signout from firebase
220 | setUserPersistence( 'none' )
221 | return firebase.auth().signOut()
222 | }
223 | catch (error) {
224 | alert( "Error in signout process: ", error )
225 | throw( error )
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/scripts/webmodal.js:
--------------------------------------------------------------------------------
1 | // webmodal.js
2 | // this is not a module, but is included in the index.html page
3 | // functions for formatting data returned from info-buttons requests
4 |
5 | function isImagePath(url)
6 | {
7 | if ( typeof url !== 'string' ) return false
8 | return null != url.match( /\.(jpeg|jpg|gif|png)$/, 'i' )
9 | }
10 |
11 | function isURL( whatever )
12 | {
13 | if ( typeof whatever !== 'string' ) return false
14 | return null != whatever.match( /^(http|https):\/\//, 'i' )
15 | }
16 |
17 | function isTag( whatever )
18 | {
19 | if ( typeof whatever !== 'string' ) return false
20 | return ( whatever.charAt(0) == '<' && whatever.charAt(whatever.length-1) == '>' )
21 | }
22 |
23 | function makeImageElement( url )
24 | {
25 | return $(`
`)
26 | }
27 |
28 | function makeJsonElement( arg )
29 | {
30 | var pretext = $('')
31 | pretext.text( JSON.stringify( arg, null, 4 ) )
32 | return pretext
33 | }
34 |
35 | function makeBasicElement( arg )
36 | {
37 | return String( arg )
38 | }
39 |
40 | function createTableRow( table, ...args )
41 | {
42 | var row = $('')
43 | table.append(row)
44 | for (let arg of args) {
45 | var cell = $('')
46 | row.append(cell)
47 | // make a formatting decision based on the content of this arg
48 | if ( isTag(arg) ) cell.append( $(arg) )
49 | else if ( $.isPlainObject(arg) ) cell.append( makeJsonElement(arg) )
50 | else if ( isURL(arg) ) cell.append( makeImageElement(arg) )
51 | else cell.text( makeBasicElement( arg ) )
52 | }
53 | }
54 |
55 | async function setModalContent( table, request, parameter )
56 | {
57 | // send the info-request back to the main app, and
58 | // stuff each response into a table row
59 | const response = await askMain( 'info-request', request, parameter )
60 | Object.entries(response).forEach( ([key,value]) => {
61 | createTableRow( table, key, value )
62 | })
63 | return response
64 | }
65 |
--------------------------------------------------------------------------------
/scripts/webutils.js:
--------------------------------------------------------------------------------
1 | // webutils.js
2 | // this is not a module, but is included in other HTML pages
3 |
4 | function isJSON( s )
5 | {
6 | return ( typeof s == 'string' ) && ( s.charAt(0) == '{' || s.charAt(0) == '[' );
7 | }
8 |
9 | function isObject( obj )
10 | {
11 | return obj && typeof obj == 'object'
12 | }
13 |
14 | function parseJSON( jString )
15 | {
16 | if( jString == null ) return null
17 | if ( !isJSON( jString ) ) {
18 | return jString
19 | }
20 | try {
21 | return JSON.parse( jString )
22 | }
23 | catch (err) {
24 | console.error( "ERROR parsing JSON: ", err, jString )
25 | return null
26 | }
27 | }
28 |
29 | function stringifyJSON( thisObject )
30 | {
31 | if ( thisObject == null ) return null
32 | if ( typeof thisObject !== 'object' && !Array.isArray( thisObject ) ) {
33 | return thisObject
34 | }
35 | try {
36 | return JSON.stringify( thisObject );
37 | }
38 | catch (err) {
39 | console.error( "ERROR stringifying JSON: ", err )
40 | return null
41 | }
42 | }
43 |
44 | function api( method, url, payload )
45 | {
46 | return new Promise((resolve, reject) => {
47 | var xhr = new XMLHttpRequest();
48 | xhr.open( method , url );
49 | if ( isJSON( payload ) || isObject( payload ) ) {
50 | xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
51 | }
52 | xhr.onreadystatechange = function() {
53 | // readyState 4 is DONE
54 | if ( xhr.readyState !== 4 ) return
55 | if ( xhr.status >= 300 ) {
56 | console.error( "ERROR in api: ", method, url, xhr.status, xhr.response )
57 | reject( {error: "API ERROR", url: url, payload: payload, status: xhr.status } )
58 | return
59 | }
60 | resolve( parseJSON( xhr.response ) )
61 | }
62 | xhr.send( stringifyJSON( payload ) );
63 | })
64 | }
65 |
--------------------------------------------------------------------------------
/setupApp.js:
--------------------------------------------------------------------------------
1 | /* setupapp.js
2 | * Copyright (c) 2019-2021 by David Asher, https://github.com/david-asher
3 | *
4 | * This is a quickstart template for building Firebase authentication workflow into an electron app
5 | * This module contains functions that help to initialize or update the application
6 | * @module setupapp
7 | */
8 | 'use strict';
9 |
10 | const { firestore, fbstorage } = loadModule( 'electron-firebase' )
11 |
12 | const docAboutmeFolder = "aboutme/"
13 |
14 | function makeUserDocuments( user, appContext, appConfig )
15 | {
16 | if ( !user || !user.uid || !user.displayName ) {
17 | return null
18 | }
19 |
20 | const isNow = ( new Date() ).toISOString()
21 |
22 | // fixups
23 | const profile = { ... user.profile }
24 | if ( !profile.email ) profile.email = user.email || null
25 | if ( !profile.picture ) profile.picture = user.photoURL || null
26 |
27 | const provider = { ... user.providerData[0] }
28 | if ( !provider.displayName ) provider.displayName = user.displayName || null
29 | if ( !provider.email ) provider.displayName = user.email || null
30 | if ( !provider.phoneNumber ) provider.phoneNumber = user.phoneNumber || null
31 | if ( !provider.photoURL ) provider.photoURL = user.photoURL || null
32 |
33 | const account = {
34 | uid: user.uid,
35 | name: user.displayName,
36 | photo: user.photoURL || null,
37 | email: user.email || null,
38 | created: user.metadata.creationTime || null,
39 | accessed: isNow
40 | }
41 |
42 | const session = {
43 | uid: user.uid,
44 | apiKey: global.fbConfig.apiKey || null,
45 | project: global.fbConfig.projectId || null,
46 | domain: global.fbConfig.authDomain || null,
47 | authenticated: user.metadata.lastSignInTime || null,
48 | start: isNow
49 | }
50 |
51 | return {
52 | profile: profile,
53 | provider: provider,
54 | account: account,
55 | session: session,
56 | application: appContext,
57 | configuration: appConfig
58 | }
59 | }
60 |
61 | async function updateUserDocs( user, appContext, appConfig )
62 | {
63 | try {
64 | const userDocs = makeUserDocuments( user, appContext, appConfig )
65 |
66 | const aboutMe = await firestore.doc.about( docAboutmeFolder + "profile" )
67 |
68 | if ( !aboutMe || !aboutMe.exists ) {
69 | await firestore.doc.write( docAboutmeFolder + "profile", userDocs.profile )
70 | await firestore.doc.write( docAboutmeFolder + "provider", userDocs.provider )
71 | await firestore.doc.write( docAboutmeFolder + "account", userDocs.account )
72 | await firestore.doc.write( docAboutmeFolder + "session", userDocs.session )
73 | }
74 |
75 | const myProfile = await fbstorage.file.about( "aboutme/MyProfile" )
76 |
77 | if ( !myProfile || !myProfile.exists ) {
78 | await fbstorage.file.upload( "aboutme/MyProfile", userDocs.profile )
79 |
80 | await fbstorage.file.upload( "aboutme/MyAccount", userDocs.account )
81 | await fbstorage.file.upload( "/info/first/second/MyProfile", userDocs.profile )
82 | await fbstorage.file.upload( "/info/MyProvider", userDocs.provider )
83 | await fbstorage.file.upload( "/account/third/my-account", userDocs.account )
84 | await fbstorage.file.upload( "/account/my-session", userDocs.session )
85 |
86 | await fbstorage.app.upload( "account/my-session", userDocs.session )
87 | await fbstorage.app.upload( "info/MyProvider", userDocs.provider )
88 |
89 | await fbstorage.public.upload( "account/my-session", userDocs.session )
90 | await fbstorage.public.upload( "info/MyProvider", userDocs.provider )
91 |
92 | }
93 | }
94 | catch (error) {
95 | console.error( error )
96 | }
97 | }
98 |
99 | module.exports = {
100 | updateUserDocs: updateUserDocs
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/tests/generated.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "_id": "5c749a8f42f6afd2f30e23a6",
4 | "index": 0,
5 | "guid": "fb89a89d-0256-48d0-ab8c-a89cce710d34",
6 | "isActive": true,
7 | "balance": "$2,068.74",
8 | "picture": "http://placehold.it/32x32",
9 | "age": 28,
10 | "eyeColor": "blue",
11 | "name": "Lou Mckee",
12 | "gender": "female",
13 | "company": "SOFTMICRO",
14 | "email": "loumckee@softmicro.com",
15 | "phone": "+1 (961) 417-3509",
16 | "address": "939 Clinton Avenue, Chalfant, New York, 8598",
17 | "about": "Ad quis officia do cupidatat dolor non elit fugiat magna sint. Labore consectetur minim duis dolore proident minim nisi pariatur deserunt veniam incididunt commodo minim labore. Quis consequat amet culpa cillum eu fugiat fugiat dolor aliqua sint consequat ut. Aute commodo culpa est pariatur anim sint aliqua.\r\n",
18 | "registered": "2016-09-06T04:41:03 +04:00",
19 | "latitude": 42.489112,
20 | "longitude": -176.878198,
21 | "tags": [
22 | "consectetur",
23 | "eu",
24 | "occaecat",
25 | "est",
26 | "amet",
27 | "non",
28 | "exercitation"
29 | ],
30 | "friends": [
31 | {
32 | "id": 0,
33 | "name": "Elinor Park"
34 | },
35 | {
36 | "id": 1,
37 | "name": "Iris Nunez"
38 | },
39 | {
40 | "id": 2,
41 | "name": "Salinas Sims"
42 | }
43 | ],
44 | "greeting": "Hello, Lou Mckee! You have 3 unread messages.",
45 | "favoriteFruit": "apple"
46 | },
47 | {
48 | "_id": "5c749a8fc99924096feaea4a",
49 | "index": 1,
50 | "guid": "6cb0fc4e-cfe0-4bf5-b6b3-dc635ad15e8d",
51 | "isActive": true,
52 | "balance": "$1,111.32",
53 | "picture": "http://placehold.it/32x32",
54 | "age": 24,
55 | "eyeColor": "brown",
56 | "name": "Elizabeth Robinson",
57 | "gender": "female",
58 | "company": "XELEGYL",
59 | "email": "elizabethrobinson@xelegyl.com",
60 | "phone": "+1 (988) 506-2008",
61 | "address": "468 Cove Lane, Islandia, Texas, 4733",
62 | "about": "Non dolor consequat commodo amet pariatur mollit anim sint eu. Excepteur sunt exercitation minim commodo labore duis id sint adipisicing adipisicing magna ut. Exercitation tempor ullamco irure velit ut occaecat non nulla. Enim consequat quis adipisicing excepteur velit proident id irure eiusmod. Deserunt esse Lorem ex aute veniam laboris id excepteur enim et consequat fugiat. Dolor aliquip exercitation nulla commodo elit sit laborum deserunt reprehenderit ullamco ipsum ut adipisicing.\r\n",
63 | "registered": "2017-03-04T05:41:59 +05:00",
64 | "latitude": -14.38438,
65 | "longitude": 179.438195,
66 | "tags": [
67 | "dolore",
68 | "dolor",
69 | "nulla",
70 | "cupidatat",
71 | "proident",
72 | "non",
73 | "tempor"
74 | ],
75 | "friends": [
76 | {
77 | "id": 0,
78 | "name": "Cook Watson"
79 | },
80 | {
81 | "id": 1,
82 | "name": "Gutierrez Herman"
83 | },
84 | {
85 | "id": 2,
86 | "name": "Katelyn Rodgers"
87 | }
88 | ],
89 | "greeting": "Hello, Elizabeth Robinson! You have 5 unread messages.",
90 | "favoriteFruit": "strawberry"
91 | },
92 | {
93 | "_id": "5c749a8fd27511e0e4caf518",
94 | "index": 2,
95 | "guid": "6054d644-d267-469f-b9e5-1d2081682751",
96 | "isActive": true,
97 | "balance": "$3,147.53",
98 | "picture": "http://placehold.it/32x32",
99 | "age": 34,
100 | "eyeColor": "blue",
101 | "name": "Orr Short",
102 | "gender": "male",
103 | "company": "EPLODE",
104 | "email": "orrshort@eplode.com",
105 | "phone": "+1 (974) 599-3402",
106 | "address": "224 Monaco Place, Welch, Wisconsin, 2501",
107 | "about": "Sint aliqua cillum eiusmod exercitation voluptate duis Lorem pariatur ipsum anim proident enim pariatur. Ad labore quis minim occaecat deserunt sunt do laborum laboris amet cillum. In eiusmod id veniam pariatur voluptate consectetur fugiat laboris occaecat et commodo amet. Tempor quis nisi velit consequat aliquip pariatur officia. Id nulla amet exercitation ut tempor id dolore esse ipsum esse. Aliqua reprehenderit laboris commodo culpa est ipsum officia elit laboris voluptate excepteur. Elit amet veniam exercitation velit eu elit dolore fugiat.\r\n",
108 | "registered": "2018-12-19T04:07:50 +05:00",
109 | "latitude": -40.242281,
110 | "longitude": 13.339475,
111 | "tags": [
112 | "labore",
113 | "reprehenderit",
114 | "magna",
115 | "eiusmod",
116 | "sint",
117 | "dolor",
118 | "minim"
119 | ],
120 | "friends": [
121 | {
122 | "id": 0,
123 | "name": "Turner Daniel"
124 | },
125 | {
126 | "id": 1,
127 | "name": "Snyder Espinoza"
128 | },
129 | {
130 | "id": 2,
131 | "name": "Neal Key"
132 | }
133 | ],
134 | "greeting": "Hello, Orr Short! You have 10 unread messages.",
135 | "favoriteFruit": "strawberry"
136 | },
137 | {
138 | "_id": "5c749a8f80f7dd9e8fd41679",
139 | "index": 3,
140 | "guid": "d96617a8-d7ca-445b-95e0-5a823b19983b",
141 | "isActive": false,
142 | "balance": "$1,492.03",
143 | "picture": "http://placehold.it/32x32",
144 | "age": 34,
145 | "eyeColor": "blue",
146 | "name": "Rojas Morales",
147 | "gender": "male",
148 | "company": "SONGLINES",
149 | "email": "rojasmorales@songlines.com",
150 | "phone": "+1 (883) 431-3642",
151 | "address": "214 Nichols Avenue, Bainbridge, Virgin Islands, 323",
152 | "about": "Nostrud laboris dolor sit elit aliqua ad. Anim aliquip cillum eu nostrud sit. Enim qui labore proident Lorem aliqua proident cupidatat sint nulla labore elit adipisicing non velit. Ex consequat excepteur ipsum cupidatat amet in magna veniam qui nisi magna duis.\r\n",
153 | "registered": "2016-05-03T11:40:36 +04:00",
154 | "latitude": -85.328954,
155 | "longitude": 110.667992,
156 | "tags": [
157 | "culpa",
158 | "incididunt",
159 | "excepteur",
160 | "ipsum",
161 | "dolor",
162 | "qui",
163 | "qui"
164 | ],
165 | "friends": [
166 | {
167 | "id": 0,
168 | "name": "Teri Hatfield"
169 | },
170 | {
171 | "id": 1,
172 | "name": "Mcmahon Ward"
173 | },
174 | {
175 | "id": 2,
176 | "name": "Pate Garrett"
177 | }
178 | ],
179 | "greeting": "Hello, Rojas Morales! You have 3 unread messages.",
180 | "favoriteFruit": "strawberry"
181 | },
182 | {
183 | "_id": "5c749a8f17cf878b8d979244",
184 | "index": 4,
185 | "guid": "7998ca64-fde5-42d8-ac7b-e56e7ed61309",
186 | "isActive": true,
187 | "balance": "$3,671.65",
188 | "picture": "http://placehold.it/32x32",
189 | "age": 20,
190 | "eyeColor": "blue",
191 | "name": "Roth Benson",
192 | "gender": "male",
193 | "company": "EURON",
194 | "email": "rothbenson@euron.com",
195 | "phone": "+1 (971) 531-3567",
196 | "address": "508 Dean Street, Dawn, Kentucky, 5276",
197 | "about": "Dolor aute excepteur cupidatat magna velit proident voluptate Lorem nulla sit ad reprehenderit amet commodo. Dolore nostrud id sint aute labore. Est deserunt minim aliquip exercitation aliquip do aute nisi aliquip ex amet duis. Aliqua voluptate dolore minim ullamco ut magna voluptate. Enim consequat incididunt magna sunt tempor proident proident elit commodo commodo cillum eu qui tempor.\r\n",
198 | "registered": "2017-08-18T06:31:37 +04:00",
199 | "latitude": 24.692351,
200 | "longitude": 32.425209,
201 | "tags": [
202 | "velit",
203 | "id",
204 | "velit",
205 | "quis",
206 | "id",
207 | "laborum",
208 | "aute"
209 | ],
210 | "friends": [
211 | {
212 | "id": 0,
213 | "name": "Gibbs Rosario"
214 | },
215 | {
216 | "id": 1,
217 | "name": "Stein Hodges"
218 | },
219 | {
220 | "id": 2,
221 | "name": "Letha Manning"
222 | }
223 | ],
224 | "greeting": "Hello, Roth Benson! You have 6 unread messages.",
225 | "favoriteFruit": "banana"
226 | }
227 | ]
--------------------------------------------------------------------------------
/tests/main_test_all.js:
--------------------------------------------------------------------------------
1 | /* main.js
2 | * electron-firebase
3 | * This is a quickstart template for building Firebase authentication workflow into an electron app
4 | * Copyright (c) 2019 by David Asher, https://github.com/david-asher
5 | *
6 | * Log output will show DeprecationWarning: grpc.load blah blah
7 | * which is a know bug: https://github.com/googleapis/nodejs-vision/issues/120
8 | * and here: https://github.com/firebase/firebase-js-sdk/issues/1112
9 | */
10 | 'use strict';
11 |
12 | /**
13 | * Testing for all of the electron-firebase modules.
14 | */
15 |
16 | process.on('warning', e => console.warn(e.stack));
17 |
18 | const { app } = require('electron')
19 | const { mainapp } = require( '../electron-firebase' )
20 |
21 | // console logging is not strictly synchronous, so for testing we force log and error to block
22 |
23 | const nodeFS = require('fs')
24 | const nodeUtil = require('util')
25 |
26 | var lastTime = Date.now()
27 |
28 | console.log = (...args) => {
29 | const isNow = Date.now()
30 | const delta = ( " " + ( isNow - lastTime ) ).slice(-4)
31 | nodeFS.writeSync( process.stdout.fd, delta + " -- " + nodeUtil.format.apply(null,args) + "\n" )
32 | lastTime = isNow
33 | }
34 |
35 | console.error = (...args) => {
36 | const isNow = Date.now()
37 | const delta = ( " " + ( isNow - lastTime ) ).slice(-4)
38 | nodeFS.writeSync( process.stderr.fd, delta + " xx " + nodeUtil.format.apply(null,args) + "\n" )
39 | lastTime = isNow
40 | }
41 |
42 | global.__TESTMODE__ = true
43 | global.testDocPath = "./tests/generated.json"
44 |
45 | // catch everything just in case
46 |
47 | process.on( 'unhandledRejection', (reason, p) => {
48 | console.log('Unhandled Rejection at: Promise', p, 'reason:', reason)
49 | // application specific logging, throwing an error, or other logic here
50 | })
51 |
52 | // run the tests after both the user is ready and main window is open
53 | var bIsWindowOpen = false
54 | var bIsUserReady = false
55 |
56 | // one call to setup the electron-firebase framework
57 | mainapp.setupAppConfig()
58 |
59 | // inject the tests folder into the webpage static content set
60 | global.appConfig.webFolders.push( "tests" )
61 |
62 | function logwrite( ...stuff )
63 | {
64 | // if ( !global.appConfig.debugMode ) return
65 | console.log.apply( null, stuff )
66 | }
67 |
68 | // readFile and readJSON are defined here, even though there is a fileutils module,
69 | // to eliminate that dependency
70 |
71 | global.readFile = function( sourceFilename )
72 | {
73 | try {
74 | return nodeFS.readFileSync( sourceFilename ).toString()
75 | }
76 | catch (error) {
77 | return null
78 | }
79 | }
80 |
81 | global.readJSON = function( sourceFilename )
82 | {
83 | try {
84 | return JSON.parse( global.readFile( sourceFilename ) )
85 | }
86 | catch (error) {
87 | return null
88 | }
89 | }
90 |
91 | async function testModule( moduleName, withOption = "" )
92 | {
93 | // the process for running one module through a test
94 | console.log( `++ ++ ++ ++ ++ ++ ++ ${moduleName}.${withOption}` )
95 | const testModule = require( `./test_${moduleName}` )
96 | await testModule.testall( withOption )
97 | console.log( "-- -- -- -- -- -- -- -- -- -- -- -- ")
98 | }
99 |
100 | async function runTests()
101 | {
102 | // spin through all of the modules
103 | await testModule( "applibrary" )
104 | await testModule( "fileutils" )
105 | await testModule( "localstorage" )
106 | await testModule( "firestore", "doc" )
107 | await testModule( "firestore", "app" )
108 | await testModule( "firestore", "public" )
109 | await testModule( "fbstorage", "file" )
110 | await testModule( "fbstorage", "app" )
111 | await testModule( "fbstorage", "public" )
112 |
113 | // done!
114 | app.exit(0)
115 | }
116 |
117 |
118 | // electron-firebase framework event handling
119 |
120 | mainapp.event.once( "user-login", (user) =>
121 | {
122 | // this event will trigger on sign-in, not every time the app runs with cached credentials
123 | logwrite( "EVENT user-login: ", user.displayName )
124 | })
125 |
126 | mainapp.event.once( "user-ready", async ( user ) =>
127 | {
128 | logwrite( "EVENT user-ready: ", user.displayName )
129 | mainapp.sendToBrowser( 'app-ready' )
130 | if ( bIsWindowOpen ) runTests()
131 | bIsUserReady = true
132 | })
133 |
134 | mainapp.event.once( "window-open", (window) =>
135 | {
136 | // first event will be the main window
137 | logwrite( "EVENT window-open: ", window.getTitle() )
138 | if ( bIsUserReady ) runTests()
139 | bIsWindowOpen = true
140 | })
141 |
142 | mainapp.event.once( "main-window-ready", (window) =>
143 | {
144 | logwrite( "EVENT main-window-ready: ", window.getTitle() )
145 |
146 | // mainapp.getFromBrowser( "user-signout", mainapp.signoutUser )
147 |
148 | })
149 |
150 | mainapp.event.once( "main-window-close", (window) =>
151 | {
152 | // use this to clean up things
153 | })
154 |
155 | // electron app event handling
156 |
157 | // Quit when all windows are closed.
158 | // see: https://www.electronjs.org/docs/api/app#event-window-all-closed
159 | app.on( 'window-all-closed', () =>
160 | {
161 | logwrite( "EVENT app window-all-closed" )
162 | mainapp.closeApplication()
163 | })
164 |
165 | // This function will be called when Electron has finished initialization and is ready to create
166 | // browser windows. Some APIs can only be used after this event occurs. launchInfo is macOS specific.
167 | // see: https://www.electronjs.org/docs/api/app#event-ready
168 | app.on( 'ready', async (launchInfo) =>
169 | {
170 | logwrite( "EVENT app ready" )
171 | global.launchInfo = launchInfo | {}
172 | try {
173 | await mainapp.startMainApp({
174 | title: "TEST Window: " + global.fbConfig.projectId,
175 | open_html: "tests/testpage_local.html",
176 | show: true, ///////////////////////////////////////// false,
177 | movable: false,
178 | resizable: false
179 | })
180 | }
181 | catch (error) {
182 | console.error( error )
183 | }
184 | })
185 |
186 | // see: https://electronjs.org/docs/api/app#event-activate-macos
187 | // macOS specific - Emitted when the application is activated. Various actions can trigger this
188 | // event, such as launching the application for the first time, attempting to re-launch the
189 | // application when it's already running, or clicking on the application's dock or taskbar icon.
190 | app.on( 'activate', (appEvent,hasVisibleWindows) =>
191 | {
192 | logwrite( "EVENT app activate " )
193 | // do whatever
194 | })
195 |
196 |
--------------------------------------------------------------------------------
/tests/test_all_modules.js:
--------------------------------------------------------------------------------
1 | /*
2 | * test_apputils.js
3 | * Unit-test automation for electron-firebase
4 | * Copyright (c) 2019 by David Asher, https://github.com/david-asher
5 | */
6 | 'use strict';
7 |
8 | var moduleList = [
9 | // 'test_applibrary',
10 | // 'test_fileutils',
11 | // 'test_firestore',
12 | // 'test_fbstorage',
13 | // 'test_windows',
14 | // 'test_localstorage',
15 | 'test_webserver'
16 | ]
17 |
18 | /*
19 | * global.__TESTMODE__ is used to configure each JS module to export modulename()
20 | * and probe() functions. Otherwise in normal use these won't be exported.
21 | *
22 | * global.testDocPath is a file with a bunch of random JSON stuff.
23 | * thanks to https://www.json-generator.com/
24 | *
25 | * The readFile() and readJSON() files are included here as very simple test
26 | * functions - if they throw an error it's okay, it's better to be sure that
27 | * the test are working.
28 | */
29 |
30 | global.__TESTMODE__ = true
31 | global.testDocPath = "./tests/generated.json"
32 |
33 | const fs = require('fs')
34 |
35 | /* if you want to catch unhandled rejections:
36 | process.on( 'unhandledRejection', (reason, p) => {
37 | console.error('Unhandled Rejection at: Promise', p, 'reason:', reason)
38 | // application specific logging, throwing an error, or other logic here
39 | })
40 | */
41 |
42 | global.readFile = function( sourceFilename )
43 | {
44 | try {
45 | return fs.readFileSync( sourceFilename ).toString()
46 | }
47 | catch (error) {
48 | return null
49 | }
50 | }
51 |
52 | global.readJSON = function( sourceFilename )
53 | {
54 | try {
55 | return JSON.parse( global.readFile( sourceFilename ) )
56 | }
57 | catch (error) {
58 | return null
59 | }
60 | }
61 |
62 | // const { auth, data } = require('../lib/electron-firebase')
63 | const efb = require('../electron-firebase')
64 |
65 | function catchError( error )
66 | {
67 | throw( error )
68 | }
69 |
70 | async function setupUser( user )
71 | {
72 | global.user = user
73 | return await efb.data.setup( user )
74 | .then( async (rootDoc) => {
75 | global.rootDoc = rootDoc
76 | return await Promise.resolve( rootDoc )
77 | })
78 | .catch( catchError )
79 | }
80 |
81 | async function loginAndSetup()
82 | {
83 | efb.auth.initializeFirebase()
84 |
85 | return await efb.auth.startNewSignIn() // signInSavedUser()
86 | .then( async (user) => {
87 | return await setupUser( user )
88 | })
89 | .catch( catchError )
90 | }
91 |
92 | async function testModule( moduleName, index )
93 | {
94 | const module = require( `./${moduleName}` )
95 | console.log( `${index}: ${module.target()}` )
96 | await module.testall().catch( catchError )
97 | console.log( "_ _ _ _ _ _ _ _")
98 | }
99 |
100 | async function testList()
101 | {
102 | for ( var moduleIndex in moduleList ) {
103 | await testModule( moduleList[moduleIndex], moduleIndex )
104 | }
105 | }
106 |
107 | async function runAllTests()
108 | {
109 | console.log( "* * * Testing all modules * * *" )
110 |
111 | // the app config must be loaded since it's used by a lot of functions
112 | global.appConfig = global.readJSON( 'app-config-test.json' )
113 | if ( !global.appConfig ) global.appConfig = global.readJSON( 'config/app-config.json' )
114 | global.fbConfig = global.readJSON( 'firebase-config-test.json' )
115 | if ( !global.fbConfig ) global.fbConfig = global.readJSON( 'config/firebase-config.json' )
116 |
117 | // console.log( "global.appConfig = ", global.appConfig )
118 | // console.log( "global.fbConfig = ", global.fbConfig )
119 |
120 | // database and storage unit tests require a logged in user
121 | await loginAndSetup()
122 | .then( testList )
123 | .catch( catchError )
124 |
125 | console.log( "ALL TESTS COMPLETED SUCCESSFULLY")
126 | process.exit(0)
127 | }
128 |
129 | runAllTests()
130 |
--------------------------------------------------------------------------------
/tests/test_applibrary.js:
--------------------------------------------------------------------------------
1 | // test_applibrary.js
2 | 'use strict';
3 |
4 | const assert = require('assert').strict
5 |
6 | // module under test:
7 | const { applib } = require('../electron-firebase');
8 |
9 | // specimens
10 | const jsonDoc = global.readFile( global.testDocPath )
11 | const testDoc = JSON.parse( jsonDoc )
12 | const testObj = testDoc[0]
13 |
14 | var errorCount = 0
15 |
16 | var baseOptions = {
17 | https: {
18 | rejectUnauthorized: false,
19 | },
20 | headers: {
21 | referer: global.appConfig.webapp.hostUrl
22 | }
23 | }
24 |
25 | const newDocOptions = { ...baseOptions, ...{
26 | method: 'POST',
27 | url: `${global.appConfig.webapp.hostUrl}/api/test/newdoc`,
28 | data: testObj
29 | } }
30 |
31 | const oldDocOptions = { ...baseOptions, ...{
32 | method: 'GET',
33 | url: `${global.appConfig.webapp.hostUrl}/api/test/olddoc`
34 | } }
35 |
36 | async function testallFunctions()
37 | {
38 | var newDoc
39 |
40 | // isJSON( s )
41 | console.log( ">> isJSON" )
42 | assert( applib.isJSON( jsonDoc ) ) // valid JSON
43 | assert( !applib.isJSON( testObj ) ) // object
44 | assert( !applib.isJSON( testObj._id ) ) // string
45 | assert( !applib.isJSON( testObj.latitude ) ) // number
46 | assert( !applib.isJSON( testObj.tags ) ) // array
47 |
48 | // isObject( obj )
49 | console.log( ">> isObject" )
50 | assert( applib.isObject( testObj ) ) // object
51 | assert( applib.isObject( testObj.tags ) ) // array
52 | assert( !applib.isObject( testObj._id ) ) // string
53 | assert( !applib.isObject( testObj.latitude ) ) // number
54 |
55 | // parseJSON( inputSerialized, optionalErrorCode )
56 | // note: correct testing of optionalErrorCode yields a process exit
57 | console.log( ">> parseJSON" )
58 | assert.deepEqual( testDoc, applib.parseJSON( jsonDoc ) )
59 |
60 | // compactJSON( inputObject, optionalErrorCode )
61 | console.log( ">> compactJSON" )
62 | assert.deepEqual( testDoc, applib.parseJSON( applib.compactJSON( testDoc ) ) )
63 |
64 | // stringifyJSON( inputObject, optionalErrorCode )
65 | console.log( ">> stringifyJSON" )
66 | assert.deepEqual( testDoc, applib.parseJSON( applib.stringifyJSON( testDoc ) ) )
67 |
68 | // mergeObjects( ...objects )
69 | console.log( ">> mergeObjects" )
70 | const merged = applib.mergeObjects( testDoc[0], testDoc[1], testDoc[2] )
71 | assert.deepEqual( merged.tags, [].concat( testDoc[0].tags, testDoc[1].tags, testDoc[2].tags ) )
72 | assert.deepEqual( merged.friends, [].concat( testDoc[0].friends, testDoc[1].friends, testDoc[2].friends ) )
73 |
74 | // set up a route for GET and a route for POST, then test if a GET retreives
75 | // the same content that was sent with a POST
76 | global.apiRouter.post( "/api/test/newdoc", (req, res, next ) =>
77 | {
78 | newDoc = req.body
79 | res.status( 200 ).send()
80 | })
81 | global.apiRouter.get( "/api/test/olddoc", (req, res, next ) =>
82 | {
83 | res.json( newDoc )
84 | })
85 |
86 | console.log( ">> request post" )
87 | const postResponse = await applib.request( newDocOptions )
88 | assert.equal( postResponse.status, 200 )
89 |
90 | console.log( ">> request get" )
91 | const getResponse = await applib.request( oldDocOptions )
92 | assert.equal( getResponse.status, 200 )
93 | assert.deepEqual( getResponse.data, testObj )
94 |
95 | return true
96 | }
97 |
98 | async function testall()
99 | {
100 | try {
101 | await testallFunctions()
102 | }
103 | catch (error) {
104 | errorCount++
105 | console.error( error )
106 | }
107 | return errorCount
108 | }
109 |
110 | module.exports = {
111 | testall: testall
112 | }
113 |
--------------------------------------------------------------------------------
/tests/test_fbstorage.js:
--------------------------------------------------------------------------------
1 | // test_fbstorage.js
2 | 'use strict';
3 |
4 | const assert = require('assert').strict
5 |
6 | // module under test:
7 | const { fbstorage } = require('../electron-firebase')
8 |
9 | // specimens
10 | const jsonDoc = global.readFile( "./tests/generated.json" )
11 | const testDoc = JSON.parse( jsonDoc )
12 | const testObj = testDoc[0]
13 |
14 | var errorCount = 0
15 |
16 | const folderPath = "testFolder"
17 | const filePath = `${folderPath}/path_to_file.whatever`
18 |
19 | const makeFileSet = [
20 | `${folderPath}/junk-one/temp-two/test-file-A.tmp`,
21 | `${folderPath}/junk-one/temp-two/test-file-B.tmp`,
22 | `${folderPath}/junk-one/temp-four/test-file-C.tmp`,
23 | `${folderPath}/junk-one/temp-six/test-file-D.tmp`,
24 | `${folderPath}/junk-two/temp-four/test-file-E.tmp`,
25 | `${folderPath}/junk-two/temp-five/test-file-F.tmp`,
26 | `${folderPath}/junk-two/temp-five/test-file-G.tmp`,
27 | `${folderPath}/junk-three/temp-six/test-file-H.tmp`,
28 | `${folderPath}/junk-three/temp-six/test-file-J.tmp`
29 | ]
30 |
31 | const checkFileList = [
32 | [`${folderPath}/junk-one/temp-two`,2],
33 | [`${folderPath}/junk-one/temp-four`,1],
34 | [`${folderPath}/junk-one/temp-six`,1],
35 | [`${folderPath}/junk-two/temp-four`,1],
36 | [`${folderPath}/junk-two/temp-five`,2],
37 | [`${folderPath}/junk-three/temp-six`,2]
38 | ]
39 |
40 | async function testallFunctions( store )
41 | {
42 | console.log( ">> upload" )
43 | var uploadResult = await store.upload( filePath, testObj )
44 | assert.equal( filePath, uploadResult.path )
45 |
46 | console.log( ">> about" )
47 | var aboutResult = await store.about( filePath )
48 | assert.equal( uploadResult.docid, aboutResult.docid )
49 | assert.equal( uploadResult.fullPath, aboutResult.fullPath )
50 | assert.equal( uploadResult.size, aboutResult.size )
51 | assert.equal( uploadResult.updated, aboutResult.updated )
52 |
53 | console.log( ">> download" )
54 | var fileContent = await store.download( filePath )
55 | assert.deepEqual( testObj, fileContent )
56 |
57 | console.log( ">> find" )
58 | var foundFile = await store.find( filePath )
59 | assert.equal( filePath, foundFile.path )
60 | assert.equal( uploadResult.docid, foundFile.docid )
61 |
62 | console.log( ">> update" )
63 | var fileMeta = await store.update( filePath, {
64 | contentEncoding: 'gzip',
65 | contentType: 'text/html'
66 | } )
67 | assert.equal( fileMeta.contentEncoding, 'gzip' )
68 | assert.equal( fileMeta.contentType, 'text/html' )
69 |
70 |
71 | console.log( ">> folders" )
72 | for ( var k in makeFileSet ) {
73 | uploadResult = await store.upload( makeFileSet[k], testObj )
74 | }
75 | var folderList = await store.folders( folderPath )
76 | assert.equal( 7, folderList.length )
77 |
78 | console.log( ">> list" )
79 | for ( var j in checkFileList ) {
80 | var fileList = await store.list( checkFileList[j][0] )
81 | assert.equal( fileList.length, checkFileList[j][1] )
82 | }
83 |
84 | console.log( ">> delete" )
85 | for ( var m in makeFileSet ) {
86 | await store.delete( makeFileSet[m] )
87 | }
88 | folderList = await store.folders( folderPath )
89 | assert.equal( 0, folderList.length )
90 | var deleteResult = await store.delete( filePath )
91 | assert( !deleteResult )
92 | aboutResult = await store.about( filePath )
93 | assert.ok( !aboutResult.exists )
94 |
95 | return true
96 | }
97 |
98 | async function testall( domain )
99 | {
100 | try {
101 | await testallFunctions( fbstorage[domain] )
102 | }
103 | catch (error) {
104 | errorCount++
105 | console.error( error )
106 | }
107 | return errorCount
108 | }
109 |
110 | module.exports = {
111 | testall: testall
112 | }
113 |
--------------------------------------------------------------------------------
/tests/test_fileutils.js:
--------------------------------------------------------------------------------
1 | // test_fileutils.js
2 | 'use strict';
3 |
4 | const assert = require('assert').strict
5 |
6 | // module under test:
7 | const { file } = require('../electron-firebase')
8 |
9 | const writeDocPath = "./tests/temp-write-doc.json"
10 |
11 | // specimens
12 | const jsonDoc = global.readFile( global.testDocPath )
13 | const testDoc = JSON.parse( jsonDoc )
14 | const testObj = testDoc[0]
15 |
16 | var errorCount = 0
17 |
18 | async function testallFunctions()
19 | {
20 | // readFile( fileName )
21 | console.log( ">> readFile" )
22 | assert.equal( jsonDoc, file.readFile( global.testDocPath ) )
23 |
24 | // writeFile( fileName, fileContent )
25 | console.log( ">> writeFile" )
26 | file.writeFile( writeDocPath, jsonDoc )
27 | assert.equal( jsonDoc, file.readFile( writeDocPath ) )
28 |
29 | // isFile( fileName )
30 | console.log( ">> isFile" )
31 | assert( file.isFile( global.testDocPath ) )
32 | assert( !file.isFile( global.testDocPath + "BAD_STUFF" ) )
33 |
34 | // isFolder( folderName )
35 | console.log( ">> isFolder" )
36 | const testFolder = global.testDocPath.split("/").slice(0,-1).join("/")
37 | const newFolder = testFolder + "/newFolder"
38 | assert( file.isFolder( testFolder ) )
39 | assert( !file.isFolder( newFolder ) )
40 |
41 | // makeFolder( folderName )
42 | console.log( ">> makeFolder" )
43 | assert( file.makeFolder( newFolder ) )
44 | assert( file.isFolder( newFolder ) )
45 |
46 | // deleteFolder( folderName )
47 | console.log( ">> deleteFolder" )
48 | assert( file.deleteFolder( newFolder ) )
49 | assert( !file.isFolder( newFolder ) )
50 |
51 | // listFolders( folderName )
52 | const showFolder = "."
53 | console.log( ">> listFolders" )
54 | const folderList = file.listFolders( showFolder )
55 | assert( 0 <= folderList.indexOf( 'lib' ) )
56 | assert( 0 <= folderList.indexOf( 'pages' ) )
57 | assert( 0 <= folderList.indexOf( 'node_modules' ) )
58 | assert( 0 <= folderList.indexOf( 'tests' ) )
59 | assert( -1 == folderList.indexOf( 'is-not-there' ) )
60 |
61 | // listFiles( folderName )
62 | console.log( ">> listFiles" )
63 | const fileList = file.listFiles( showFolder )
64 | assert( 0 <= fileList.indexOf( 'LICENSE' ) )
65 | assert( 0 <= fileList.indexOf( 'electron-firebase.js' ) )
66 | assert( 0 <= fileList.indexOf( 'package.json' ) )
67 | assert( 0 <= fileList.indexOf( 'README.md' ) )
68 | assert( -1 == fileList.indexOf( 'is-not-there' ) )
69 |
70 | // deleteFile( fileName )
71 | console.log( ">> deleteFile" )
72 | assert( file.isFile( writeDocPath ) )
73 | file.deleteFile( writeDocPath )
74 | assert( !file.isFile( writeDocPath ) )
75 |
76 | // readJSON( fileName )
77 | console.log( ">> readJSON" )
78 | assert.deepEqual( testDoc, file.readJSON( global.testDocPath ) )
79 |
80 | // writeJSON( fileName, fileContent )
81 | console.log( ">> writeJSON" )
82 | file.writeJSON( writeDocPath, testObj )
83 | assert.deepEqual( testObj, file.readJSON( writeDocPath ) )
84 |
85 | // updateJSON( fileName, updateObject )
86 | console.log( ">> updateJSON" )
87 | file.updateJSON( writeDocPath, { age: 59, company: "fictionco" } )
88 | testObj.age = 59
89 | testObj.company = "fictionco"
90 | assert.deepEqual( testObj, file.readJSON( writeDocPath ) )
91 |
92 | // checkCommand( commandString )
93 | console.log( ">> checkCommand" )
94 |
95 | assert( file.checkCommand( "mkdir" ) )
96 | assert( !file.checkCommand( "doesNotExist" ) )
97 |
98 | // cleanup
99 | file.deleteFile( writeDocPath )
100 | assert( !file.isFile( writeDocPath ) )
101 |
102 | return true
103 | }
104 |
105 | async function testall()
106 | {
107 | try {
108 | await testallFunctions()
109 | }
110 | catch (error) {
111 | errorCount++
112 | console.error( error )
113 | }
114 | return errorCount
115 | }
116 |
117 | module.exports = {
118 | testall: testall
119 | }
120 |
121 |
--------------------------------------------------------------------------------
/tests/test_firestore.js:
--------------------------------------------------------------------------------
1 | // test_firestore.js
2 | 'use strict';
3 |
4 | const assert = require('assert').strict
5 |
6 | // module under test:
7 | const { firestore } = require('../electron-firebase')
8 |
9 | const testMergeName = "height"
10 | const testMergeValue = "medium"
11 | const testUpdateBogusDoc = "testAlpha/does_not_exist"
12 |
13 | // specimens
14 | const jsonDoc = global.readFile( global.testDocPath )
15 | const testDoc = JSON.parse( jsonDoc )
16 | const testObj = testDoc[0]
17 | const mergeObj = {}
18 | mergeObj[testMergeName] = testMergeValue
19 |
20 | const testPath = "testAlpha/docOne.json"
21 | const testCollection = "testAlpha"
22 | const testDocSet = "testAlpha/docOne-"
23 |
24 | const insertTag = "insert-this-tag"
25 | const checkTag = "occaecat"
26 | const tagName = "tags"
27 |
28 | var errorCount = 0
29 |
30 | async function testallFunctions( data )
31 | {
32 | console.log( ">> write" )
33 | await data.write( testPath, testObj )
34 |
35 | console.log( ">> about" )
36 | var aboutResult = await data.about( testPath )
37 | assert.ok( aboutResult.exists )
38 | assert.equal( aboutResult.id, testPath.split("/").pop() )
39 | assert.equal( aboutResult.get("guid"), testObj.guid )
40 |
41 | console.log( ">> read" )
42 | var readResult = await data.read( testPath )
43 | assert.deepEqual( testObj, readResult )
44 |
45 | console.log( ">> merge" )
46 | assert.ok( !readResult[testMergeName] )
47 | await data.merge( testPath, mergeObj )
48 | readResult = await data.read( testPath )
49 | assert.ok( aboutResult.exists )
50 | assert.equal( readResult[testMergeName], testMergeValue )
51 | assert.equal( aboutResult.get("guid"), testObj.guid )
52 |
53 | console.log( ">> update" )
54 | await data.write( testPath, testObj )
55 | readResult = await data.read( testPath )
56 | assert.ok( !readResult[testMergeName] )
57 | await data.update( testPath, mergeObj )
58 | readResult = await data.read( testPath )
59 | assert.equal( readResult[testMergeName], testMergeValue )
60 | assert.equal( aboutResult.get("guid"), testObj.guid )
61 | await data.update( testUpdateBogusDoc, mergeObj )
62 | aboutResult = await data.about( testUpdateBogusDoc )
63 | assert.ok( !aboutResult.exists )
64 |
65 | console.log( ">> field" )
66 | var getField = await data.field( testPath, "guid" )
67 | assert.equal( getField, testObj.guid )
68 | getField = await data.field( testPath, "friends" )
69 | assert.deepEqual( getField, testObj.friends )
70 | getField = await data.field( testPath, "there-is-no-field" )
71 | assert.ok( getField == undefined )
72 |
73 | console.log( ">> union" )
74 | assert.equal( 7, ( await data.field( testPath, tagName ) ).length )
75 | await data.union( testPath, tagName, checkTag )
76 | await data.union( testPath, tagName, checkTag )
77 | await data.union( testPath, tagName, checkTag )
78 | assert.equal( 7, ( await data.field( testPath, tagName ) ).length )
79 | await data.union( testPath, tagName, insertTag )
80 | await data.union( testPath, tagName, insertTag )
81 | await data.union( testPath, tagName, insertTag )
82 | assert.equal( 8, ( await data.field( testPath, tagName ) ).length )
83 |
84 | console.log( ">> splice" )
85 | await data.splice( testPath, tagName, "eu" )
86 | assert.equal( 7, ( await data.field( testPath, tagName ) ).length )
87 |
88 | console.log( ">> push" )
89 | const endTag = "at-the-end"
90 | await data.push( testPath, tagName, endTag )
91 | await data.push( testPath, tagName, endTag )
92 | await data.push( testPath, tagName, endTag )
93 | await data.push( testPath, tagName, endTag )
94 | assert.equal( 11, ( await data.field( testPath, tagName ) ).length )
95 |
96 | console.log( ">> pop" )
97 | await data.pop( testPath, tagName )
98 | await data.pop( testPath, tagName )
99 | await data.pop( testPath, tagName )
100 | var popped = await data.pop( testPath, tagName )
101 | assert.equal( popped, endTag )
102 | assert.equal( 7, ( await data.field( testPath, tagName ) ).length )
103 |
104 | console.log( ">> delete" )
105 | aboutResult = await data.about( testPath )
106 | assert.ok( aboutResult.exists )
107 | await data.delete( testPath )
108 | aboutResult = await data.about( testPath )
109 | assert.ok( !aboutResult.exists )
110 |
111 | console.log( ">> query" )
112 | const docSet = []
113 | testDoc.forEach( async (doc,index) => {
114 | docSet[index] = testDocSet + index
115 | await data.write( docSet[index], doc )
116 | })
117 | var queryResult = await data.query( testCollection, "eyeColor", "blue" )
118 | assert.equal( queryResult.size, 4 )
119 | queryResult = await data.query( testCollection, "eyeColor", "red" )
120 | assert.equal( queryResult.size, 0 )
121 | queryResult = await data.query( testCollection, "age", 28, ">" )
122 | assert.equal( queryResult.size, 2 )
123 | docSet.forEach( async (docPath) => {
124 | await( data.delete( docPath ) )
125 | })
126 |
127 | return true
128 | }
129 |
130 | async function testall( domain )
131 | {
132 | try {
133 | await testallFunctions( firestore[domain] )
134 | }
135 | catch (error) {
136 | errorCount++
137 | console.error( error )
138 | }
139 | return errorCount
140 | }
141 |
142 | module.exports = {
143 | testall: testall
144 | }
145 |
146 |
147 |
--------------------------------------------------------------------------------
/tests/test_localstorage.js:
--------------------------------------------------------------------------------
1 | // test_localstorage.js
2 | 'use strict';
3 |
4 | const assert = require('assert').strict
5 |
6 | // module under test:
7 | const { local } = require('../electron-firebase')
8 |
9 | // specimens
10 | const testKey = "testKey"
11 | const testValue = {
12 | one: "first item",
13 | two: "second item"
14 | }
15 |
16 | var errorCount = 0
17 |
18 | async function testallFunctions()
19 | {
20 | // setItem( key, value )
21 | console.log( ">> setItem" )
22 | local.setItem( testKey, testValue )
23 |
24 | console.log( ">> getItem" )
25 | const getValue = await local.getItem( testKey )
26 | assert.deepEqual( getValue, testValue )
27 |
28 | // removeItem( key )
29 | console.log( ">> removeItem" )
30 | local.removeItem( testKey )
31 | const removeValue = await local.getItem( testKey )
32 | assert.equal( removeValue, null )
33 | }
34 |
35 | async function testall()
36 | {
37 | try {
38 | await testallFunctions()
39 | }
40 | catch (error) {
41 | errorCount++
42 | console.error( error )
43 | }
44 | return errorCount
45 | }
46 |
47 | module.exports = {
48 | testall: testall
49 | }
50 |
51 |
52 |
--------------------------------------------------------------------------------
/tests/testpage_local.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Test Webpage - LocalStorage
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 | TEST PAGE
20 | for electron-firebase
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
|