@geravant/sinain 1.22.3 → 1.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/sinain-core/src/server.ts +61 -4
package/package.json
CHANGED
|
@@ -896,30 +896,43 @@ async function renderEntityPage(entity) {
|
|
|
896
896
|
|
|
897
897
|
// Auto-import path for share links — runs BEFORE the local existence check
|
|
898
898
|
// so a recipient with no prior data on this entity gets the page populated.
|
|
899
|
+
// We KEEP the hash on failure so a refresh retries; only strip on success.
|
|
900
|
+
let shareError = null;
|
|
901
|
+
let shareMode = null;
|
|
899
902
|
if (location.hash.startsWith("#bundle=")) {
|
|
903
|
+
shareMode = "bundle";
|
|
900
904
|
try {
|
|
901
905
|
const json = await ungzipBase64(location.hash.slice("#bundle=".length));
|
|
902
906
|
await api("/knowledge/concepts/import?conflict=merge", {
|
|
903
907
|
method: "POST", headers: {"Content-Type": "application/json"}, body: json
|
|
904
908
|
});
|
|
905
909
|
showToast("✓ Concept imported");
|
|
910
|
+
history.replaceState({}, "", location.pathname); // strip on success only
|
|
906
911
|
} catch (e) {
|
|
907
|
-
|
|
912
|
+
shareError = (e && e.message) || "decode error";
|
|
908
913
|
}
|
|
909
|
-
history.replaceState({}, "", location.pathname); // strip hash
|
|
910
914
|
} else if (location.hash.startsWith("#peer=")) {
|
|
915
|
+
shareMode = "peer";
|
|
911
916
|
const token = location.hash.slice("#peer=".length);
|
|
912
|
-
history.replaceState({}, "", location.pathname); // strip early — keeps refresh sane
|
|
913
917
|
try {
|
|
914
918
|
await ShareManager.connectAsRecipient(token);
|
|
915
919
|
showToast("✓ Concept imported via peer");
|
|
920
|
+
history.replaceState({}, "", location.pathname); // strip on success only
|
|
916
921
|
} catch (e) {
|
|
917
|
-
|
|
922
|
+
shareError = (e && e.message) || "unreachable";
|
|
918
923
|
}
|
|
919
924
|
}
|
|
920
925
|
|
|
921
926
|
const page = await api("/knowledge/page?entity=" + encodeURIComponent(entity));
|
|
922
927
|
if (!page.ok || page.fact_count === 0) {
|
|
928
|
+
// If a share-import was attempted and failed AND we have no local data,
|
|
929
|
+
// render an explicit share-failed view rather than the generic
|
|
930
|
+
// MissingConcept page — the user needs to know the share didn't land,
|
|
931
|
+
// not just that the entity is absent.
|
|
932
|
+
if (shareError && page.fact_count === 0) {
|
|
933
|
+
renderShareFailed(entity, shareMode, shareError, root);
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
923
936
|
if (page.fact_count === 0) {
|
|
924
937
|
renderMissingConcept(entity, root);
|
|
925
938
|
return;
|
|
@@ -1184,6 +1197,50 @@ function showUndoToast(factId, undoToken, sourceEntity) {
|
|
|
1184
1197
|
setTimeout(() => { if ($("#undoToast")) root.innerHTML = ""; }, ms);
|
|
1185
1198
|
}
|
|
1186
1199
|
|
|
1200
|
+
// ── Share-failed landing ──────────────────────────────────────────────────
|
|
1201
|
+
// Shown when a #bundle= or #peer= share-link couldn't be imported AND no
|
|
1202
|
+
// local data exists for the entity. Distinct from MissingConcept because
|
|
1203
|
+
// the user explicitly tried to load shared data — they need to know that
|
|
1204
|
+
// transfer failed, not just that the entity isn't here.
|
|
1205
|
+
function renderShareFailed(entity, mode, errorMsg, root) {
|
|
1206
|
+
document.title = "Share couldn't load · Sinain";
|
|
1207
|
+
const isPeer = mode === "peer";
|
|
1208
|
+
root.innerHTML = \`
|
|
1209
|
+
<h1>Share couldn't be loaded</h1>
|
|
1210
|
+
<div class="error-block" style="margin-bottom: 16px;">
|
|
1211
|
+
<strong>\${esc(entity)}</strong> — \${isPeer ? "couldn't receive bundle from sender" : "couldn't decode bundle from URL"}.
|
|
1212
|
+
<br><br>
|
|
1213
|
+
<code>\${esc(errorMsg)}</code>
|
|
1214
|
+
</div>
|
|
1215
|
+
\${isPeer ? \`
|
|
1216
|
+
<p style="color: var(--fg-dim); line-height: 1.5;">
|
|
1217
|
+
Likely causes:
|
|
1218
|
+
</p>
|
|
1219
|
+
<ul style="color: var(--fg-dim); line-height: 1.6;">
|
|
1220
|
+
<li>The sender's tab is closed (peer share needs sender's sinain-core open).</li>
|
|
1221
|
+
<li>The share has expired or was revoked.</li>
|
|
1222
|
+
<li>Network/NAT blocked the WebRTC connection (about 10–20% of pairs need a TURN relay).</li>
|
|
1223
|
+
</ul>
|
|
1224
|
+
<p style="color: var(--fg-dim);">
|
|
1225
|
+
Ask the sender to reopen sinain (their peer registration auto-resumes), then click <strong>Retry</strong>.
|
|
1226
|
+
</p>\` : \`
|
|
1227
|
+
<p style="color: var(--fg-dim); line-height: 1.5;">
|
|
1228
|
+
The link's <code>#bundle=…</code> portion may have been truncated by a chat tool — some preview tools strip URL fragments.
|
|
1229
|
+
Ask the sender to re-share, ideally pasted as a code block so the URL stays intact.
|
|
1230
|
+
</p>\`}
|
|
1231
|
+
<div class="actions" style="margin-top: 20px; display: flex; gap: 10px;">
|
|
1232
|
+
<button class="primary" onclick="location.reload()">Retry</button>
|
|
1233
|
+
<button onclick="window._proceedWithoutShare('\${esc(entity)}')">Continue without share</button>
|
|
1234
|
+
</div>
|
|
1235
|
+
\`;
|
|
1236
|
+
// Continue-without-share clears the hash so the next render takes the
|
|
1237
|
+
// normal MissingConcept path (which has the file-drop dropzone).
|
|
1238
|
+
window._proceedWithoutShare = (eid) => {
|
|
1239
|
+
history.replaceState({}, "", "/knowledge/ui/entity/" + encodeURIComponent(eid));
|
|
1240
|
+
renderMissingConcept(eid, root);
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1187
1244
|
// ── Missing concept landing ───────────────────────────────────────────────
|
|
1188
1245
|
function renderMissingConcept(entity, root) {
|
|
1189
1246
|
document.title = "Missing · " + entity;
|