Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow bidirectional communication within the invite system #12

Open
Staninna opened this issue Dec 7, 2023 · 5 comments
Open

Allow bidirectional communication within the invite system #12

Staninna opened this issue Dec 7, 2023 · 5 comments
Assignees

Comments

@Staninna
Copy link
Contributor

Staninna commented Dec 7, 2023

The way invites work now doesn't let data move between the client and the server (slide). This makes it hard for interactive stuff like games or quizzes to work properly. Even though it's in early development, it's important to let the client and server talk using for example JSON.

@luttje
Copy link
Member

luttje commented Dec 8, 2023

You're absolutely right! So I dove into the prototype code for you to see what would have to be done, and I found something! In this post I'll describe a way to communicate and a problem I noticed.

⛓ Bidirectional communication

Looking at the system again, I feel it's currently technically possible, however the whole way of setting interaction data not very intuitive.

It should be possible for the interaction screen (on a user's phone) to postMessage just like the slide would. This is because the interaction screen is the same webpage as the slide on the monitor, except it has #interaction-{local_user_id} as the anchor.
Sending any message from the slide, or any client will cause a message to be sent to the API and sent back to any listening slide/client:

// untested psuedo-code
window.parent.postMessage({
    type: 'requestSetInteractionData',
    data: {
        user_quiz_actions: {
            "tl10": [ "a", "c", "b" ],
            "staninna": [ "c", "b", "a" ],
        },
    },
}, '*');

The slide (or any other user interaction screen) could listen for it, similar to how the slide listens in the betting example, but then in the interaction screen:

// untested psuedo-code
function interactionScreen(localId) {
    window.addEventListener('message', function(event) {
        if (event.data.type === 'onInteractionData') {
            const interactionData = event.data.data;
            const localAnswers = interactionData.user_quiz_actions[localId];
    
            document.querySelector('#yourAnswers').innerText = localAnswers.join(', ');
        }
    });
}

Both the slide and all connected interaction screens (users) would get that data update.

If you have trouble implementing this let me know and I'll help out!

Here's a sketch showing how the same slide displays different content based on whether it's on the screen or on a user's phone. (I doubt it makes the situation much clearer, but I drew it already so just dumping it here)
image

🗝 Security issue

Although the above should work, it's not at all safe. Seeing how a user could modify (and thus cheat) the data in any way. Since the server blindly accepts any requestSetInteractionData and sets it in the database as the data. A user could set the contents of user_quiz_actions to anything. They could change answers after they've been submitted, submit answers for the other user, etc.

If things work as I describe, then I'd like to turn this issue into a bug/security fix, there must be some way to set data from the slide authoritatively (so the slide can later know for sure it was the one to set data).

A low-effort fix that I'm thinking could resolve this is as follows (contains psuedo-code):

  1. The slide is hosted on the screen which sends along a X-Secret-Tick-Key with every request. The API could use this to authorize the slide and know that it was legit. If a slide then sets interaction data like requestSetInteractionData({ data.any_key: 10 }) the API would allow it, since the slide can write anywhere.
  2. When my user interaction screen sends data, it's limited to a sub-key with my ID, e.g: requestSetInteractionData({ data.users.tl10.answers: ['a', 'b'] }). This would work, but only the screen shown to me.
  3. If my user interaction screen writes to anywhere else (like the same key as step 1 data.any_key), the API would reject it, since the screen can only write to data.users.tl10.

Let me know if you manage to implement bidirectional communication. Especially let me know if you have suggestions on how to improve the system, so we get the right tools to make cool stuff.

@Staninna
Copy link
Contributor Author

Staninna commented Dec 8, 2023

Thanks for the tips. I'll give this a shot soon and let you know how it goes. Appreciate the help!

@Staninna
Copy link
Contributor Author

I played a bit around, but I can't get it to work, here is my current draft: gist

@luttje
Copy link
Member

luttje commented Dec 12, 2023

Your code looks like it should work based on what I explained, but now looking at it again I've made a mistake:
https://github.com/curio-team/narrowblast/blob/1bde5753138fca76543b02ad70cfaf3c747497ea/resources/views/app/slides/iframe-scripts.blade.php#L373C21-L373C52

This code finds the iframe to send the interaction data to by checking the data.publicPath, which only points to the slide URL (which doesn't end in #interaction-etc). If you were to trim the hash off (data.publicPath.split('#')[0]) in the location I linked, I feel that your code might work.

@luttje
Copy link
Member

luttje commented Dec 13, 2023

I woke up thinking it might possibly be more complicated than I made it out to be. I'll try find some time this weekend to actually run, test and change the code. Instead of trying to read it and deduce it's workings. I'll let you know if I find something

@luttje luttje self-assigned this Dec 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants