Commit 73d2a44f authored by Eric Seigne's avatar Eric Seigne
Browse files

upload ok via resumeable

parent 86d45455
<?xml version='1.0' encoding='utf-8'?>
<widget id="fr.caprel.doliscan" version="1.0.12" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<widget id="fr.caprel.doliscan" version="1.0.14" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
<name>doliscan</name>
<description>
A simple app for french people looking for a free and self hosting "notes de frais/expenses report" solution.
......@@ -50,7 +50,7 @@
<edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryAddUsageDescription">
<string>Please pick a pictures of your invoice / receipt</string>
</edit-config>
<preference name="OverrideUserAgent" value="Mozilla/5.0 OpenNDF/1.0.12 Android" />
<preference name="OverrideUserAgent" value="Mozilla/5.0 OpenNDF/1.0.14 Android" />
<plugin name="cordova-plugin-inappbrowser" spec="^3.0.0" />
<plugin name="cordova-plugin-whitelist" spec="^1.3.4" />
</widget>
......@@ -38,5 +38,7 @@
<script type="text/javascript" src="js/pinch-zoom.js"></script>
<script type="text/javascript" src="js/session-timeout.js"></script>
<script type="text/javascript" src="js/resumable.js"></script>
<script type="text/javascript">
</script>
</html>
......@@ -240,23 +240,23 @@
},
success: function (result) {
hideWait();
myDebug(result);
// myDebug(result);
globalNetworkResponse = Date.now();
myDebug("Success on a un message du serveur on l'affiche ...");
// myDebug("Success on a un message du serveur on l'affiche ...");
ons.notification.alert(result.message);
carouselLogin();
},
error: function (result) {
hideWait();
myDebug(result.responseJSON);
// myDebug(result.responseJSON);
if (typeof result.responseJSON.message !== 'undefined') {
myDebug("Error ... on a un message du serveur on l'affiche ...");
// myDebug("Error ... on a un message du serveur on l'affiche ...");
ons.notification.alert(result.responseJSON.message);
}
else {
myDebug("Error ... pas de message serveur");
myDebug(result.message);
// myDebug("Error ... pas de message serveur");
// myDebug(result.message);
ons.notification.alert("Erreur ... contactez le service technique pour plus d'informations.");
// setTimeout(function(){ $("#erreurLogin").hide(); }, 5000);
// ons.notification.alert(result.message);
......@@ -271,14 +271,14 @@
//On essaye de trouver le serveur pour ce compte: si le sous domaine doliscan.domaine.ext existe alors c'est lui
//sinon on utilise doliscan.fr tout simplement
function DNSresolveSuccess(address) {
myDebug('Resolved ' + globalDoliScanDomain + ' to ' + address);
// myDebug('Resolved ' + globalDoliScanDomain + ' to ' + address);
localStoreData("api_server", "https://" + globalDoliScanDomain);
localStoreData("name_server", globalDoliScanDomain);
loginJson();
}
function DNSresolveFailure(error) {
myDebug('Failed to resolve ' + globalDoliScanDomain + ': ' + error);
// myDebug('Failed to resolve ' + globalDoliScanDomain + ': ' + error);
localStoreData("api_server", "https://doliscan.fr");
localStoreData("name_server", "doliscan.fr");
loginJson();
......@@ -291,7 +291,7 @@
let lemail = document.getElementById('email').value;
//on essaye de passer en variable globale car les fonctions de retour en ont besoin ...
globalDoliScanDomain = lemail.replace(/.*@/, "doliscan.");
myDebug('On cherche ' + globalDoliScanDomain);
// myDebug('On cherche ' + globalDoliScanDomain);
cordova.plugins.dns.resolve(globalDoliScanDomain, DNSresolveSuccess, DNSresolveFailure);
}
......@@ -302,7 +302,7 @@
let carousel = document.getElementById('carousel');
showWait();
myDebug('loginJson : tentative de login via ajax request vers le serveur ' + localGetData("api_server"));
// myDebug('loginJson : tentative de login via ajax request vers le serveur ' + localGetData("api_server"));
$.ajax({
type: "POST",
timeout: 5000,
......@@ -369,7 +369,7 @@
},
error: function (result) {
hideWait();
myDebug(result.status);
// myDebug(result.status);
if (result.status == "503") {
ons.notification.alert("Serveur indisponible, veuillez re-essayer plus tard.");
}
......@@ -405,7 +405,7 @@
ons.ready(function () {
document.addEventListener('deviceready', function () {
myDebug("deviceReady index.html");
// myDebug("deviceReady index.html");
let gotoLoginPage = true;
// $(document).ready(function () {
......@@ -419,34 +419,34 @@
cleanHistorique();
if (globalDB == null) {
myDebug("SQL : On essaye d'ouvrir la base de données ...");
// myDebug("SQL : On essaye d'ouvrir la base de données ...");
globalDB = window.sqlitePlugin.openDatabase({
name: 'doliscan.db',
location: 'default',
androidDatabaseProvider: 'system'
}, function () {
myDebug("SQL : ouverture OK, creation des tables");
// myDebug("SQL : ouverture OK, creation des tables");
globalDB.executeSql('CREATE TABLE IF NOT EXISTS ldfsToSync (id Integer PRIMARY KEY AUTOINCREMENT, url Text, jsonData Text, localFileName Text);');
myDebug("SQL : create ldfsToSync ok");
// myDebug("SQL : create ldfsToSync ok");
globalDB.executeSql('CREATE UNIQUE INDEX IF NOT EXISTS localFileName on ldfsToSync (localFileName);');
myDebug("SQL : create index on ldfsToSync ok");
// myDebug("SQL : create index on ldfsToSync ok");
globalDB.executeSql('DELETE FROM ldfsToSync WHERE localFileName IS NULL;');
myDebug("SQL : clean up ldfsToSync ok");
// myDebug("SQL : clean up ldfsToSync ok");
globalDB.executeSql("DELETE FROM ldfsToSync WHERE localFileName='undefined';");
myDebug("SQL : clean up 2 ldfsToSync ok");
// myDebug("SQL : clean up 2 ldfsToSync ok");
globalDB.executeSql('CREATE TABLE IF NOT EXISTS config (key Text, value Text);');
myDebug("SQL : create config ok");
// myDebug("SQL : create config ok");
globalDB.executeSql('CREATE UNIQUE INDEX IF NOT EXISTS key on config (key);');
myDebug("SQL : create index on config ok");
// myDebug("SQL : create index on config ok");
globalDB.readTransaction(function (t) {
t.executeSql('SELECT * FROM config WHERE value IS NOT NULL;', [],
function (tx, rs) {
let nbRes = rs.rows.length;
myDebug("SQL : select * from config -> " + nbRes);
// myDebug("SQL : select * from config -> " + nbRes);
let i = 0;
for (i = 0; i < nbRes; i++) {
let k = rs.rows.item(i).key;
......@@ -454,17 +454,17 @@
//Mise a jour uniquement si non vide
if (v != null && v != "") {
localStorage.setItem(k, v);
myDebug(' (c) SQL select localStorage ' + k + ' -> ' + v);
// myDebug(' (c) SQL select localStorage ' + k + ' -> ' + v);
}
else {
myDebug(' (c) SQL select localStorage ' + k + ' -> null on ne fait donc pas le localStorage :-)');
// myDebug(' (c) SQL select localStorage ' + k + ' -> null on ne fait donc pas le localStorage :-)');
}
}
}, function () {
myDebug("SELECT * FROM config ERROR");
// myDebug("SELECT * FROM config ERROR");
});
});
myDebug("SQL : end config ok");
// myDebug("SQL : end config ok");
if (localGetData("api_server") != null) {
gotoLoginPage = false;
......@@ -480,7 +480,7 @@
});
myDebug("SQL : end 2 config ok");
// myDebug("SQL : end 2 config ok");
}
......
This diff is collapsed.
......@@ -2,7 +2,7 @@
* (c) Éric Seigne <eric.seigne@cap-rel.fr> - 2019 - GNU AGPLv3
*/
var globalDevMode = false; // on est en mode dev ou prod ? par defaut oui pour la page d'index
var globalDevMode = true; // on est en mode dev ou prod ? par defaut oui pour la page d'index
var globalMyCamera; // l'appareil photo
var globalpictureSource; // picture source
var globaldestinationType; // sets the format of returned value
......@@ -17,10 +17,11 @@ var globalLastInsertId = ""; // Avant d'envoyer les données au serve
var globalSyncInProgress = false; // Pour savoir si on est en phase de synchronisation ou pas (gestion différente du "formulaire" d'upload)
// **********************************************************************
//Ne pas toucher, mis à jour automatiquement à partir du config.xml
var globalAppVersion = "1.0.12"; // mis à jour automatiquement à partir du fichier config.xml lors du build android
var globalAppVersion = "1.0.14"; // mis à jour automatiquement à partir du fichier config.xml lors du build android
// **********************************************************************
var dataLDF = "";
var globalResumable;
var globalProgressBar;
//liste des donnees que le serveur peut eventuellement échanger avec nous (factorisation de code)
var globalSrvKeys = ['email', 'api_token', 'firstname', 'name', 'adresse', 'cp', 'ville', 'pays', 'send_copy_to', 'compta_email', 'compta_ik', 'compta_peage', 'compta_hotel', 'compta_train', 'compta_carburant', 'compta_taxi', 'compta_restauration', 'compta_divers', 'compta_compteperso', 'compta_tvadeductible', 'official_app_version', 'mode_simple'];
......@@ -9,7 +9,7 @@
function distanceAutomatique(depart, arrivee) {
showWait();
$.ajaxSetup({
timeout: 40000,
timeout: 5000,
});
$.ajax({
type: "GET",
......
......@@ -91,16 +91,16 @@ function feedVehiculesConfig(formField, storageKey) {
//Liste des facturettes en attente de synchronisation ...
function feedToSync(formField) {
let htmlFTS = "";
myDebug("feedToSync pour " + formField);
// myDebug("feedToSync pour " + formField);
$(formField).empty();
if (globalDB != null) {
myDebug("ldfsToSync : select * from ldfsToSync");
// myDebug("ldfsToSync : select * from ldfsToSync");
globalDB.readTransaction(function (t) {
t.executeSql('SELECT * FROM ldfsToSync', [],
function (tx, rs) {
myDebug(" ldfsToSync -> SQL SELECT * OK: res " + rs.rows.length);
// myDebug(" ldfsToSync -> SQL SELECT * OK: res " + rs.rows.length);
let nbRes = rs.rows.length;
let iFTS = 0;
for (iFTS = 0; iFTS < nbRes; iFTS++) {
......@@ -119,11 +119,11 @@ function feedToSync(formField) {
$(formField).append(htmlFTS);
},
function () {
myDebug(" ldfsToSync -> SQL Erreur de SELECT * FROM ldfsToSync");
// myDebug(" ldfsToSync -> SQL Erreur de SELECT * FROM ldfsToSync");
});
});
}
myDebug("end feedToSync ..." + htmlFTS);
// myDebug("end feedToSync ..." + htmlFTS);
return htmlFTS;
}
......
......@@ -10,7 +10,7 @@ function sendForm() {
showWait();
$.ajaxSetup({
timeout: 40000,
timeout: 10000,
headers: {
'X-CSRF-TOKEN': localGetData("api_token")
}
......@@ -80,7 +80,7 @@ function endSendFormForUpdate(formdata) {
var ajaxRequest = $.ajax({
url: localGetData("api_server") + "/api/LdeFrais/" + document.getElementById('updateID').value,
timeout: 40000,
timeout: 10000,
type: "PUT",
data: formJSON,
processData: true,
......@@ -110,17 +110,17 @@ function endSendFormForUpdate(formdata) {
hideWait();
myDebug("Request finished (error signal) : " + textStatus);
},
complete: function (request, textStatus, errorThrown) {
myDebug("Request PUT finished (complete signal) : " + textStatus);
myDebug('Taille des donees envoyees sur le serveur : ' + request.getResponseHeader('Content-Length') + ' octets');
if (request.status == "200") {
gotoPage('messageDone.html', false);
}
else {
ons.notification.alert("Le serveur n'est pas accessible. Votre document est enregistré sur votre téléphone, pensez à le synchroniser avec le serveur dès que vous retrouverez une bonne connexion (utilisez le menu synchronisation à ce moment là).", { title: "Réseau lent" });
gotoPage('menu.html', false);
}
}
// complete: function (request, textStatus, errorThrown) {
// myDebug("Request PUT finished (complete signal) : " + textStatus);
// myDebug('Taille des donees envoyees sur le serveur : ' + request.getResponseHeader('Content-Length') + ' octets');
// if (request.status == "200") {
// gotoPage('messageDone.html', false);
// }
// else {
// ons.notification.alert("Le serveur n'est pas accessible. Votre document est enregistré sur votre téléphone, pensez à le synchroniser avec le serveur dès que vous retrouverez une bonne connexion (utilisez le menu synchronisation à ce moment là).", { title: "Réseau lent" });
// gotoPage('menu.html', false);
// }
// }
});
}
......@@ -175,19 +175,17 @@ function endSendForm(formdata, filename) {
}
}
let ajaxURL = localGetData("api_server") + "/api/LdeFrais";
myDebug(" endSendForm on se connecte sur " + ajaxURL);
myDebug(" endSendForm on se connecte sur " + localGetData("api_server") + "/api/LdeFrais");
var ajaxRequest = $.ajax({
url: ajaxURL,
timeout: 40000,
url: localGetData("api_server") + "/api/LdeFrais",
timeout: 10000,
type: "POST",
data: formdata,
processData: false,
cache: false,
contentType: false,
tryCount: 0,
retryLimit: 1,
tryCount: 3,
retryLimit: 4,
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', 'Bearer ' + localGetData("api_token"));
},
......@@ -208,64 +206,9 @@ function endSendForm(formdata, filename) {
//Expedition du fichier ?
let ajaxURL = localGetData("api_server") + "/api/upload";
//On stoppe tout autre transfert qui serait encore en arrière plan ...
myDebug("Request finished, creation d'un objet resumable");
var resumable = new Resumable({
// Use chunk size that is smaller than your maximum limit due a resumable issue
// https://github.com/23/resumable.js/issues/51
chunkSize: 1 * 512 * 1024, // 512Ko
simultaneousUploads: 1,
testChunks: false,
throttleProgressCallbacks: 1,
// Get the url from data-url tag
target: ajaxURL,
headers: {
ldfFileName: result.fileName,
Authorization: 'Bearer ' + localGetData("api_token"),
}
});
var progressBar = new ProgressBar($('#upload-progress'));
resumable.on('fileAdded', function (file, event) {
myDebug("resumable : fileAdded ");
progressBar.fileAdded();
myDebug("Apres progressBar.fileAdded, on essaye de lancer l'upload");
resumable.upload();
});
resumable.on('fileSuccess', function (file, message) {
myDebug("resumable : fileSuccess ");
progressBar.finish();
if (globalDB != null) {
myDebug("globalLastInsertId : " + globalLastInsertId);
if (globalLastInsertId > 0) {
myDebug(" ldfsToSync SQL DELETE FROM ldfsToSync WHERE ID=" + globalLastInsertId);
globalDB.executeSql('DELETE FROM ldfsToSync WHERE ID=?;', [globalLastInsertId]);
myDebug(" ldfsToSync SQL DELETE FROM ... : ok");
globalLastInsertId = "";
}
}
gotoPage('messageDone.html', false);
});
resumable.on('progress', function () {
myDebug("resumable : progress " + resumable.progress());
progressBar.uploading(resumable.progress() * 100);
// $('#pause-upload-btn').find('.glyphicon').removeClass('glyphicon-play').addClass('glyphicon-pause');
});
resumable.on('pause', function () {
myDebug("resumable : pause ");
// $('#pause-upload-btn').find('.glyphicon').removeClass('glyphicon-pause').addClass('glyphicon-play');
});
pushFileResumeable(filename, resumable, result.fileName);
myDebug("resumable : ajout du fichier");
let r = newResumable(ajaxURL, result, filename);
}
else {
ons.notification.alert("Erreur de transfert avec le serveur. Votre document est enregistré sur votre téléphone, pensez à le synchroniser avec le serveur dès que vous retrouverez une bonne connexion (utilisez le menu synchronisation à ce moment là).", { title: "Réseau lent" });
......@@ -406,3 +349,116 @@ function initialiseForm() {
}
}
function newResumable(ajaxURL, result, filename) {
myDebug("creation de l'objet globalResumable pour " + filename + " a envoyer vers " + ajaxURL);
//Si on avait déjà un resume.js qui bosse (planté ?) on stoppe tout
if (typeof globalProgressBar !== 'undefined') {
globalResumable.cancel();
}
//Note/WARNING : ne pas passer en dessous de 40.000 ms de xhrTimeout sinon ça déconne complet
//tests 4G eric@garage ...
globalResumable = new Resumable({
// Use chunk size that is smaller than your maximum limit due a resumable issue
// https://github.com/23/resumable.js/issues/51
chunkSize: 1 * 128 * 1024, // 128Ko ? 512Ko ?
simultaneousUploads: 4,
testChunks: true,
throttleProgressCallbacks: 1,
chunkRetryInterval: 500,
maxChunkRetries: 4,
xhrTimeout: 40000,
// Get the url from data-url tag
target: ajaxURL,
headers: {
ldfFileName: result.fileName,
Authorization: 'Bearer ' + localGetData("api_token"),
}
});
if (typeof globalProgressBar === 'undefined') {
myDebug("resumable : creation de globalProgressBar ");
globalProgressBar = new ProgressBar($('#upload-progress'));
}
globalResumable.on('fileAdded', function (file, event) {
myDebug("resumable : fileAdded ");
globalProgressBar.fileAdded();
myDebug("Apres progressBar.fileAdded, on essaye de lancer l'upload");
globalResumable.upload();
});
globalResumable.on('fileSuccess', function (file, message) {
myDebug("resumable : fileSuccess ");
globalProgressBar.finish();
if (globalDB != null) {
myDebug("globalLastInsertId : " + globalLastInsertId);
if (globalLastInsertId > 0) {
myDebug(" ldfsToSync SQL DELETE FROM ldfsToSync WHERE ID=" + globalLastInsertId);
globalDB.executeSql('DELETE FROM ldfsToSync WHERE ID=?;', [globalLastInsertId]);
myDebug(" ldfsToSync SQL DELETE FROM ... : ok");
globalLastInsertId = "";
}
}
gotoPage('messageDone.html', false);
});
globalResumable.on('fileError', function (file, message) {
myDebug("resumable : fileError ");
ons.notification.alert("Erreur de transfert du fichier, réessayez plus tard depuis le menu Synchronisation. Assurez-vous d'avoir une bonne couverture 4G/Data ou d'utiliser une connexion internet rapide via WiFi.", { title: "Attention" });
gotoPage('menu.html', false);
});
globalResumable.on('progress', function () {
myDebug("resumable : progress " + globalResumable.progress());
globalProgressBar.uploading(globalResumable.progress() * 100);
// $('#pause-upload-btn').find('.glyphicon').removeClass('glyphicon-play').addClass('glyphicon-pause');
});
globalResumable.on('pause', function () {
myDebug("resumable : pause ");
// $('#pause-upload-btn').find('.glyphicon').removeClass('glyphicon-pause').addClass('glyphicon-play');
});
myDebug("resumable : ajout du fichier " + filename);
/* *************************************
* on expédie le fichier sur le serveur en utilisant le découpeur de fichier
* resumable.js histoire de proposer une barre de progression et de rendre
* tout ça un peu plus pro
*************************************** */
myDebug('pushFileResumeable (1) : ' + filename);
window.resolveLocalFileSystemURL(filename, function (fileEntry) {
// myDebug("readFromFile (2): " + JSON.stringify(fileEntry));
fileEntry.file(function (file) {
var reader = new FileReader();
var fileSize = file.size;
// myDebug("readFromFile (3) Size = " + file.size);
reader.onloadend = function (e) {
var fileContent = new Uint8Array(reader.result);
var myBlob = new Blob([fileContent], { type: 'image/jpeg' });
myBlob.name = result.fileName;
myDebug("pushFileResumeable : addFile sur globalResumable");
globalResumable.addFile(myBlob);
// -------------------------------------------------
//On sauvegarde le Fichier sous un nom qui sera plus facile a gerer par la suite
// var today = new Date();
// var destFilename = today.getFullYear().toString() + ('0' + (today.getMonth() + 1)).slice(-2) + ('0' + today.getDate()).slice(-2) + "_"
// + today.getHours().toString() + today.getMinutes().toString() + "-" + subject + "-" + basename(fileName);
// moveFile(fileName, destFilename);
// -------------------------------------------------
};
reader.readAsArrayBuffer(file);
}, errorHandlerFileResumeable.bind(null, "FileEntry " + filename));
}, errorHandlerFileResumeable.bind(null, "resolveLocalFileSystemURL " + filename));
}
......@@ -3,103 +3,103 @@
*/
function updateLocalUserFromServerSuccessMessage(code) {
ons.notification.alert('Données sauvegardées, vous pouvez revenir au menu principal...', { title: "Succès" });
}
ons.notification.alert('Données sauvegardées, vous pouvez revenir au menu principal...', { title: "Succès" });
}
function updateLocalUserFromServerSuccess(resultBool) {
myDebug("updateLocalUserFromServerSuccess : " + resultBool);
//pour une raison obscure l'appel direct deconne parfois alors que via
if (resultBool) {
myDebug("resultBool 1");
window.location.assign("app.html");
}
else {
myDebug("resultBool 0");
window.location.assign("index.html");
}
function updateLocalUserFromServerSuccess(resultBool) {
// myDebug("updateLocalUserFromServerSuccess : " + resultBool);
//pour une raison obscure l'appel direct deconne parfois alors que via
if (resultBool) {
// myDebug("resultBool 1");
window.location.assign("app.html");
}
else {
// myDebug("resultBool 0");
window.location.assign("index.html");
}
}
//Factorisation de code: on analyse un result serveur pour sauvegarder en local les donnees
function updateLocalUserFromServer(result, callback) {
myDebug("======================================================== updateLocalUserFromServer");
let ret = false;
myDebug(JSON.stringify(result));
if ((typeof result !== 'undefined') && (result != null) && (result != "")) {
for (let key of globalSrvKeys) {
console.log("On cherche " + key);
if ((typeof result.data !== 'undefined') && (result.data != null) && (result.data != "")) {
myDebug('updateLocalUserFromServer typeOf : ' + (typeof result.data));
myDebug("on essaye : " + key + " -> " + result.data[key]);
if ((typeof result.data[key] !== 'undefined') && (result.data[key] != null) && (result.data[key] != "")) {
myDebug(" JSON / valeur non nulle on la stocke : " + key + " = " + result.data[key]);
localStoreData(key, result.data[key]);
ret = true;
}
//Factorisation de code: on analyse un result serveur pour sauvegarder en local les donnees
function updateLocalUserFromServer(result, callback) {
myDebug("======================================================== updateLocalUserFromServer");
let ret = false;
// myDebug(JSON.stringify(result));
if ((typeof result !== 'undefined') && (result != null) && (result != "")) {
for (let key of globalSrvKeys) {
console.log("On cherche " + key);
if ((typeof result.data !== 'undefined') && (result.data != null) && (result.data != "")) {
// myDebug('updateLocalUserFromServer typeOf : ' + (typeof result.data));
// myDebug("on essaye : " + key + " -> " + result.data[key]);
if ((typeof result.data[key] !== 'undefined') && (result.data[key] != null) && (result.data[key] != "")) {
// myDebug(" JSON / valeur non nulle on la stocke : " + key + " = " + result.data[key]);
localStoreData(key, result.data[key]);
ret = true;
}
else {
//Dans les retours de PUT on a droit aux donnees directement
if ((typeof result[key] !== 'undefined') && (result[key] != null) && (result[key] != "")) {
myDebug(" PUT / valeur non nulle on la stocke : " + key + " = " + result.data[key]);
localStoreData(key, result[key]);
ret = true;
}
}
else {
//Dans les retours de PUT on a droit aux donnees directement
if ((typeof result[key] !== 'undefined') && (result[key] != null) && (result[key] != "")) {
// myDebug(" PUT / valeur non nulle on la stocke : " + key + " = " + result.data[key]);
localStoreData(key, result[key]);
ret = true;
}
}
}
callback(ret);
}
callback(ret);
}
//Factorisation de code: on envoie le profil utilisateur sur le serveur
function updateServerUserFromLocal() {
var data = {};
//Factorisation de code: on envoie le profil utilisateur sur le serveur
function updateServerUserFromLocal() {
var data = {};
for (let key of globalSrvKeys) {
let value = localGetData(key);
if (value != null) {
data[key] = value;
}
for (let key of globalSrvKeys) {
let value = localGetData(key);
if (value != null) {
data[key]<