diff --git a/sdk/demos/groups/script.js b/sdk/demos/groups/script.js
index a9538c07..ed6b5b4e 100644
--- a/sdk/demos/groups/script.js
+++ b/sdk/demos/groups/script.js
@@ -57,17 +57,8 @@ async function v3Post(action, params = {}) {
return res.json()
}
-authButton.onclick = () => window.web10.openAuthPortal(AUTH_ORIGIN)
-window.web10.authListen(() => initApp())
-
-function initApp() {
- authButton.innerHTML = "Log out"
- authButton.onclick = () => { w.signOut(); window.location.reload() }
- const t = w.readToken()
- message.innerHTML = `Signed in as ${t["provider"]}/${t["username"]}`
- app.classList.remove('hidden')
-
- // Request app contract via auth UI (not direct API)
+authButton.onclick = () => {
+ window.web10.openAuthPortal(AUTH_ORIGIN)
w.contractRequest([{
kind: 'app',
app_origin: window.location.origin,
@@ -87,6 +78,15 @@ function initApp() {
loadManageGroups()
})
}
+window.web10.authListen(() => initApp())
+
+function initApp() {
+ authButton.innerHTML = "Log out"
+ authButton.onclick = () => { w.signOut(); window.location.reload() }
+ const t = w.readToken()
+ message.innerHTML = `Signed in as ${t["provider"]}/${t["username"]}`
+ app.classList.remove('hidden')
+}
// Self-register
fetch(`${API_ORIGIN}/v3/apps/register`, {
diff --git a/sdk/demos/hello/script.js b/sdk/demos/hello/script.js
index d4912be3..70fa7046 100644
--- a/sdk/demos/hello/script.js
+++ b/sdk/demos/hello/script.js
@@ -6,16 +6,8 @@ const isLocal = host === 'localhost' || host === '127.0.0.1' || host.endsWith('.
const AUTH_ORIGIN = isLocal ? 'http://auth.localhost' : isDev ? 'https://auth.dev.web10.app' : 'https://auth.web10.app'
const w = window.web10.createV3Client({ apiOrigin: isLocal ? 'http://api.localhost' : isDev ? 'https://api.dev.web10.app' : 'https://api.web10.app' })
-authButton.onclick = () => window.web10.openAuthPortal(AUTH_ORIGIN)
-window.web10.authListen(() => initApp())
-
-function initApp() {
- authButton.innerHTML = 'log out'
- authButton.onclick = () => { w.signOut(); window.location.reload() }
- const t = w.readToken()
- message.innerHTML = `hello ${t.provider}/${t.username},
`
-
- // Request app contract for basic read access
+authButton.onclick = () => {
+ window.web10.openAuthPortal(AUTH_ORIGIN)
w.contractRequest([{
kind: 'app',
app_origin: window.location.origin,
@@ -32,5 +24,13 @@ function initApp() {
}
})
}
+window.web10.authListen(() => initApp())
+
+function initApp() {
+ authButton.innerHTML = 'log out'
+ authButton.onclick = () => { w.signOut(); window.location.reload() }
+ const t = w.readToken()
+ message.innerHTML = `hello ${t.provider}/${t.username},
`
+}
if (w.isSignedIn()) initApp()
\ No newline at end of file
diff --git a/sdk/demos/mailer/script.js b/sdk/demos/mailer/script.js
index d660bb71..c436f0db 100644
--- a/sdk/demos/mailer/script.js
+++ b/sdk/demos/mailer/script.js
@@ -8,16 +8,8 @@ const w = window.web10.createV3Client({ apiOrigin: isLocal ? 'http://api.localho
const COLLECTION = 'mail'
-authButton.onclick = () => window.web10.openAuthPortal(AUTH_ORIGIN)
-window.web10.authListen(() => initApp())
-
-function initApp() {
- authButton.innerHTML = 'log out'
- authButton.onclick = () => { w.signOut(); window.location.reload() }
- const t = w.readToken()
- message.innerHTML = `hello ${t.provider}/${t.username},
`
-
- // Request app contract for mail collection
+authButton.onclick = () => {
+ window.web10.openAuthPortal(AUTH_ORIGIN)
w.contractRequest([{
kind: 'app',
app_origin: window.location.origin,
@@ -35,6 +27,14 @@ function initApp() {
}
})
}
+window.web10.authListen(() => initApp())
+
+function initApp() {
+ authButton.innerHTML = 'log out'
+ authButton.onclick = () => { w.signOut(); window.location.reload() }
+ const t = w.readToken()
+ message.innerHTML = `hello ${t.provider}/${t.username},
`
+}
async function readMail() {
try {
diff --git a/sdk/demos/notes/script.js b/sdk/demos/notes/script.js
index 0689f2e0..1b7e71d5 100644
--- a/sdk/demos/notes/script.js
+++ b/sdk/demos/notes/script.js
@@ -9,16 +9,8 @@ const w = window.web10.createV3Client({ apiOrigin: isLocal ? 'http://api.localho
const COLLECTION = 'notes'
let NOTES_GROUP = null
-authButton.onclick = () => window.web10.openAuthPortal(AUTH_ORIGIN)
-window.web10.authListen(() => initApp())
-
-function initApp() {
- authButton.innerHTML = 'log out'
- authButton.onclick = () => { w.signOut(); window.location.reload() }
- const t = w.readToken()
- message.innerHTML = `hello ${t.provider}/${t.username},
`
-
- // Request app contract for notes collection
+authButton.onclick = () => {
+ window.web10.openAuthPortal(AUTH_ORIGIN)
w.contractRequest([{
kind: 'app',
app_origin: window.location.origin,
@@ -36,6 +28,14 @@ function initApp() {
}
})
}
+window.web10.authListen(() => initApp())
+
+function initApp() {
+ authButton.innerHTML = 'log out'
+ authButton.onclick = () => { w.signOut(); window.location.reload() }
+ const t = w.readToken()
+ message.innerHTML = `hello ${t.provider}/${t.username},
`
+}
function ensureNotesGroup(username, provider) {
const groupName = `notes-${username}`
diff --git a/ui/src/interfaces/Interface.tsx b/ui/src/interfaces/Interface.tsx
index 02f708fd..805b98f8 100644
--- a/ui/src/interfaces/Interface.tsx
+++ b/ui/src/interfaces/Interface.tsx
@@ -186,25 +186,25 @@ function useInterface() {
// v3 contract listening — direct postMessage, no wapiAuth wrapper
I.initAuthenticator = function () {
if (typeof window === 'undefined') return;
+ let contractReceived = false;
window.addEventListener('message', (e) => {
- // Contract requests are inherently cross-origin (app → auth UI).
- // Only reject obviously malicious sources (null origin from sandboxed iframe).
if (!e.origin) return;
if (e.data?.type === 'acr' || e.data?.type === 'contract') {
I.setPendingContracts(normalizeContracts(e.data, e.source));
+ contractReceived = true;
}
});
- // Signal readiness to opener — broadcast periodically so the SDK can
- // always sync before sending a contract, even when reusing an open popup
- // that was opened seconds ago (user logged in, then clicked "Create Group").
if (window.opener) {
+ // Broadcast auth_ready until contract arrives — the app may call
+ // contractRequest after login, so we keep signaling readiness.
const broadcastReady = () => {
+ if (contractReceived) return;
try {
window.opener.postMessage({ type: 'auth_ready' }, '*');
} catch { /* cross-origin */ }
};
broadcastReady();
- const interval = setInterval(broadcastReady, 1000);
+ const interval = setInterval(broadcastReady, 500);
window.addEventListener('beforeunload', () => clearInterval(interval));
}
}
@@ -722,9 +722,6 @@ function useInterface() {
I.denyACR = (c: any) => I.denyContract({ ...c, kind: c.kind || 'app' });
I.removePendingACR = (c: any) => I.removePendingContract({ ...c, kind: c.kind || 'app' });
- // Return to the requesting app, logging it in. Send the current v3 token
- // directly to the opener — no tiered token needed for v3.
- // Do NOT close the popup — keep it alive so contractRequest can reuse it.
I.goToApp = function () {
const token = I.v3.state?.token;
if (token && window.opener) {
@@ -733,10 +730,12 @@ function useInterface() {
const target = referrer ? new URL(referrer).origin : '*';
I.setStatus("Connecting…");
window.opener.postMessage({ type: 'auth', token }, target);
- // Don't close — SDK needs this popup for contractRequest
+ window.close();
} catch {
I.setStatus("Failed to connect to app.");
}
+ } else if (window.opener) {
+ window.close();
}
}