From cc90861bd56eecdf865a22a4f8dfbbf8d88db61f Mon Sep 17 00:00:00 2001 From: Sebastian Serth Date: Tue, 12 Sep 2023 17:39:45 +0200 Subject: [PATCH] Generate Session ID on server for synchronized editor This change allows us to use the session ID immediately for the connection_change and connection_status methods. Hence, we can identify different browser sessions of the same user. --- .../channels/synchronized_editor_channel.js | 14 +++++--------- app/channels/synchronized_editor_channel.rb | 7 ++++++- app/models/event/synchronized_editor.rb | 3 ++- ...ire_session_id_for_event_synchronized_editor.rb | 7 +++++++ db/schema.rb | 4 ++-- 5 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 db/migrate/20230912162208_require_session_id_for_event_synchronized_editor.rb diff --git a/app/assets/javascripts/channels/synchronized_editor_channel.js b/app/assets/javascripts/channels/synchronized_editor_channel.js index 70168549..1368dccb 100644 --- a/app/assets/javascripts/channels/synchronized_editor_channel.js +++ b/app/assets/javascripts/channels/synchronized_editor_channel.js @@ -1,12 +1,6 @@ $(document).on('turbolinks:load', function () { if (window.location.pathname.includes('/implement')) { - function generateUUID() { - // We decided to use this function instead of crypto.randomUUID() because it also supports older browser versions - // https://caniuse.com/?search=createObjectURL - return URL.createObjectURL(new Blob()).slice(-36) - } - function is_other_user(user) { return !_.isEqual(current_user, user); } @@ -17,7 +11,7 @@ $(document).on('turbolinks:load', function () { const editor = $('#editor'); const exercise_id = editor.data('exercise-id'); - const session_id = generateUUID(); + let session_id; if ($.isController('exercises') && is_other_user(current_contributor)) { @@ -38,14 +32,16 @@ $(document).on('turbolinks:load', function () { received(data) { // Called when there's incoming data on the websocket for this channel switch (data.action) { + case 'session_id': + session_id = data.session_id; + break; case 'editor_change': if (is_other_session(data.session_id)) { CodeOceanEditor.applyChanges(data.delta, data.active_file); } break; case 'connection_change': - // TODO: Check session id instead of user id to support multiple windows per user - if (is_other_user(data.user) && data.status === 'connected') { + if (is_other_session(data.session_id) && data.status === 'connected') { const message = {files: CodeOceanEditor.collectFiles(), session_id: session_id}; this.perform('current_content', message); } diff --git a/app/channels/synchronized_editor_channel.rb b/app/channels/synchronized_editor_channel.rb index cb3c12d7..923e5834 100644 --- a/app/channels/synchronized_editor_channel.rb +++ b/app/channels/synchronized_editor_channel.rb @@ -3,8 +3,12 @@ class SynchronizedEditorChannel < ApplicationCable::Channel def subscribed stream_from specific_channel - message = create_message('connection_change', 'connected') + # We generate a session_id for the user and send it to the client + @session_id = SecureRandom.uuid + connection.transmit identifier: @identifier, message: {action: :session_id, session_id: @session_id} + + message = create_message('connection_change', 'connected') Event::SynchronizedEditor.create_for_connection_change(message, current_user, programming_group) ActionCable.server.broadcast(specific_channel, message) end @@ -47,6 +51,7 @@ class SynchronizedEditorChannel < ApplicationCable::Channel action:, status:, user: current_user.to_page_context, + session_id: @session_id, } end end diff --git a/app/models/event/synchronized_editor.rb b/app/models/event/synchronized_editor.rb index 3df7654f..aba87161 100644 --- a/app/models/event/synchronized_editor.rb +++ b/app/models/event/synchronized_editor.rb @@ -25,9 +25,9 @@ class Event::SynchronizedEditor < ApplicationRecord remove: 1, }, _prefix: true + validates :session_id, presence: true validates :status, presence: true, if: -> { action_connection_change? } validates :file_id, presence: true, if: -> { action_editor_change? } - validates :session_id, presence: true, if: -> { action_editor_change? } validates :editor_action, presence: true, if: -> { action_editor_change? } validates :range_start_row, numericality: {only_integer: true, greater_than_or_equal_to: 0}, if: -> { action_editor_change? } validates :range_start_column, numericality: {only_integer: true, greater_than_or_equal_to: 0}, if: -> { action_editor_change? } @@ -64,6 +64,7 @@ class Event::SynchronizedEditor < ApplicationRecord user:, programming_group:, study_group_id: user.current_study_group_id, + session_id: message[:session_id], action: message[:action], status: message[:status] ) diff --git a/db/migrate/20230912162208_require_session_id_for_event_synchronized_editor.rb b/db/migrate/20230912162208_require_session_id_for_event_synchronized_editor.rb new file mode 100644 index 00000000..89081fd0 --- /dev/null +++ b/db/migrate/20230912162208_require_session_id_for_event_synchronized_editor.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class RequireSessionIdForEventSynchronizedEditor < ActiveRecord::Migration[7.0] + def change + change_column_null :events_synchronized_editor, :session_id, false + end +end diff --git a/db/schema.rb b/db/schema.rb index ef64e9a9..f676b90e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_09_12_142620) do +ActiveRecord::Schema[7.0].define(version: 2023_09_12_162208) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" enable_extension "pgcrypto" @@ -166,7 +166,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_12_142620) do t.jsonb "data" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.uuid "session_id" + t.uuid "session_id", null: false t.index ["file_id"], name: "index_events_synchronized_editor_on_file_id" t.index ["programming_group_id"], name: "index_events_synchronized_editor_on_programming_group_id" t.index ["study_group_id"], name: "index_events_synchronized_editor_on_study_group_id"