- fixed final "map update" sync request beforeUnload event

- fixed a bug where "multi character location tracking" is not working on different browser tabs, #446
- fixed a "map sync" bug with 2 open browser tabs with the same character active
This commit is contained in:
Mark Friedrich
2019-07-05 16:57:33 +02:00
parent 437fdf0db9
commit f4f30e0975
18 changed files with 380 additions and 224 deletions

View File

@@ -74,15 +74,25 @@ class AccessController extends Controller {
return $loginStatus;
}
/**
* broadcast MapModel to clients
* @see broadcastMapData()
* @param Pathfinder\MapModel $map
*/
protected function broadcastMap(Pathfinder\MapModel $map) : void {
$this->broadcastMapData($this->getFormattedMapData($map));
}
/**
* broadcast map data to clients
* -> send over TCP Socket
* @param Pathfinder\MapModel $map
* @throws \Exception
* @param array|null $mapData
*/
protected function broadcastMapData(Pathfinder\MapModel $map) : void {
$mapData = $this->getFormattedMapData($map);
$this->getF3()->webSocket()->write('mapUpdate', $mapData);
protected function broadcastMapData(?array $mapData) : void {
if(!empty($mapData)){
$this->getF3()->webSocket()->write('mapUpdate', $mapData);
}
}
/**
@@ -91,16 +101,27 @@ class AccessController extends Controller {
* @return array
* @throws \Exception
*/
protected function getFormattedMapData(Pathfinder\MapModel $map) : array {
$mapData = $map->getData();
return [
'config' => $mapData->mapData,
'data' => [
'systems' => $mapData->systems,
'connections' => $mapData->connections,
]
];
/**
* @param Pathfinder\MapModel $map
* @return array|null
*/
protected function getFormattedMapData(Pathfinder\MapModel $map) : ?array {
$data = null;
try{
$mapData = $map->getData();
$data = [
'config' => $mapData->mapData,
'data' => [
'systems' => $mapData->systems,
'connections' => $mapData->connections,
]
];
}catch(\Exception $e){
}
return $data;
}
}

View File

@@ -660,7 +660,7 @@ class Map extends Controller\AccessController {
$this->getF3()->webSocket()->write('mapAccess', $mapAccess);
// map has (probably) active connections that should receive map Data
$this->broadcastMapData($map);
$this->broadcastMap($map);
}
/**
@@ -689,9 +689,12 @@ class Map extends Controller\AccessController {
unset($characterData->corporation->rights);
}
// access token
$token = bin2hex(random_bytes(16));
$return->data = [
'id' => $activeCharacter->_id,
'token' => bin2hex(random_bytes(16)), // token for character access
'token' => $token, // character access
'characterData' => $characterData,
'mapData' => []
];
@@ -700,7 +703,7 @@ class Map extends Controller\AccessController {
foreach($maps as $map){
$return->data['mapData'][] = [
'id' => $map->_id,
'token' => bin2hex(random_bytes(16)), // token for map access
'token' => $token, // map access
'name' => $map->name
];
}
@@ -722,41 +725,33 @@ class Map extends Controller\AccessController {
}
/**
* update map data
* -> function is called continuously (trigger) by any active client
* @param \Base $f3
* @throws Exception
* update maps with $mapsData where $character has access to
* @param Pathfinder\CharacterModel $character
* @param array $mapsData
* @return \stdClass
*/
public function updateData(\Base $f3){
$postData = (array)$f3->get('POST');
$mapData = (array)$postData['mapData'];
$userDataRequired = (bool)$postData['getUserData'];
protected function updateMapsData(Pathfinder\CharacterModel $character, array $mapsData) : \stdClass {
$return = (object) [];
$return->error = [];
$return->mapData = [];
$activeCharacter = $this->getCharacter();
$mapIdsChanged = [];
$maps = $character->getMaps();
// get current map data
$maps = $activeCharacter->getMaps();
// if there is any system/connection change data submitted -> save new data
if( !empty($maps) && !empty($mapData) ){
// loop all submitted map data that should be saved
if(!empty($mapsData) && !empty($maps)){
// loop all $mapsData that should be saved
// -> currently there will only be ONE map data change submitted -> single loop
foreach($mapData as $data){
foreach($mapsData as $data){
$systems = [];
$connections = [];
// check whether system data and/or connection data is send
// empty arrays are not included in ajax requests
if( isset($data['data']['systems']) ){
if(isset($data['data']['systems'])){
$systems = (array)$data['data']['systems'];
}
if( isset($data['data']['connections']) ){
if(isset($data['data']['connections'])){
$connections = (array)$data['data']['connections'];
}
@@ -769,15 +764,15 @@ class Map extends Controller\AccessController {
// loop current user maps and check for changes
foreach($maps as $map){
$mapChanged = false;
// update system data -------------------------------------------------------------------------
foreach($systems as $i => $systemData){
// check if current system belongs to the current map
if($system = $map->getSystemById((int)$systemData['id'])){
$system->copyfrom($systemData, ['alias', 'status', 'position', 'locked', 'rallyUpdated', 'rallyPoke']);
if($system->save($activeCharacter)){
$mapChanged = true;
if($system->save($character)){
if(!in_array($map->_id, $mapIdsChanged)){
$mapIdsChanged[] = $map->_id;
}
// one system belongs to ONE map -> speed up for multiple maps
unset($systemData[$i]);
}else{
@@ -791,8 +786,10 @@ class Map extends Controller\AccessController {
// check if the current connection belongs to the current map
if($connection = $map->getConnectionById((int)$connectionData['id'])){
$connection->copyfrom($connectionData, ['scope', 'type', 'endpoints']);
if($connection->save($activeCharacter)){
$mapChanged = true;
if($connection->save($character)){
if(!in_array($map->_id, $mapIdsChanged)){
$mapIdsChanged[] = $map->_id;
}
// one connection belongs to ONE map -> speed up for multiple maps
unset($connectionData[$i]);
}else{
@@ -800,17 +797,39 @@ class Map extends Controller\AccessController {
}
}
}
if($mapChanged){
$this->broadcastMapData($map);
}
}
}
}
}
// format map Data for return
$return->mapData = $this->getFormattedMapsData($maps);
foreach($maps as $map){
// format map Data for return/broadcast
if($mapData = $this->getFormattedMapData($map)){
if(in_array($map->_id, $mapIdsChanged)){
$this->broadcastMapData($mapData);
}
$return->mapData[] = $mapData;
}
}
return $return;
}
/**
* update map data
* -> function is called continuously (trigger) by any active client
* @param \Base $f3
* @throws Exception
*/
public function updateData(\Base $f3){
$postData = (array)$f3->get('POST');
$mapsData = (array)$postData['mapData'];
$userDataRequired = (bool)$postData['getUserData'];
$activeCharacter = $this->getCharacter();
$return = $this->updateMapsData($activeCharacter, $mapsData);
// if userData is requested -> add it as well
// -> Only first trigger call should request this data!
@@ -822,18 +841,22 @@ class Map extends Controller\AccessController {
}
/**
* get formatted map data
* @param Pathfinder\MapModel[] $mapModels
* @return array
* onUnload map sync
* @see https://developer.mozilla.org/docs/Web/API/Navigator/sendBeacon
* @param \Base $f3
* @throws Exception
*/
protected function getFormattedMapsData(array $mapModels) : array {
$mapData = [];
foreach($mapModels as $mapModel){
$mapData[] = $this->getFormattedMapData($mapModel);
}
public function updateUnloadData(\Base $f3){
$postData = (array)$f3->get('POST');
return $mapData;
if(!empty($mapsData = (string)$postData['mapData'])){
$mapsData = (array)json_decode($mapsData, true);
if(($jsonError = json_last_error()) === JSON_ERROR_NONE){
$activeCharacter = $this->getCharacter();
$this->updateMapsData($activeCharacter, $mapsData);
}
}
}
/**
@@ -1137,7 +1160,7 @@ class Map extends Controller\AccessController {
}
if($mapDataChanged){
$this->broadcastMapData($map);
$this->broadcastMap($map);
}
return $map;

View File

@@ -56,7 +56,7 @@ class Connection extends AbstractRestController {
$connectionData = $connection->getData();
// broadcast map changes
$this->broadcastMapData($connection->mapId);
$this->broadcastMap($connection->mapId);
}
}
}
@@ -95,7 +95,7 @@ class Connection extends AbstractRestController {
// broadcast map changes
if(count($deletedConnectionIds)){
$this->broadcastMapData($map);
$this->broadcastMap($map);
}
}
}

View File

@@ -144,7 +144,7 @@ class System extends AbstractRestController {
}
// broadcast map changes
if(count($deletedSystemIds)){
$this->broadcastMapData($map);
$this->broadcastMap($map);
}
}
}
@@ -187,7 +187,7 @@ class System extends AbstractRestController {
$newSystem->clearCacheData();
// broadcast map changes
$this->broadcastMapData($newSystem->mapId);
$this->broadcastMap($newSystem->mapId);
return $newSystem;
}

View File

@@ -64,10 +64,15 @@ class CharacterUpdate extends AbstractCron {
* @var $characterLog Pathfinder\CharacterLogModel
*/
if(is_object($characterLog->characterId)){
// force characterLog as "updated" even if no changes were made
$characterLog->characterId->updateLog([
'markUpdated' => true
]);
if($accessToken = $characterLog->characterId->getAccessToken()){
if($this->isOnline($accessToken)){
// force characterLog as "updated" even if no changes were made
$characterLog->touch('updated');
$characterLog->save();
}else{
$characterLog->erase();
}
}
}else{
// character_log does not have a character assigned -> delete
$characterLog->erase();

View File

@@ -507,7 +507,7 @@ class CharacterModel extends AbstractPathfinderModel {
/**
* get ESI API "access_token" from OAuth
* @return bool|mixed
* @return bool|string
*/
public function getAccessToken(){
$accessToken = false;
@@ -797,6 +797,31 @@ class CharacterModel extends AbstractPathfinderModel {
$this->roleId = $this->requestRole();
}
/**
* get online status data from ESI
* @param string $accessToken
* @return array
*/
protected function getOnlineData(string $accessToken) : array {
return self::getF3()->ccpClient()->getCharacterOnlineData($this->_id, $accessToken);
}
/**
* check online state from ESI
* @param string $accessToken
* @return bool
*/
public function isOnline(string $accessToken) : bool {
$isOnline = false;
$onlineData = $this->getOnlineData($accessToken);
if($onlineData['online'] === true){
$isOnline = true;
}
return $isOnline;
}
/**
* update character log (active system, ...)
* -> API request for character log data
@@ -815,170 +840,162 @@ class CharacterModel extends AbstractPathfinderModel {
$this->hasBasicScopes()
){
// Try to pull data from API
if( $accessToken = $this->getAccessToken() ){
$onlineData = self::getF3()->ccpClient()->getCharacterOnlineData($this->_id, $accessToken);
if($accessToken = $this->getAccessToken()){
if($this->isOnline($accessToken)){
$locationData = self::getF3()->ccpClient()->getCharacterLocationData($this->_id, $accessToken);
// check whether character is currently ingame online
if(is_bool($onlineData['online'])){
if($onlineData['online'] === true){
$locationData = self::getF3()->ccpClient()->getCharacterLocationData($this->_id, $accessToken);
if( !empty($locationData['system']['id']) ){
// character is currently in-game
if( !empty($locationData['system']['id']) ){
// character is currently in-game
// get current $characterLog or get new ---------------------------------------------------
if( !($characterLog = $this->getLog()) ){
// create new log
$characterLog = $this->rel('characterLog');
}
// get current $characterLog or get new ---------------------------------------------------
if( !($characterLog = $this->getLog()) ){
// create new log
$characterLog = $this->rel('characterLog');
}
// get current log data and modify on change
$logData = json_decode(json_encode( $characterLog->getData()), true);
// get current log data and modify on change
$logData = json_decode(json_encode( $characterLog->getData()), true);
// check system and station data for changes ----------------------------------------------
// check system and station data for changes ----------------------------------------------
// IDs for "systemId", "stationId" that require more data
$lookupUniverseIds = [];
// IDs for "systemId", "stationId" that require more data
$lookupUniverseIds = [];
if(
empty($logData['system']['name']) ||
$logData['system']['id'] !== $locationData['system']['id']
){
// system changed -> request "system name" for current system
$lookupUniverseIds[] = $locationData['system']['id'];
}
if( !empty($locationData['station']['id']) ){
if(
empty($logData['system']['name']) ||
$logData['system']['id'] !== $locationData['system']['id']
empty($logData['station']['name']) ||
$logData['station']['id'] !== $locationData['station']['id']
){
// system changed -> request "system name" for current system
$lookupUniverseIds[] = $locationData['system']['id'];
// station changed -> request "station name" for current station
$lookupUniverseIds[] = $locationData['station']['id'];
}
}else{
unset($logData['station']);
}
if( !empty($locationData['station']['id']) ){
$logData = array_replace_recursive($logData, $locationData);
// get "more" data for systemId and/or stationId -----------------------------------------
if( !empty($lookupUniverseIds) ){
// get "more" information for some Ids (e.g. name)
$universeData = self::getF3()->ccpClient()->getUniverseNamesData($lookupUniverseIds);
if( !empty($universeData) && !isset($universeData['error']) ){
// We expect max ONE system AND/OR station data, not an array of e.g. systems
if(!empty($universeData['system'])){
$universeData['system'] = reset($universeData['system']);
}
if(!empty($universeData['station'])){
$universeData['station'] = reset($universeData['station']);
}
$logData = array_replace_recursive($logData, $universeData);
}else{
// this is important! universe data is a MUST HAVE!
$deleteLog = true;
}
}
// check structure data for changes -------------------------------------------------------
if(!$deleteLog){
// IDs for "structureId" that require more data
$lookupStructureId = 0;
if( !empty($locationData['structure']['id']) ){
if(
empty($logData['station']['name']) ||
$logData['station']['id'] !== $locationData['station']['id']
empty($logData['structure']['name']) ||
$logData['structure']['id'] !== $locationData['structure']['id']
){
// station changed -> request "station name" for current station
$lookupUniverseIds[] = $locationData['station']['id'];
// structure changed -> request "structure name" for current station
$lookupStructureId = $locationData['structure']['id'];
}
}else{
unset($logData['station']);
unset($logData['structure']);
}
$logData = array_replace_recursive($logData, $locationData);
// get "more" data for systemId and/or stationId -----------------------------------------
if( !empty($lookupUniverseIds) ){
// get "more" information for some Ids (e.g. name)
$universeData = self::getF3()->ccpClient()->getUniverseNamesData($lookupUniverseIds);
if( !empty($universeData) && !isset($universeData['error']) ){
// We expect max ONE system AND/OR station data, not an array of e.g. systems
if(!empty($universeData['system'])){
$universeData['system'] = reset($universeData['system']);
}
if(!empty($universeData['station'])){
$universeData['station'] = reset($universeData['station']);
}
$logData = array_replace_recursive($logData, $universeData);
}else{
// this is important! universe data is a MUST HAVE!
$deleteLog = true;
}
}
// check structure data for changes -------------------------------------------------------
if(!$deleteLog){
// IDs for "structureId" that require more data
$lookupStructureId = 0;
if( !empty($locationData['structure']['id']) ){
if(
empty($logData['structure']['name']) ||
$logData['structure']['id'] !== $locationData['structure']['id']
){
// structure changed -> request "structure name" for current station
$lookupStructureId = $locationData['structure']['id'];
}
// get "more" data for structureId ---------------------------------------------------
if($lookupStructureId > 0){
/**
* @var $structureModel Universe\StructureModel
*/
$structureModel = Universe\AbstractUniverseModel::getNew('StructureModel');
$structureModel->loadById($lookupStructureId, $accessToken, $additionalOptions);
if(!$structureModel->dry()){
$structureData['structure'] = (array)$structureModel->getData();
$logData = array_replace_recursive($logData, $structureData);
}else{
unset($logData['structure']);
}
}
}
// get "more" data for structureId ---------------------------------------------------
if($lookupStructureId > 0){
/**
* @var $structureModel Universe\StructureModel
*/
$structureModel = Universe\AbstractUniverseModel::getNew('StructureModel');
$structureModel->loadById($lookupStructureId, $accessToken, $additionalOptions);
if(!$structureModel->dry()){
$structureData['structure'] = (array)$structureModel->getData();
$logData = array_replace_recursive($logData, $structureData);
}else{
unset($logData['structure']);
}
// check ship data for changes ------------------------------------------------------------
if( !$deleteLog ){
$shipData = self::getF3()->ccpClient()->getCharacterShipData($this->_id, $accessToken);
// IDs for "shipTypeId" that require more data
$lookupShipTypeId = 0;
if( !empty($shipData['ship']['typeId']) ){
if(
empty($logData['ship']['typeName']) ||
$logData['ship']['typeId'] !== $shipData['ship']['typeId']
){
// ship changed -> request "station name" for current station
$lookupShipTypeId = $shipData['ship']['typeId'];
}
// "shipName"/"shipId" could have changed...
$logData = array_replace_recursive($logData, $shipData);
}else{
// ship data should never be empty -> keep current one
//unset($logData['ship']);
$invalidResponse = true;
}
// check ship data for changes ------------------------------------------------------------
if( !$deleteLog ){
$shipData = self::getF3()->ccpClient()->getCharacterShipData($this->_id, $accessToken);
// IDs for "shipTypeId" that require more data
$lookupShipTypeId = 0;
if( !empty($shipData['ship']['typeId']) ){
if(
empty($logData['ship']['typeName']) ||
$logData['ship']['typeId'] !== $shipData['ship']['typeId']
){
// ship changed -> request "station name" for current station
$lookupShipTypeId = $shipData['ship']['typeId'];
}
// "shipName"/"shipId" could have changed...
// get "more" data for shipTypeId ----------------------------------------------------
if($lookupShipTypeId > 0){
/**
* @var $typeModel Universe\TypeModel
*/
$typeModel = Universe\AbstractUniverseModel::getNew('TypeModel');
$typeModel->loadById($lookupShipTypeId, '', $additionalOptions);
if(!$typeModel->dry()){
$shipData['ship'] = (array)$typeModel->getShipData();
$logData = array_replace_recursive($logData, $shipData);
}else{
// ship data should never be empty -> keep current one
//unset($logData['ship']);
$invalidResponse = true;
}
// get "more" data for shipTypeId ----------------------------------------------------
if($lookupShipTypeId > 0){
/**
* @var $typeModel Universe\TypeModel
*/
$typeModel = Universe\AbstractUniverseModel::getNew('TypeModel');
$typeModel->loadById($lookupShipTypeId, '', $additionalOptions);
if(!$typeModel->dry()){
$shipData['ship'] = (array)$typeModel->getShipData();
$logData = array_replace_recursive($logData, $shipData);
}else{
// this is important! ship data is a MUST HAVE!
$deleteLog = true;
}
// this is important! ship data is a MUST HAVE!
$deleteLog = true;
}
}
}
if( !$deleteLog ){
// mark log as "updated" even if no changes were made
if($additionalOptions['markUpdated'] === true){
$characterLog->touch('updated');
}
$characterLog->setData($logData);
$characterLog->characterId = $this->id;
$characterLog->save();
$this->characterLog = $characterLog;
if( !$deleteLog ){
// mark log as "updated" even if no changes were made
if($additionalOptions['markUpdated'] === true){
$characterLog->touch('updated');
}
}else{
// systemId should always exists
$invalidResponse = true;
$characterLog->setData($logData);
$characterLog->characterId = $this->id;
$characterLog->save();
$this->characterLog = $characterLog;
}
}else{
// user is in-game offline
$deleteLog = true;
// systemId should always exists
$invalidResponse = true;
}
}else{
// online status request failed
$invalidResponse = true;
// user is in-game offline
$deleteLog = true;
}
}else{
// access token request failed

View File

@@ -287,7 +287,8 @@ EXECUTION_LIMIT = 50
; CACHE ===========================================================================================
[PATHFINDER.CACHE]
; Delete character log data if nothing (ship/system/...) changed for X seconds
; Checks "character log" data by cronjob after x seconds
; If character is ingame offline -> delete "character log"
; Syntax: Integer (seconds)
; Default: 180
CHARACTER_LOG_INACTIVE = 180

View File

@@ -18,6 +18,9 @@ GET|POST /api/@controller/@action [ajax] = Controller\Api\@cont
GET|POST /api/@controller/@action/@arg1 [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action/@arg1/@arg2 [ajax] = Controller\Api\@controller->@action, 0, 512
; onUnload route or final map sync (@see https://developer.mozilla.org/docs/Web/API/Navigator/sendBeacon)
POST /api/map/updateUnloadData = Controller\Api\map->updateUnloadData, 0, 512
[maps]
; REST API wildcard endpoints (not cached, throttled)
/api/rest/@controller* [ajax] = Controller\Api\Rest\@controller, 0, 512

View File

@@ -27,6 +27,7 @@ define(['jquery'], ($) => {
getAccessData: '/api/map/getAccessData', // ajax URL - get map access tokens (WebSocket)
updateMapData: '/api/map/updateData', // ajax URL - main map update trigger
updateUserData: '/api/map/updateUserData', // ajax URL - main map user data trigger
updateUnloadData: '/api/map/updateUnloadData', // post URL - for my sync onUnload
// map API
saveMap: '/api/map/save', // ajax URL - save/update map
deleteMap: '/api/map/delete', // ajax URL - delete map

View File

@@ -121,9 +121,12 @@ define([
* -> this removes the port from its port collection and closes it
*/
let close = () => {
let MsgWorkerClose = new MsgWorker('sw:closePort');
MsgWorkerClose.task('unsubscribe');
sendMessage(MsgWorkerClose);
// check if MsgWorker is available (SharedWorker was initialized)
if(MsgWorker){
let MsgWorkerClose = new MsgWorker('sw:closePort');
MsgWorkerClose.task('unsubscribe');
sendMessage(MsgWorkerClose);
}
};
/**

View File

@@ -549,14 +549,42 @@ define([
// initial start of the map update function
triggerMapUpdatePing(true);
/**
* handles "final" map update request before window.unload event
* -> navigator.sendBeacon browser support required
* ajax would not work here, because browsers might cancel the request!
* @param mapModule
*/
let mapUpdateUnload = mapModule => {
// get updated map data
let mapData = ModuleMap.getMapModuleDataForUpdate(mapModule);
if(mapData.length){
let fd = new FormData();
fd.set('mapData', JSON.stringify(mapData));
navigator.sendBeacon(Init.path.updateUnloadData, fd);
console.info('Map update request send by: %O', navigator.sendBeacon);
}
};
// Send map update request on tab close/reload, in order to save map changes that
// haven´t been saved through default update trigger
window.addEventListener('beforeunload', function(e){
// close connection to "SharedWorker"
// close "SharedWorker" connection
MapWorker.close();
// clear periodic update timeouts
// -> this function will handle the final map update request
clearUpdateTimeouts();
// save unsaved map changes ...
triggerMapUpdatePing();
if(navigator.sendBeacon){
mapUpdateUnload(mapModule);
}else{
// fallback if sendBeacon() is not supported by browser
triggerMapUpdatePing();
}
// check if character should be switched on reload or current character should be loaded afterwards
let characterSwitch = Boolean( $('body').data('characterSwitch') );
@@ -569,7 +597,7 @@ define([
// IMPORTANT, return false in order to not "abort" ajax request in background!
return false;
});
}, false);
};

View File

@@ -1339,16 +1339,17 @@ define([
* collect all data (systems/connections) for export/save from each active map in the map module
* if no change detected -> do not attach map data to return array
* @param mapModule
* @param filter
* @returns {Array}
*/
let getMapModuleDataForUpdate = mapModule => {
let getMapModuleDataForUpdate = (mapModule, filter = ['hasId', 'hasChanged']) => {
// get all active map elements for module
let mapElements = getMaps(mapModule);
let data = [];
for(let i = 0; i < mapElements.length; i++){
// get all changed (system / connection) data from this map
let mapData = Map.getMapDataForSync($(mapElements[i]), ['hasId', 'hasChanged']);
let mapData = Map.getMapDataForSync($(mapElements[i]), filter);
if(mapData !== false){
if(
mapData.data.systems.length > 0 ||

View File

@@ -182,10 +182,20 @@ self.addEventListener('connect', event => { // jshint ignore:line
case 'sw:closePort':
port.close();
socket.send(JSON.stringify({
task: MsgWorkerMessage.task(),
load: removePort(port)
}));
// remove port from store
// -> charIds managed by closed port
let characterIds = removePort(port);
// check if there are still other ports active that manage removed ports
// .. if not -> send "unsubscribe" event to WebSocket server
let portsLeft = getPortsByCharacterIds(characterIds);
if(!portsLeft.length){
socket.send(JSON.stringify({
task: MsgWorkerMessage.task(),
load: characterIds
}));
}
break;
case 'ws:close':
// closeSocket();

View File

@@ -27,6 +27,7 @@ define(['jquery'], ($) => {
getAccessData: '/api/map/getAccessData', // ajax URL - get map access tokens (WebSocket)
updateMapData: '/api/map/updateData', // ajax URL - main map update trigger
updateUserData: '/api/map/updateUserData', // ajax URL - main map user data trigger
updateUnloadData: '/api/map/updateUnloadData', // post URL - for my sync onUnload
// map API
saveMap: '/api/map/save', // ajax URL - save/update map
deleteMap: '/api/map/delete', // ajax URL - delete map

View File

@@ -121,9 +121,12 @@ define([
* -> this removes the port from its port collection and closes it
*/
let close = () => {
let MsgWorkerClose = new MsgWorker('sw:closePort');
MsgWorkerClose.task('unsubscribe');
sendMessage(MsgWorkerClose);
// check if MsgWorker is available (SharedWorker was initialized)
if(MsgWorker){
let MsgWorkerClose = new MsgWorker('sw:closePort');
MsgWorkerClose.task('unsubscribe');
sendMessage(MsgWorkerClose);
}
};
/**

View File

@@ -549,14 +549,42 @@ define([
// initial start of the map update function
triggerMapUpdatePing(true);
/**
* handles "final" map update request before window.unload event
* -> navigator.sendBeacon browser support required
* ajax would not work here, because browsers might cancel the request!
* @param mapModule
*/
let mapUpdateUnload = mapModule => {
// get updated map data
let mapData = ModuleMap.getMapModuleDataForUpdate(mapModule);
if(mapData.length){
let fd = new FormData();
fd.set('mapData', JSON.stringify(mapData));
navigator.sendBeacon(Init.path.updateUnloadData, fd);
console.info('Map update request send by: %O', navigator.sendBeacon);
}
};
// Send map update request on tab close/reload, in order to save map changes that
// haven´t been saved through default update trigger
window.addEventListener('beforeunload', function(e){
// close connection to "SharedWorker"
// close "SharedWorker" connection
MapWorker.close();
// clear periodic update timeouts
// -> this function will handle the final map update request
clearUpdateTimeouts();
// save unsaved map changes ...
triggerMapUpdatePing();
if(navigator.sendBeacon){
mapUpdateUnload(mapModule);
}else{
// fallback if sendBeacon() is not supported by browser
triggerMapUpdatePing();
}
// check if character should be switched on reload or current character should be loaded afterwards
let characterSwitch = Boolean( $('body').data('characterSwitch') );
@@ -569,7 +597,7 @@ define([
// IMPORTANT, return false in order to not "abort" ajax request in background!
return false;
});
}, false);
};

View File

@@ -1339,16 +1339,17 @@ define([
* collect all data (systems/connections) for export/save from each active map in the map module
* if no change detected -> do not attach map data to return array
* @param mapModule
* @param filter
* @returns {Array}
*/
let getMapModuleDataForUpdate = mapModule => {
let getMapModuleDataForUpdate = (mapModule, filter = ['hasId', 'hasChanged']) => {
// get all active map elements for module
let mapElements = getMaps(mapModule);
let data = [];
for(let i = 0; i < mapElements.length; i++){
// get all changed (system / connection) data from this map
let mapData = Map.getMapDataForSync($(mapElements[i]), ['hasId', 'hasChanged']);
let mapData = Map.getMapDataForSync($(mapElements[i]), filter);
if(mapData !== false){
if(
mapData.data.systems.length > 0 ||

View File

@@ -182,10 +182,20 @@ self.addEventListener('connect', event => { // jshint ignore:line
case 'sw:closePort':
port.close();
socket.send(JSON.stringify({
task: MsgWorkerMessage.task(),
load: removePort(port)
}));
// remove port from store
// -> charIds managed by closed port
let characterIds = removePort(port);
// check if there are still other ports active that manage removed ports
// .. if not -> send "unsubscribe" event to WebSocket server
let portsLeft = getPortsByCharacterIds(characterIds);
if(!portsLeft.length){
socket.send(JSON.stringify({
task: MsgWorkerMessage.task(),
load: characterIds
}));
}
break;
case 'ws:close':
// closeSocket();