Skip to content

Commit

Permalink
fix: conflicts
Browse files Browse the repository at this point in the history
Signed-off-by: Luka Trovic <luka@nextcloud.com>
  • Loading branch information
juliusknorr authored and luka-nextcloud committed Aug 29, 2024
1 parent 55e5d24 commit 6f61157
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 2 deletions.
10 changes: 8 additions & 2 deletions lib/Service/DocumentService.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
use OCA\Text\Db\StepMapper;
use OCA\Text\Exception\DocumentHasUnsavedChangesException;
use OCA\Text\Exception\DocumentSaveConflictException;
use OCA\Text\YjsMessage;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Constants;
use OCP\DB\Exception;
Expand Down Expand Up @@ -230,11 +231,16 @@ public function writeDocumentState(int $documentId, string $content): void {
public function addStep(Document $document, Session $session, $steps, $version, $shareToken): array {
$sessionId = $session->getId();
$documentId = $session->getDocumentId();
$readOnly = $this->isReadOnly($this->getFileForSession($session, $shareToken), $shareToken);
$stepsToInsert = [];
$querySteps = [];
$getStepsSinceVersion = null;
$newVersion = $version;
foreach ($steps as $step) {
$message = YjsMessage::fromBase64($step);
if ($readOnly && $message->isUpdate()) {
continue;
}
// Steps are base64 encoded messages of the yjs protocols
// https://github.com/yjs/y-protocols
// Base64 encoded values smaller than "AAE" belong to sync step 1 messages.
Expand All @@ -245,8 +251,8 @@ public function addStep(Document $document, Session $session, $steps, $version,
array_push($stepsToInsert, $step);
}
}
if (sizeof($stepsToInsert) > 0) {
if ($this->isReadOnly($this->getFileForSession($session, $shareToken), $shareToken)) {
if (count($stepsToInsert) > 0) {
if ($readOnly) {
throw new NotPermittedException('Read-only client tries to push steps with changes');
}
$newVersion = $this->insertSteps($document, $session, $stepsToInsert, $version);
Expand Down
98 changes: 98 additions & 0 deletions lib/YjsMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

namespace OCA\Text;

use InvalidArgumentException;

/**
* Steps are base64 encoded messages of the yjs protocols
* https://github.com/yjs/y-protocols
*
* This class is a simple representation of a message containing some methods
* to decode parts of it for what we need on the backend
*
* Relevant resources:
* https://github.com/yjs/y-protocols/blob/master/PROTOCOL.md
* https://github.com/yjs/y-websocket/blob/master/src/y-websocket.js#L19-L22
* https://github.com/yjs/y-protocols/blob/master/sync.js#L38-L40
* https://github.com/dmonad/lib0/blob/master/decoding.js
*/
class YjsMessage {

public const YJS_MESSAGE_SYNC = 0;
public const YJS_MESSAGE_AWARENESS = 1;
public const YJS_MESSAGE_AWARENESS_QUERY = 3;

public const YJS_MESSAGE_SYNC_STEP1 = 0;
public const YJS_MESSAGE_SYNC_STEP2 = 1;
public const YJS_MESSAGE_SYNC_UPDATE = 2;

private int $pos = 0;

public function __construct(
private string $data = ''
) {
}

public static function fromBase64(string $data = ''): self {
return new self(base64_decode($data));
}

/**
* https://github.com/dmonad/lib0/blob/bd69ab4dc701d77e808f2bab08d96d63acd297da/decoding.js#L242
*/
public function readVarUint(): int {
$bytes = array_values(unpack('C*', $this->data));
$num = 0;
$mult = 1;
$len = count($bytes);
while ($this->pos < $len) {
$r = $bytes[$this->pos++];
// num = num | ((r & binary.BITS7) << len)
$num = $num + ($r & 0b1111111) * $mult;
$mult *= 128;
if ($r <= 0b1111111) {
return $num;
}
// Number.MAX_SAFE_INTEGER in JS
if ($num > 9007199254740990) {
throw new \OutOfBoundsException();
}
}
throw new InvalidArgumentException();
}

public function getYjsMessageType(): int {
$oldPos = $this->pos;
$this->pos = 0;
$messageType = $this->readVarUint();
$this->pos = $oldPos;
return $messageType;
}

public function getYjsSyncType(): int {
$oldPos = $this->pos;
$this->pos = 0;
$messageType = $this->readVarUint();
if ($messageType !== self::YJS_MESSAGE_SYNC) {
throw new \ValueError('Message is not a sync message');
}
$syncType = $this->readVarUint();
$this->pos = $oldPos;
return $syncType;
}

/**
* Based on https://github.com/yjs/y-protocols/blob/master/PROTOCOL.md#handling-read-only-users
*/
public function isUpdate(): bool {
if ($this->getYjsMessageType() === self::YJS_MESSAGE_SYNC) {
if (in_array($this->getYjsSyncType(), [self::YJS_MESSAGE_SYNC_STEP2, self::YJS_MESSAGE_SYNC_UPDATE])) {
return true;
}
}

return false;
}

}

0 comments on commit 6f61157

Please sign in to comment.