Limit connection to only one device

Question:
Can I limit user connection to only one device at a time?

Product:
Wix Editor

What are you trying to achieve:
Hi, I am creating a subscription based website for a very niche audience (that can easily share accounts if not protected) and I would like to know if there is a way to limit the connection for the users to only one device at a time. This is very important to me and I thank you all in advance :slight_smile:

What have you already tried:
I have looked everywhere but couldn’t find anything on the subject.

I doubt there is anything built in for it. I think maybe one could do it with velo, I would probably try wix-realtime.

@simen is correct, it’s not currently available, but there is a feature request for it, that you should definitely vote for :slight_smile:

Really no way to narrow down a user by using only one device?

I think there are some possibilities :thinking: :wink:

We can start here…

  1. browserLocale - Velo API Reference - Wix.com
  • "Desktop": When viewed in a desktop browser.
  • "Mobile": When viewed in a mobile browser.
  • "Tablet": When viewed in a tablet browser.

The first little step for our identification…
2024-02-29 19_24_20-browserLocale - Velo API Reference - Wix.com

Yes, you are right, this is b…lls…t, we still do not have enough data of a specific user to be able to identify him…All we know right now is, what kind of device he is using.

But let’s dive deeper…

  1. We also could get …
    getBoundingRect - Velo API Reference - Wix.com

Ok, now we got the window size of the user, still not an interesting info about our user.
Sure ???

  1. Let’s go further…
    getCurrentGeolocation - Velo API Reference - Wix.com

This one may not work in all cases, but could still be helpfull…

  1. Now we go even more into detail…
function detectDevice() {
    // User Agent String
    var userAgent = navigator.userAgent;

    // Screen Resolution
    var screenWidth = window.screen.width;
    var screenHeight = window.screen.height;

    // Browser Features
    var cookiesEnabled = navigator.cookieEnabled;
    var isMobile = /Mobile|Android/.test(userAgent);
    var isTablet = /iPad|Android/.test(userAgent) && !isMobile;
    var isDesktop = !isMobile && !isTablet;

    // Constructing Device Object
    var device = {
        userAgent: userAgent,
        screenWidth: screenWidth,
        screenHeight: screenHeight,
        cookiesEnabled: cookiesEnabled,
        isMobile: isMobile,
        isTablet: isTablet,
        isDesktop: isDesktop
    };

    return device;
}

// Example usage:
var userDevice = detectDevice();
console.log(userDevice);

Using → userAgent <— could help us to clarify our generated FOOTPRINT of our current logge-in user even more.

So, all you have to do, is to generate a function, which can detect everyones generated footprint.

Maybe i know even more techniques to generate a FOOTPRINT, which would identify a user :joy:

Further more → IP-ADRESS

Or maybe even try out Fingerprint-JS to include somehow (never tried) just an idea.

This may still not work 100%, but 50-80% still better then nothing.

Maybe i should dive deeper into this and generate a FOOTPRINT-DETECTOR :rofl: :rofl: :rofl: :rofl:

Played around for 25min…and this was my result…

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Agent Information</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fingerprintjs2/2.1.0/fingerprint2.min.js"></script>
</head>
<body>
<script>
function hasUserBeenSeen() {
    return localStorage.getItem('userFingerprint') !== null;
}

function saveUserFingerprint(fingerprint) {
    localStorage.setItem('userFingerprint', fingerprint);
}

function generateFingerprint() {
    return new Promise((resolve, reject) => {
        const fpPromise = FingerprintJS.load();
        fpPromise
            .then(fp => fp.get())
            .then(result => {
                resolve(result.visitorId);
            })
            .catch(error => {
                reject(error);
            });
    });
}

if (hasUserBeenSeen()) {
    console.log('User has been seen before.');
} else {
    console.log('User is new.');
    generateFingerprint()
        .then(fingerprint => {
            saveUserFingerprint(fingerprint);
        });
}

</script>
</body>
</html>

Maybe someone can test it…

Hi, after a lot of trial and thanks to chatGPT’s expertise on the matter :wink: I found a working code, but it has a major drawback as it takes its toll on server performance and it uses databases so the more users you have the more you will need to pay for the website to actually keep working. Also, it only works with the default login.

Frontend :

import wixUsers from 'wix-users';
import {registerSession, invalidateOtherSessions, isSessionValid} from 'backend/sessionManager';
import wixLocation from 'wix-location';


let checkSessionInterval;

wixUsers.onLogin((user) => {
    registerSession(user.id)
    .then((sessionId) => {
        // The session is now registered. Next, invalidate any other sessions.
        invalidateOtherSessions(user.id, sessionId);

        // Set interval to check if session is still valid every minute
checkSessionInterval = setInterval(() => {
    isSessionValid(user.id, sessionId)
    .then(valid => {
        console.log(`Session validity checked at ${new Date().toString()}. Status: ${valid ? 'Valid' : 'Invalid'}`);
        if(!valid) {
                    clearInterval(checkSessionInterval);
                    wixLocation.to("/sessioninterrupted");
        }
    })
    .catch((error) => {
        console.log("Error checking session: ", error);
    });
}, 300000); // Check every 30sec
    })
    .catch((error) => {
        console.log("Error managing session: ", error);
    });
});

