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.
This commit is contained in:

committed by
Sebastian Serth

parent
914adeed42
commit
cc90861bd5
@ -1,12 +1,6 @@
|
|||||||
$(document).on('turbolinks:load', function () {
|
$(document).on('turbolinks:load', function () {
|
||||||
|
|
||||||
if (window.location.pathname.includes('/implement')) {
|
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) {
|
function is_other_user(user) {
|
||||||
return !_.isEqual(current_user, user);
|
return !_.isEqual(current_user, user);
|
||||||
}
|
}
|
||||||
@ -17,7 +11,7 @@ $(document).on('turbolinks:load', function () {
|
|||||||
|
|
||||||
const editor = $('#editor');
|
const editor = $('#editor');
|
||||||
const exercise_id = editor.data('exercise-id');
|
const exercise_id = editor.data('exercise-id');
|
||||||
const session_id = generateUUID();
|
let session_id;
|
||||||
|
|
||||||
if ($.isController('exercises') && is_other_user(current_contributor)) {
|
if ($.isController('exercises') && is_other_user(current_contributor)) {
|
||||||
|
|
||||||
@ -38,14 +32,16 @@ $(document).on('turbolinks:load', function () {
|
|||||||
received(data) {
|
received(data) {
|
||||||
// Called when there's incoming data on the websocket for this channel
|
// Called when there's incoming data on the websocket for this channel
|
||||||
switch (data.action) {
|
switch (data.action) {
|
||||||
|
case 'session_id':
|
||||||
|
session_id = data.session_id;
|
||||||
|
break;
|
||||||
case 'editor_change':
|
case 'editor_change':
|
||||||
if (is_other_session(data.session_id)) {
|
if (is_other_session(data.session_id)) {
|
||||||
CodeOceanEditor.applyChanges(data.delta, data.active_file);
|
CodeOceanEditor.applyChanges(data.delta, data.active_file);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'connection_change':
|
case 'connection_change':
|
||||||
// TODO: Check session id instead of user id to support multiple windows per user
|
if (is_other_session(data.session_id) && data.status === 'connected') {
|
||||||
if (is_other_user(data.user) && data.status === 'connected') {
|
|
||||||
const message = {files: CodeOceanEditor.collectFiles(), session_id: session_id};
|
const message = {files: CodeOceanEditor.collectFiles(), session_id: session_id};
|
||||||
this.perform('current_content', message);
|
this.perform('current_content', message);
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,12 @@
|
|||||||
class SynchronizedEditorChannel < ApplicationCable::Channel
|
class SynchronizedEditorChannel < ApplicationCable::Channel
|
||||||
def subscribed
|
def subscribed
|
||||||
stream_from specific_channel
|
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)
|
Event::SynchronizedEditor.create_for_connection_change(message, current_user, programming_group)
|
||||||
ActionCable.server.broadcast(specific_channel, message)
|
ActionCable.server.broadcast(specific_channel, message)
|
||||||
end
|
end
|
||||||
@ -47,6 +51,7 @@ class SynchronizedEditorChannel < ApplicationCable::Channel
|
|||||||
action:,
|
action:,
|
||||||
status:,
|
status:,
|
||||||
user: current_user.to_page_context,
|
user: current_user.to_page_context,
|
||||||
|
session_id: @session_id,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -25,9 +25,9 @@ class Event::SynchronizedEditor < ApplicationRecord
|
|||||||
remove: 1,
|
remove: 1,
|
||||||
}, _prefix: true
|
}, _prefix: true
|
||||||
|
|
||||||
|
validates :session_id, presence: true
|
||||||
validates :status, presence: true, if: -> { action_connection_change? }
|
validates :status, presence: true, if: -> { action_connection_change? }
|
||||||
validates :file_id, presence: true, if: -> { action_editor_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 :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_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? }
|
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:,
|
user:,
|
||||||
programming_group:,
|
programming_group:,
|
||||||
study_group_id: user.current_study_group_id,
|
study_group_id: user.current_study_group_id,
|
||||||
|
session_id: message[:session_id],
|
||||||
action: message[:action],
|
action: message[:action],
|
||||||
status: message[:status]
|
status: message[:status]
|
||||||
)
|
)
|
||||||
|
@ -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
|
@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# 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
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pg_trgm"
|
enable_extension "pg_trgm"
|
||||||
enable_extension "pgcrypto"
|
enable_extension "pgcrypto"
|
||||||
@ -166,7 +166,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_12_142620) do
|
|||||||
t.jsonb "data"
|
t.jsonb "data"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_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 ["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 ["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"
|
t.index ["study_group_id"], name: "index_events_synchronized_editor_on_study_group_id"
|
||||||
|
Reference in New Issue
Block a user