diff --git a/client/aux.js b/client/aux.js index 1ff534b..73417ea 100644 --- a/client/aux.js +++ b/client/aux.js @@ -1,3 +1,61 @@ +function ui_offline() { + document.body.classList.add('offline'); + document.querySelector('.offline-toast').classList.remove('hidden'); +} + +function ui_online() { + document.body.classList.remove('offline'); + document.querySelector('.offline-toast').classList.add('hidden'); +} + +function event_size(event) { + let size = 1 + 3; // type + padding + + switch (event.type) { + case EVENT.PREDRAW: { + size += 4 * 2; + break; + } + + case EVENT.SET_COLOR: { + size += 4; + break; + } + + case EVENT.SET_WIDTH: { + size += 2; + break; + } + + case EVENT.STROKE: { + size += 4 + 2 + 2 + 4 + event.points.length * 4 * 2; // u32 stroke id + u16 (count) + u16 (width) + u32 (color + count * (f32, f32) points + break; + } + + case EVENT.UNDO: + case EVENT.REDO: { + break; + } + + case EVENT.IMAGE: + case EVENT.IMAGE_MOVE: { + size += 4 + 4 + 4; // file id + x + y + break; + } + + case EVENT.ERASER: { + size += 4; // stroke id + break; + } + + default: { + console.error('fuck'); + } + } + + return size; +} + function find_touch(touchlist, id) { for (const touch of touchlist) { if (touch.identifier === id) { diff --git a/client/client_recv.js b/client/client_recv.js index 91c4f02..7e6bcac 100644 --- a/client/client_recv.js +++ b/client/client_recv.js @@ -298,6 +298,7 @@ async function handle_message(state, context, d) { case MESSAGE.INIT: { state.me = des_u32(d); state.server_lsn = des_u32(d); + state.online = true; init_player_defaults(state, state.me); @@ -320,7 +321,7 @@ async function handle_message(state, context, d) { state.events.length = 0; - for (let i = 0; i < user_count; ++i) { + for (let i = 0; i < user_count; ++i) { const user_id = des_u32(d); const user_color = des_u32(d); const user_width = des_u16(d); @@ -340,9 +341,7 @@ async function handle_message(state, context, d) { do_draw = true; recompute_static_data(context); - send_ack(event_count); - sync_queue(state); break; diff --git a/client/client_send.js b/client/client_send.js index 1083da4..24e146e 100644 --- a/client/client_send.js +++ b/client/client_send.js @@ -192,6 +192,8 @@ function push_event(state, event) { // Queue an event and initialize repated sends until ACKed function queue_event(state, event, skip = false) { + if (!state.online) { return; } + push_event(state, event); if (skip) { @@ -206,7 +208,9 @@ function queue_event(state, event, skip = false) { } // Fire and forget. Doesn't do anything if we are offline -async function fire_event(event) { +async function fire_event(state, event) { + if (!state.online) { return; } + const s = serializer_create(1 + event_size(event)); ser_u8(s, MESSAGE.FIRE); diff --git a/client/default.css b/client/default.css index ecc94fd..741ecab 100644 --- a/client/default.css +++ b/client/default.css @@ -15,6 +15,14 @@ html, body { overflow: hidden; } +body .main { + height: 100%; +} + +body.offline .main { + filter: brightness(50%); +} + .dhide { display: none !important; } @@ -255,4 +263,33 @@ input[type=range]::-moz-range-track { left: 50%; top: 96px; transform: translate(-50%, -50%); -} \ No newline at end of file +} + +.offline-toast { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 999; + background: rgba(0, 0, 0, 0.8); + display: flex; + align-items: center; + padding: 10px; + border-radius: var(--radius); + box-shadow: 0px 2px 3px 0px rgba(155, 150, 100, 0.2); + transition: transform .1s ease-in-out, opacity .1s; + font-size: 14px; + color: white; + font-weight: bold; + user-select: none; + transition: transform .1s ease-in-out, opacity .1s; +} + +.offline-toast.hidden { + transform: translate(-50%, -5px); + opacity: 0; +} + +body.offline * { + pointer-events: none; +} diff --git a/client/index.html b/client/index.html index 7cb14d4..6bd240a 100644 --- a/client/index.html +++ b/client/index.html @@ -3,59 +3,66 @@ Desk - + - - - - - - - - - - - - - + + + + + + + + + + + + + + - +
+ -
-
- +
+
+ +
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- -
+
+
+
+
+
+
+ +
-
- +
+ +
+ + \ No newline at end of file diff --git a/client/index.js b/client/index.js new file mode 100644 index 0000000..ca57ada --- /dev/null +++ b/client/index.js @@ -0,0 +1,143 @@ +// NEXT: fire events for brush changes + +document.addEventListener('DOMContentLoaded', main); + +const config = { + ws_url: 'ws://192.168.100.2/ws/', + ping_url: 'http://192.168.100.2/api/ping', + image_url: 'http://192.168.100.2/images/', + sync_timeout: 1000, + ws_reconnect_timeout: 2000, + brush_preview_timeout: 1000, + second_finger_timeout: 500, + buffer_first_touchmoves: 5, + debug_print: true, + min_zoom: 0.01, + max_zoom: 100.0, + initial_offline_timeout: 1000, + default_color: 0x00, + default_width: 8, +}; + +const EVENT = Object.freeze({ + PREDRAW: 10, + SET_COLOR: 11, + SET_WIDTH: 12, + + STROKE: 20, + RULER: 21, /* gets re-written with EVENT.STROKE before sending to server */ + + UNDO: 30, + REDO: 31, + + IMAGE: 40, + IMAGE_MOVE: 41, + + ERASER: 50, +}); + +const MESSAGE = Object.freeze({ + INIT: 100, + SYN: 101, + ACK: 102, + FULL: 103, + FIRE: 104, + JOIN: 105, +}); + +function main() { + const state = { + 'online': false, + 'me': null, + + 'canvas': { + 'offset': { 'x': 0, 'y': 0 }, + 'zoom': 1.0, + }, + + 'cursor': { + 'x': 0, + 'y': 0, + }, + + 'sn': 0, + 'lsn': 0, + 'server_lsn': 0, + + 'touch': { + 'moves': 0, + 'drawing': false, + 'moving': false, + 'waiting_for_second_finger': false, + 'first_finger_position': null, + 'second_finger_position': null, + 'buffered': [], + 'ids': [], + }, + + 'moving': false, + 'drawing': false, + 'spacedown': false, + + 'current_strokes': {}, + + 'queue': [], + 'events': [], + + 'tools': { + 'active': null, + 'active_element': null, + }, + + 'colors': { + 'active_element': null, + }, + + 'timers': { + 'hide_preview': null, + 'offline_toast': null, + 'raf': false, + }, + + 'players': {}, + }; + + const context = { + 'canvas': null, + 'gl': null, + 'programs': {}, + 'buffers': {}, + 'locations': {}, + 'textures': {}, + + 'dynamic_positions': {}, + 'dynamic_colors': {}, + + 'quad_positions': [], + 'quad_texcoords': [], + 'static_positions': [], + 'static_colors': [], + 'static_positions_f32': new Float32Array(0), + 'dynamic_positions_f32': new Float32Array(0), + 'static_colors_u8': new Uint8Array(0), + 'dynamic_colors_u8': new Uint8Array(0), + 'quad_positions_f32': new Float32Array(0), + 'quad_texcoords_f32': new Float32Array(0), + 'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0}, + }; + + const url = new URL(window.location.href); + const parts = url.pathname.split('/'); + + state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0; + + init_webgl(state, context); + init_listeners(state, context); + init_tools(state); + + ws_connect(state, context, true); + + schedule_draw(state, context); + + state.timers.offline_toast = setTimeout(() => ui_offline(), config.initial_offline_timeout); +} \ No newline at end of file diff --git a/client/tools.js b/client/tools.js index ac23a4d..cd13e92 100644 --- a/client/tools.js +++ b/client/tools.js @@ -20,7 +20,7 @@ function switch_color(state, item) { if (state.me in state.players) { const color_u32 = color_to_u32(color); state.players[state.me].color = color_u32 - fire_event(color_event(color_u32)); + fire_event(state, color_event(color_u32)); } state.colors.active_element = item; @@ -42,7 +42,10 @@ function hide_stroke_preview() { } function switch_stroke_width(e, state) { + if (!state.online) return; + const value = e.target.value; + state.players[state.me].width = value; show_stroke_preview(state, value); @@ -55,7 +58,7 @@ function switch_stroke_width(e, state) { function broadcast_stroke_width(e, state) { const value = e.target.value; - fire_event(width_event(value)); + fire_event(state, width_event(value)); } function init_tools(state) { diff --git a/client/webgl.js b/client/webgl.js deleted file mode 100644 index 0485ce3..0000000 --- a/client/webgl.js +++ /dev/null @@ -1,269 +0,0 @@ -// NEXT: fire events for brush changes - -document.addEventListener('DOMContentLoaded', main); - -function draw(state, context) { - state.timers.raf = false; - - const gl = context.gl; - const width = window.innerWidth; - const height = window.innerHeight; - - let locations; - let buffers; - - gl.viewport(0, 0, context.canvas.width, context.canvas.height); - gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1); - gl.clear(gl.COLOR_BUFFER_BIT); - - // Draw images - locations = context.locations['quad']; - buffers = context.buffers['quad']; - - gl.useProgram(context.programs['quad']); - - gl.enableVertexAttribArray(locations['a_pos']); - gl.enableVertexAttribArray(locations['a_texcoord']); - - gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); - gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); - gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); - gl.uniform1i(locations['u_layer'], 0); - - gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']); - gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0); - gl.bufferData(gl.ARRAY_BUFFER, context.quad_positions_f32, gl.STATIC_DRAW); - - gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']); - gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0); - gl.bufferData(gl.ARRAY_BUFFER, context.quad_texcoords_f32, gl.STATIC_DRAW); - - let tex_index = 0; - - for (const key in context.textures) { - gl.bindTexture(gl.TEXTURE_2D, context.textures[key]); - gl.drawArrays(gl.TRIANGLES, tex_index * 6, 6); - ++tex_index; - } - - // Draw strokes - locations = context.locations['stroke']; - buffers = context.buffers['stroke']; - - gl.useProgram(context.programs['stroke']); - - gl.enableVertexAttribArray(locations['a_pos']); - gl.enableVertexAttribArray(locations['a_color']); - - gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); - gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); - gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); - gl.uniform1i(locations['u_layer'], 1); - - const total_pos_size = context.static_positions_f32.byteLength + context.dynamic_positions_f32.byteLength; - const total_color_size = context.static_colors_u8.byteLength + context.dynamic_colors_u8.byteLength; - const total_point_count = (context.static_positions.length + total_dynamic_positions(context)) / 2; - - gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']); - gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0); - gl.bufferData(gl.ARRAY_BUFFER, total_pos_size, gl.DYNAMIC_DRAW); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_positions_f32); - gl.bufferSubData(gl.ARRAY_BUFFER, context.static_positions_f32.byteLength, context.dynamic_positions_f32); - - gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_color']); - gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, 0, 0); - gl.bufferData(gl.ARRAY_BUFFER, total_color_size, gl.DYNAMIC_DRAW); - gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_colors_u8); - gl.bufferSubData(gl.ARRAY_BUFFER, context.static_colors_u8.byteLength, context.dynamic_colors_u8); - - gl.drawArrays(gl.TRIANGLES, 0, total_point_count); -} - -const config = { - ws_url: 'ws://192.168.100.2/ws/', - image_url: 'http://192.168.100.2/images/', - sync_timeout: 1000, - ws_reconnect_timeout: 10000, - brush_preview_timeout: 1000, - second_finger_timeout: 500, - buffer_first_touchmoves: 5, - debug_print: true, - min_zoom: 0.01, - max_zoom: 100.0, - - default_color: 0x00, - default_width: 8, -}; - -const EVENT = Object.freeze({ - PREDRAW: 10, - SET_COLOR: 11, - SET_WIDTH: 12, - - STROKE: 20, - RULER: 21, /* gets re-written with EVENT.STROKE before sending to server */ - UNDO: 30, - REDO: 31, - IMAGE: 40, - IMAGE_MOVE: 41, - ERASER: 50, -}); - -const MESSAGE = Object.freeze({ - INIT: 100, - SYN: 101, - ACK: 102, - FULL: 103, - FIRE: 104, - JOIN: 105, -}); - -function event_size(event) { - let size = 1 + 3; // type + padding - - switch (event.type) { - case EVENT.PREDRAW: { - size += 4 * 2; - break; - } - - case EVENT.SET_COLOR: { - size += 4; - break; - } - - case EVENT.SET_WIDTH: { - size += 2; - break; - } - - case EVENT.STROKE: { - size += 4 + 2 + 2 + 4 + event.points.length * 4 * 2; // u32 stroke id + u16 (count) + u16 (width) + u32 (color + count * (f32, f32) points - break; - } - - case EVENT.UNDO: - case EVENT.REDO: { - break; - } - - case EVENT.IMAGE: - case EVENT.IMAGE_MOVE: { - size += 4 + 4 + 4; // file id + x + y - break; - } - - case EVENT.ERASER: { - size += 4; // stroke id - break; - } - - default: { - console.error('fuck'); - } - } - - return size; -} - -function main() { - const state = { - 'me': 333, - - 'canvas': { - 'offset': { 'x': 0, 'y': 0 }, - 'zoom': 1.0, - }, - - 'cursor': { - 'x': 0, - 'y': 0, - }, - - 'sn': 0, - 'lsn': 0, - 'server_lsn': 0, - - 'touch': { - 'moves': 0, - 'drawing': false, - 'moving': false, - 'waiting_for_second_finger': false, - 'first_finger_position': null, - 'second_finger_position': null, - 'buffered': [], - 'ids': [], - }, - - 'moving': false, - 'drawing': false, - 'spacedown': false, - - 'current_strokes': {}, - - 'fire_queue': [], - 'queue': [], - 'events': [], - - 'tools': { - 'active': null, - 'active_element': null, - }, - - 'colors': { - 'active_element': null, - }, - - 'timers': { - 'ws_reconnect': null, - 'hide_preview': null, - 'raf': false, - }, - - 'players': {}, - }; - - const context = { - 'canvas': null, - 'gl': null, - 'programs': {}, - 'buffers': {}, - 'locations': {}, - 'textures': {}, - - 'dynamic_positions': {}, - 'dynamic_colors': {}, - - 'quad_positions': [], - 'quad_texcoords': [], - 'static_positions': [], - 'static_colors': [], - 'static_positions_f32': new Float32Array(0), - 'dynamic_positions_f32': new Float32Array(0), - 'static_colors_u8': new Uint8Array(0), - 'dynamic_colors_u8': new Uint8Array(0), - 'quad_positions_f32': new Float32Array(0), - 'quad_texcoords_f32': new Float32Array(0), - 'bgcolor': {'r': 1.0, 'g': 1.0, 'b': 1.0}, - }; - - const url = new URL(window.location.href); - const parts = url.pathname.split('/'); - - state.desk_id = parts.length > 0 ? parts[parts.length - 1] : 0; - - init_webgl(state, context); - init_listeners(state, context); - init_tools(state); - - ws_connect(state, context, true); - - schedule_draw(state, context); -} - -function schedule_draw(state, context) { - if (!state.timers.raf) { - window.requestAnimationFrame(() => draw(state, context)); - state.timers.raf = true; - } -} \ No newline at end of file diff --git a/client/webgl_draw.js b/client/webgl_draw.js new file mode 100644 index 0000000..6314adc --- /dev/null +++ b/client/webgl_draw.js @@ -0,0 +1,83 @@ +function schedule_draw(state, context) { + if (!state.timers.raf) { + window.requestAnimationFrame(() => draw(state, context)); + state.timers.raf = true; + } +} + +function draw(state, context) { + state.timers.raf = false; + + const gl = context.gl; + const width = window.innerWidth; + const height = window.innerHeight; + + let locations; + let buffers; + + gl.viewport(0, 0, context.canvas.width, context.canvas.height); + gl.clearColor(context.bgcolor.r, context.bgcolor.g, context.bgcolor.b, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + + // Draw images + locations = context.locations['quad']; + buffers = context.buffers['quad']; + + gl.useProgram(context.programs['quad']); + + gl.enableVertexAttribArray(locations['a_pos']); + gl.enableVertexAttribArray(locations['a_texcoord']); + + gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); + gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); + gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); + gl.uniform1i(locations['u_layer'], 0); + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']); + gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0); + gl.bufferData(gl.ARRAY_BUFFER, context.quad_positions_f32, gl.STATIC_DRAW); + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_texcoord']); + gl.vertexAttribPointer(locations['a_texcoord'], 2, gl.FLOAT, false, 0, 0); + gl.bufferData(gl.ARRAY_BUFFER, context.quad_texcoords_f32, gl.STATIC_DRAW); + + let tex_index = 0; + + for (const key in context.textures) { + gl.bindTexture(gl.TEXTURE_2D, context.textures[key]); + gl.drawArrays(gl.TRIANGLES, tex_index * 6, 6); + ++tex_index; + } + + // Draw strokes + locations = context.locations['stroke']; + buffers = context.buffers['stroke']; + + gl.useProgram(context.programs['stroke']); + + gl.enableVertexAttribArray(locations['a_pos']); + gl.enableVertexAttribArray(locations['a_color']); + + gl.uniform2f(locations['u_res'], context.canvas.width, context.canvas.height); + gl.uniform2f(locations['u_scale'], state.canvas.zoom, state.canvas.zoom); + gl.uniform2f(locations['u_translation'], state.canvas.offset.x, state.canvas.offset.y); + gl.uniform1i(locations['u_layer'], 1); + + const total_pos_size = context.static_positions_f32.byteLength + context.dynamic_positions_f32.byteLength; + const total_color_size = context.static_colors_u8.byteLength + context.dynamic_colors_u8.byteLength; + const total_point_count = (context.static_positions.length + total_dynamic_positions(context)) / 2; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_pos']); + gl.vertexAttribPointer(locations['a_pos'], 2, gl.FLOAT, false, 0, 0); + gl.bufferData(gl.ARRAY_BUFFER, total_pos_size, gl.DYNAMIC_DRAW); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_positions_f32); + gl.bufferSubData(gl.ARRAY_BUFFER, context.static_positions_f32.byteLength, context.dynamic_positions_f32); + + gl.bindBuffer(gl.ARRAY_BUFFER, buffers['b_color']); + gl.vertexAttribPointer(locations['a_color'], 3, gl.UNSIGNED_BYTE, true, 0, 0); + gl.bufferData(gl.ARRAY_BUFFER, total_color_size, gl.DYNAMIC_DRAW); + gl.bufferSubData(gl.ARRAY_BUFFER, 0, context.static_colors_u8); + gl.bufferSubData(gl.ARRAY_BUFFER, context.static_colors_u8.byteLength, context.dynamic_colors_u8); + + gl.drawArrays(gl.TRIANGLES, 0, total_point_count); +} \ No newline at end of file diff --git a/client/webgl_geometry.js b/client/webgl_geometry.js index 8d14afb..0078c3f 100644 --- a/client/webgl_geometry.js +++ b/client/webgl_geometry.js @@ -111,7 +111,22 @@ function pop_stroke(state, context) { // } } +function get_static_stroke(state) { + if (!state.online) { + return null; + } + + return { + 'color': state.players[state.me].color, + 'width': state.players[state.me].width, + 'points': process_stroke(state.current_strokes[state.me].points), + 'user_id': state.me, + }; +} + function add_static_stroke(state, context, stroke, relax = false) { + if (!state.online || !stroke) return; + push_stroke(state, stroke, context.static_positions, context.static_colors); if (!relax) { @@ -163,6 +178,8 @@ function recompute_dynamic_data(state, context) { } function update_dynamic_stroke(state, context, player_id, point) { + if (!state.online) return; + if (!(player_id in state.current_strokes)) { state.current_strokes[player_id] = { 'points': [], @@ -188,6 +205,8 @@ function update_dynamic_stroke(state, context, player_id, point) { } function clear_dynamic_stroke(state, context, player_id) { + if (!state.online) return; + if (player_id in state.current_strokes) { state.current_strokes[player_id].points.length = 0; state.current_strokes[player_id].color = state.players[state.me].color; diff --git a/client/webgl_listeners.js b/client/webgl_listeners.js index 4638db8..018efff 100644 --- a/client/webgl_listeners.js +++ b/client/webgl_listeners.js @@ -84,7 +84,7 @@ function mousemove(e, state, context) { if (state.drawing) { update_dynamic_stroke(state, context, state.me, canvasp); - fire_event(predraw_event(canvasp.x, canvasp.y)); + fire_event(state, predraw_event(canvasp.x, canvasp.y)); do_draw = true; } @@ -107,21 +107,17 @@ function mouseup(e, state, context) { } if (state.drawing) { - const stroke = { - 'color': state.players[state.me].color, - 'width': state.players[state.me].width, - 'points': process_stroke(state.current_strokes[state.me].points), - 'user_id': state.me, - }; + const stroke = get_static_stroke(state); - add_static_stroke(state, context, stroke); - queue_event(state, stroke_event(state)); - clear_dynamic_stroke(state, context, state.me); + if (stroke) { + add_static_stroke(state, context, stroke); + queue_event(state, stroke_event(state)); + clear_dynamic_stroke(state, context, state.me); + schedule_draw(state, context); + } state.drawing = false; - schedule_draw(state, context); - return; } } @@ -238,14 +234,14 @@ function touchmove(e, state, context) { // BUG: can't see these on other clients!! for (const p of state.touch.buffered) { update_dynamic_stroke(state, context, state.me, p); - fire_event(predraw_event(canvasp.x, canvasp.y)); + fire_event(state, predraw_event(canvasp.x, canvasp.y)); } state.touch.buffered.length = 0; } update_dynamic_stroke(state, context, state.me, canvasp); - fire_event(predraw_event(canvasp.x, canvasp.y)); + fire_event(state, predraw_event(canvasp.x, canvasp.y)); schedule_draw(state, context); } diff --git a/client/websocket.js b/client/websocket.js index 8e3db71..123ecbf 100644 --- a/client/websocket.js +++ b/client/websocket.js @@ -6,20 +6,35 @@ // // Details best described here: https://github.com/kee-org/KeeFox/issues/189 -function ws_connect(state, context, first_connect = false) { +async function ws_connect(state, context, first_connect = false) { const session_id = localStorage.getItem('sessionId') || '0'; const desk_id = state.desk_id; - ws = new WebSocket(`${config.ws_url}?deskId=${desk_id}&sessionId=${session_id}`); - - ws.addEventListener('open', () => on_open(state)); - ws.addEventListener('message', (e) => on_message(state, context, e)); - ws.addEventListener('error', () => on_error(state, context)); - ws.addEventListener('close', () => on_close(state, context)); + try { + const resp = await fetch(config.ping_url); + + if (resp.ok) { + const text = await resp.text(); + + if (text === 'pong') { + ws = new WebSocket(`${config.ws_url}?deskId=${desk_id}&sessionId=${session_id}`); + + ws.addEventListener('open', () => on_open(state)); + ws.addEventListener('message', (e) => on_message(state, context, e)); + ws.addEventListener('error', () => on_error(state, context)); + ws.addEventListener('close', () => on_close(state, context)); + + return; + } + } + } catch (e) {} + + state.timers.offline_toast = setTimeout(() => ws_connect(state, context, first_connect), config.ws_reconnect_timeout); } function on_open(state) { - clearTimeout(state.timers.ws_reconnect); + clearTimeout(state.timers.offline_toast); + ui_online(); if (config.debug_print) console.debug('open') } @@ -47,9 +62,10 @@ async function on_message(state, context, event) { } function on_close(state, context) { + state.timers.offline_toast = setTimeout(() => ui_offline(), config.initial_offline_timeout); ws = null; if (config.debug_print) console.debug('close'); - state.timers.ws_reconnect = setTimeout(() => ws_connect(state, context, false), config.ws_reconnect_timeout); + ws_connect(state, context, false); } function on_error(state, context) {