fix(ios): ios local access limit for self-hosted workspace (#15338)

## Summary
- Route iOS nbstore worker token reads through the main-thread
MessagePort and skip Capacitor Auth on `/socket.io` polling so self-host
WebSocket/XHR sync no longer hangs on Connect timeout.
- Harden workspace `flavour:id` routing, DocSyncPeer abort/status
handling, and `resetSync` so local selfhost edits push and Mac browsers
can receive them.
- Soften root-doc readiness waits and session-exchange throttling to
keep mobile selfhost sign-in/sync stable under retries.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

* **New Features**
* Improved workspace switching across local and remote environments,
preserving workspace type and server context.
  * Added support for page navigation with query parameters.
* Added iOS local-network permission messaging for self-hosted
workspaces.
* **Bug Fixes**
* Improved document synchronization, reset handling, prioritized
document refreshes, and retry behavior.
* Prevented authentication headers and refresh attempts for socket
connection requests.
* Improved workspace reopening and routing when multiple workspace types
share an ID.
  * Fixed handling of unlimited data query limits.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DarkSky <darksky2048@gmail.com>
This commit is contained in:
keepClamDownandDarkSky authored and GitHub committed 2026-08-24 09:33:58 +08:00
1 parent b530198a3b
commit cd6593c659
15 files changed
+448 -78

No files matched your search

@@ -113,6 +113,12 @@ class TestDocStorage implements DocStorage {
}
}
class TimestampBlindDocStorage extends IndexedDBDocStorage {
override async getDocTimestamps(): Promise<DocClocks> {
return {};
}
}
class PermissionDeniedRemoteDocStorage implements DocStorage {
readonly storageType = 'doc' as const;
readonly connection = new DummyConnection();
@@ -306,7 +312,7 @@ test('doc', async () => {
type: 'workspace',
});
const peerBDoc = new IndexedDBDocStorage({
const peerBDoc = new TimestampBlindDocStorage({
id: 'ws1',
flavour: 'b',
type: 'workspace',
@@ -340,6 +346,26 @@ test('doc', async () => {
docId: 'doc1',
bin: update,
});
const prioritizedDocId = 'prioritized-doc';
const localPrioritizedDoc = new YDoc();
localPrioritizedDoc.getMap('test').set('local', true);
const localPrioritizedClock = await peerA.get('doc').pushDocUpdate({
docId: prioritizedDocId,
bin: encodeStateAsUpdate(localPrioritizedDoc),
});
await peerASync.setPeerPushedClock('b', localPrioritizedClock);
const remotePrioritizedDoc = new YDoc();
remotePrioritizedDoc.getMap('test').set('remote', true);
await peerB.get('doc').pushDocUpdate({
docId: prioritizedDocId,
bin: encodeStateAsUpdate(remotePrioritizedDoc),
});
const rootDoc = new YDoc();
rootDoc.getMap('meta').set('name', 'Self-host workspace');
await peerB.get('doc').pushDocUpdate({
docId: 'ws1',
bin: encodeStateAsUpdate(rootDoc),
});
const sync = new Sync({
local: peerA,
@@ -348,6 +374,7 @@ test('doc', async () => {
c: peerC,
},
});
const removeRootPriority = sync.doc.addPriority('ws1', 100);
sync.start();
await new Promise(resolve => setTimeout(resolve, 1000));
@@ -366,8 +393,33 @@ test('doc', async () => {
hello: 'world',
},
});
const root = await peerA.get('doc').getDoc('ws1');
expectYjsEqual(root!.bin, {
meta: {
name: 'Self-host workspace',
},
});
const prioritized = await peerA.get('doc').getDoc(prioritizedDocId);
expectYjsEqual(prioritized!.bin, {
test: {
local: true,
},
});
}
const removeDocPriority = sync.doc.addPriority(prioritizedDocId, 100);
await vi.waitFor(async () => {
const prioritized = await peerA.get('doc').getDoc(prioritizedDocId);
expectYjsEqual(prioritized!.bin, {
test: {
local: true,
remote: true,
},
});
});
doc.getMap('test').set('foo', 'bar');
const update2 = encodeStateAsUpdate(doc);
await peerC.get('doc').pushDocUpdate({
@@ -394,6 +446,13 @@ test('doc', async () => {
},
});
}
removeDocPriority();
removeRootPriority();
sync.stop();
peerA.disconnect();
peerB.disconnect();
peerC.disconnect();
});
test('blob', async () => {