diff --git a/src-electron/ui/browser-api.js b/src-electron/ui/browser-api.js index 43351351bb..4044e0c5da 100644 --- a/src-electron/ui/browser-api.js +++ b/src-electron/ui/browser-api.js @@ -29,10 +29,15 @@ const env = require('../util/env') * @returns session UUID */ async function getSessionUuidFromBrowserWindow(browserWindow) { - let sessionUuid = await browserWindow.webContents.executeJavaScript( + // Here, we're retrieving the map of all available sessions and return them. + let sessionMapJson = await browserWindow.webContents.executeJavaScript( 'window.sessionStorage.getItem("session_uuid")' ) - return sessionUuid + if (sessionMapJson == null) { + return new Map() + } else { + return new Map(JSON.parse(sessionMapJson)) + } } /** diff --git a/src-electron/ui/menu.js b/src-electron/ui/menu.js index 4f45cb70d6..afac220a8f 100644 --- a/src-electron/ui/menu.js +++ b/src-electron/ui/menu.js @@ -216,11 +216,16 @@ const template = (httpPort) => [ */ async function getUserSessionInfoMessage(browserWindow) { let userKey = await browserApi.getUserKeyFromBrowserWindow(browserWindow) - let sessionUuid = + let sessionUuidMap = await browserApi.getSessionUuidFromBrowserWindow(browserWindow) + let sessionUuidText = '' + for (const [key, value] of sessionUuidMap) { + sessionUuidText += ` ${key} => ${value}\n` + } return ` - Browser session UUID: ${sessionUuid} Browser user key: ${userKey} + Browser session UUID: + ${sessionUuidText} ` } diff --git a/src/boot/axios.js b/src/boot/axios.js index 124bd513a6..9d5fed6ac8 100644 --- a/src/boot/axios.js +++ b/src/boot/axios.js @@ -16,30 +16,17 @@ */ import axios from 'axios' -import { v4 as uuidv4 } from 'uuid' import restApi from '../../src-shared/rest-api.js' import * as Util from '../util/util.js' -const querystring = require('querystring') +import * as SessionId from '../util/session-id.js' let zapUpdateExceptions = (payload, statusCode, message) => {} // You can set this to false to not log all the roundtrips const log = false -let search = window.location.search - -if (search[0] === '?') { - search = search.substring(1) -} -let query = querystring.parse(search) -if (window.sessionStorage.getItem('session_uuid') == null) { - window.sessionStorage.setItem('session_uuid', uuidv4()) -} -if (query[`stsApplicationId`]) { - let currentSessionUuid = window.sessionStorage.getItem('session_uuid') || '' - let updatedSessionUuid = query[`stsApplicationId`] + currentSessionUuid - window.sessionStorage.setItem('session_uuid', updatedSessionUuid) -} +// Handle session initialization +SessionId.initializeSessionIdInBrowser(window) /** * URL rewriter that can come handy in development mode. @@ -90,17 +77,16 @@ function processResponse(method, url, response) { * @returns config */ function fillConfig(config) { + let sessionId = SessionId.sessionId(window) if (config == null) { config = { params: {} } - config.params[restApi.param.sessionId] = - window.sessionStorage.getItem('session_uuid') + config.params[restApi.param.sessionId] = sessionId return config } else { if (!('params' in config)) { config.params = {} } - config.params[restApi.param.sessionId] = - window.sessionStorage.getItem('session_uuid') + config.params[restApi.param.sessionId] = sessionId return config } } diff --git a/src/boot/ws.js b/src/boot/ws.js index e8e8c12bf8..a35b295533 100644 --- a/src/boot/ws.js +++ b/src/boot/ws.js @@ -20,6 +20,7 @@ import restApi from '../../src-shared/rest-api.js' import rendApi from '../../src-shared/rend-api.js' import { Notify } from 'quasar' import * as Util from '../util/util.js' +import * as SessionId from '../util/session-id.js' const tickInterval = 15000 // 15 seconds tick interval for server watchdog. @@ -27,7 +28,7 @@ let eventEmitter = new Events.EventEmitter() let restPort = Util.getServerRestPort() let wsUrl = `ws://${window.location.hostname}:${ restPort == null ? window.location.port : restPort -}?${restApi.param.sessionId}=${window.sessionStorage.getItem('session_uuid')}` +}?${restApi.param.sessionId}=${SessionId.sessionId(window)}` const client = new WebSocket(wsUrl) /** diff --git a/src/util/session-id.js b/src/util/session-id.js new file mode 100644 index 0000000000..ae8e4fd722 --- /dev/null +++ b/src/util/session-id.js @@ -0,0 +1,121 @@ +/** + * + * Copyright (c) 2025 Silicon Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { v4 as uuidv4 } from 'uuid' +import { parse as querystringParse } from 'querystring' + +// Session handling constants +const SESSION_KEY = 'session_uuid' +const APP_ID_KEY = 'stsApplicationId' +const DEFAULT_APP_ID = 'defaultAppId' + +/** + * Loads a session map from the session storage. + * Session map is a string=>string key/value map, + * which contains "appId" as a key, and the generated UUID as a value. + * If appId is not present (as in standalone), then a default value + * is used. + * + * @param {*} window + * @returns + */ +function loadSessionMap(window) { + let json = window.sessionStorage.getItem(SESSION_KEY) + if (json == null) { + // Nothing there, let's just create an empty map. + return new Map() + } else { + // Found it, so we deserialize it. + const parsedArray = JSON.parse(json) + return new Map(parsedArray) + } +} + +/** + * Saves a session map into the session storage. + * @param {*} window + * @param {*} map + */ +function saveSessionMap(window, map) { + const mapArray = Array.from(map) + const json = JSON.stringify(mapArray) + window.sessionStorage.setItem(SESSION_KEY, json) +} + +/** + * Determine application id. If there is actually an stsApplicationId passed + * in, then what we get is that. Otherwise, it uses + * + * @param {*} window + * @returns + */ +function retrieveApplicationId(window) { + // Check if it has already been put on the dom. + if (window.zapAppId) { + return window.zapAppId + } + + // If it's not there, get it from the seach query.... + let search = window.location.search + if (search[0] === '?') { + search = search.substring(1) + } + let query = querystringParse(search) + + let appId + if (query[APP_ID_KEY]) { + appId = query[APP_ID_KEY] + } else { + appId = DEFAULT_APP_ID + } + + // Store it onto the window object for quicker access. + window.zapAppId = appId + + return appId +} + +/** + * Returns the session id after it's been created. This method WILL NOT + * create one if it hasn't been created yet. Only the + * initializeSessionIdInBrowser method will create a new session id. + * @param {*} window + * @returns + */ +export function sessionId(window) { + let appId = retrieveApplicationId(window) + let sessionMap = loadSessionMap(window) + return sessionMap.get(appId) +} + +/** + * This is the entry point for the boot file to handle the session object. + * It mainly needs to figure out if it needs to create a new UUID or not. + * @param {*} window + */ +export function initializeSessionIdInBrowser(window) { + let sessionMap = loadSessionMap(window) + let appId = retrieveApplicationId(window) + + // Get the session object, that is keyed by the appId + let sessionUuid = sessionMap.get(appId) + if (sessionUuid == null) { + // This is a whole new appId. Let's create a session object for it. + sessionMap.set(appId, uuidv4()) + saveSessionMap(window, sessionMap) + } +}