Skip to content

Commit

Permalink
fix: support fragmented websocket frames, resolves alpha22jp#77
Browse files Browse the repository at this point in the history
This commit introduces handling for fragmented websocket frames in
the atomic-chrome Emacs extension.

A new hash table, `atomic-chrome-frame-socket-incomplete-buffers-hash`, maps websocket
sockets to buffers that accumulate payload fragments from incomplete
websocket frames.

This allows for efficient handling and concatenation of large and/or
fragented messages. The `atomic-chrome-on-message` function has been
updated to accumulate payload fragments in a dedicated buffer when
frames are marked as incomplete or a previous incomplete frame exists
for the socket. Upon receiving the final fragment, the full payload is
reconstructed, decoded, and processed as a JSON object to either
create or update associated Emacs buffers for editing.

Additionally, the `atomic-chrome-on-close` function now
removes the associated buffer from the hash table when a websocket
socket is closed.
  • Loading branch information
KarimAziev committed Mar 12, 2024
1 parent 072a137 commit e188dec
Showing 1 changed file with 67 additions and 16 deletions.
83 changes: 67 additions & 16 deletions atomic-chrome.el
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
;;; atomic-chrome.el --- Edit Chrome text area with Emacs using Atomic Chrome
;;; atomic-chrome.el --- Edit Chrome text area with Emacs using Atomic Chrome -*- lexical-binding: t -*-

;; Copyright (C) 2016 alpha22jp <[email protected]>

Expand Down Expand Up @@ -244,26 +244,77 @@ TITLE is used for the buffer name and TEXT is inserted to the buffer."
(erase-buffer)
(insert text)))))

(defvar atomic-chrome-frame-socket-incomplete-buffers-hash (make-hash-table
:test 'eq)
"A hash table mapping websocket sockets to buffers.
Each buffer accumulates payload fragments from incomplete websocket frames
associated with a specific socket connection.
This allows for efficient handling and concatenation of large and/or fragmented
messages.")


(defun atomic-chrome-on-message (socket frame)
"Handle data received from the websocket client specified by SOCKET.
FRAME holds the raw data received."
(let ((msg (json-read-from-string
(decode-coding-string
(encode-coding-string (websocket-frame-payload frame) 'utf-8)
'utf-8))))
(let-alist msg
(if (eq (websocket-server-conn socket) atomic-chrome-server-ghost-text)
(if (atomic-chrome-get-buffer-by-socket socket)
(atomic-chrome-update-buffer socket .text)
(atomic-chrome-create-buffer socket .url .title .text))
(cond ((string= .type "register")
(atomic-chrome-create-buffer socket .payload.url .payload.title .payload.text))
((string= .type "updateText")
(when atomic-chrome-enable-bidirectional-edit
(atomic-chrome-update-buffer socket .payload.text))))))))
FRAME holds the raw data received.
When frames are marked as incomplete or a previous incomplete frame
exists for the SOCKET, payload fragments are accumulated in a dedicated buffer.
Upon receiving the final fragment, the full payload is reconstructed,
decoded, and processed as a JSON object to either create or update
associated Emacs buffers for editing."
(let ((raw-payload (websocket-frame-payload frame))
(incomplete-data-buffer
(gethash socket
atomic-chrome-frame-socket-incomplete-buffers-hash)))
(cond ((not (websocket-frame-completep frame))
(unless incomplete-data-buffer
(setq incomplete-data-buffer
(generate-new-buffer
(format " *atomic-chrome-%s*" (current-time-string)) t)))
(with-current-buffer incomplete-data-buffer
(goto-char (point-max))
(insert raw-payload))
(puthash socket incomplete-data-buffer
atomic-chrome-frame-socket-incomplete-buffers-hash))
(t
(let* ((combined-payload
(if (not incomplete-data-buffer)
raw-payload
(unwind-protect
(with-current-buffer
incomplete-data-buffer
(goto-char (point-max))
(insert raw-payload)
(set-buffer-modified-p nil)
(buffer-string))
(remhash socket
atomic-chrome-frame-socket-incomplete-buffers-hash)
(kill-buffer incomplete-data-buffer))))
(msg (json-read-from-string
(decode-coding-string
(encode-coding-string combined-payload
'utf-8)
'utf-8))))
(let-alist msg
(if (eq (websocket-server-conn socket)
atomic-chrome-server-ghost-text)
(if (atomic-chrome-get-buffer-by-socket socket)
(atomic-chrome-update-buffer socket .text)
(atomic-chrome-create-buffer socket .url .title .text))
(cond ((string= .type "register")
(atomic-chrome-create-buffer socket .payload.url
.payload.title
.payload.text))
((string= .type "updateText")
(when atomic-chrome-enable-bidirectional-edit
(atomic-chrome-update-buffer socket .payload.text)))))))))))


(defun atomic-chrome-on-close (socket)
"Function to handle request from client to close websocket SOCKET."
(remhash socket atomic-chrome-frame-socket-incomplete-buffers-hash)
(let ((buffer (atomic-chrome-get-buffer-by-socket socket)))
(when buffer (atomic-chrome-close-edit-buffer buffer))))

Expand Down

0 comments on commit e188dec

Please sign in to comment.