Backend :

import wixData from 'wix-data';
import {v4 as uuidv4} from 'uuid'; // Importing UUID generator

// Registers a new session
export function registerSession(userId) {
    const sessionId = uuidv4(); // generates a unique session ID
    return wixData.insert('UserSessions', {
        userId,
        sessionId,
        loginTimestamp: new Date()
    }).then(() => sessionId); // returns the sessionId for later checks
}


// Invalidates other sessions this user has, with added logging for debugging
export function invalidateOtherSessions(userId, currentSessionId) {
    // Log when the function is called, along with the userId and currentSessionId
    console.log("invalidateOtherSessions called for userId:", userId, "with currentSessionId:", currentSessionId);
    
    return wixData.query('UserSessions')
        .eq('userId', userId)
        .ne('sessionId', currentSessionId)
        .find()
        .then(results => {
            // Log how many sessions were found to invalidate
            console.log(`Found ${results.items.length} sessions to invalidate for user ${userId}`);
            
            const itemsToRemove = results.items.map(item => item._id);
            
            // If there are sessions to remove, proceed with removal
            if (itemsToRemove.length > 0) {
                return wixData.bulkRemove('UserSessions', itemsToRemove)
                    .then((results) => {
                        // Log successful removal
                        console.log(`Successfully removed ${results.length} sessions for user ${userId}`);
                        return results; // Return the removal results
                    })
                    .catch((error) => {
                        // Log if there was an error during removal
                        console.error("Error during session invalidation: ", error);
                        throw error; // Rethrow the error to handle it outside if needed
                    });
            } else {
                // Log if no sessions to remove were found
                console.log("No sessions to remove were found.")
                return null; // Return null if there's nothing to remove
            }
        })
        .catch((error) => {
            // Log if there was an error during the query operation
            console.error("Error querying sessions for invalidation: ", error);
            throw error; // Rethrow the error to handle it outside if needed
        });
}

export function isSessionValid(userId, sessionId) {
    return wixData.query('UserSessions')
        .eq('userId', userId)
        .eq('sessionId', sessionId)
        .find()
        .then(results => results.items.length > 0 ? true : false) // Return true if session is still valid, false otherwise
        .catch((error) => {
            // Log if there was an error during the query operation
            console.error("Error querying session validation: ", error);
            throw error; // Rethrow the error to handle it outside if needed
        });
}

session interrupted page :

import wixUsers from 'wix-users';


$w.onReady(function () {
	wixUsers.logout()
});

You will need to create a sessioninterrupted page to redirect disconnected users, and a database with the correct parameters. Logs are also included in the code so you can check for errors directly in the logs.

If you have a better way to do it please feel free to share I would be greatful.

1 Like

Maybe you also want to explain what exactly your setup is capable to do, for those who will not understand everything inside your code.

Features: ???

Functions: ???

When and how to use: ???

Used NPMs: ???

…and so on…

Sure, my code uses polling to periodically check for members who have multiple sessions open, and disconnect them redirecting them to a sessionInterrupted page. On login, it enters a new database entry with userId, uniques sessionId and a timestamp, and looks for and deletes any previous entries with the same userId. Then periodically (time can be changed in the code), it checks on the frontend if the sessionId is still available in the database. If it is not, user on the expired session is disconnected and redirected, while the new session on the other device stays connected.

1 Like

Well, you should do it one more time with → wix-members ← instead, because → wix-usersis a depricated API.

Have you actually tested this using multiple devices across a few minutes of time?

I feel like the logic is flawed somehow.

First of all the front end code must be entered on the masterpage area. Second, you are only triggering it upon login. Which means, if a person logs in, leaves the page (Wix does not log them out), re-opens the page and is STILL logged in ---- that code will not run.

I feel like you need to be looking elsewhere for a code solution, and most likely the realtime API is where you should explore a little more.

https://www.wix.com/velo/reference/wix-realtime-backend

1 Like

First of all the front end code must be entered on the masterpage area. → agree

Do not mark it to fast as → SOLUTION ← (at current time this is a SEMI-SOLUTION)

@codequeen I tested it as thoroughly as I could for now (before opening the website to the public) and it works well, even in the case you described because as soon as they reopen the page the periodic checks restart and it is just a matter of seconds/minutes until they are disconnected.

About the realtime api, it was my first choice but I am a novice and couldn’t manage to do it :confused: but since no one in any thread has provided an easy working solution for now I posted this.

@CODE-NINJA I tried to modify it to wix-members but i couldn’t find enough documentation to make the changes + again I am a novice and it took me a long time to even come up with that code.

2 Likes

I marked it as solution because it worked, but you’re the expert :slight_smile: so i unticked the box.

1 Like

I will check it when i find some freetime, it could be interessting for me aswell.
Anyway → Thanks for sharing. Until here good job. :+1:

1 Like

One more thing, could you please add → (login) to your POST-TITLE

Limit connection to only one device (login)

Sorry I can’t figure out how to do that :thinking: can’t find an edit button.

Click onto the TITLE itself…

Idk if there is a time limit or something but it is locked. I tried everything it won’t let me edit it :confused:

Edit : I created a new topic for another question and on the new one I have the option to edit the text and title, it is just not available on this post :confused: