fix(sessions): fully consume PWA launch action
This commit is contained in:
1 parent
cd04f5fcc2
commit
127a57e0c5
2 files changed
+178
-10
No files matched your search
+12
-3
@@ -4151,11 +4151,14 @@ function _sessionUrlForSid(sid){
|
|||||||
const current=new URL(window.location.href);
|
const current=new URL(window.location.href);
|
||||||
current.searchParams.delete('session');
|
current.searchParams.delete('session');
|
||||||
current.searchParams.delete('session_id');
|
current.searchParams.delete('session_id');
|
||||||
current.searchParams.delete('action');
|
|
||||||
current.searchParams.delete('q');
|
current.searchParams.delete('q');
|
||||||
current.searchParams.delete('prompt');
|
current.searchParams.delete('prompt');
|
||||||
current.searchParams.delete('send');
|
current.searchParams.delete('send');
|
||||||
base.search=current.searchParams.toString();
|
const retained=new URLSearchParams();
|
||||||
|
current.searchParams.forEach((value,key)=>{
|
||||||
|
if(key!=='action'||value!=='new-chat') retained.append(key,value);
|
||||||
|
});
|
||||||
|
base.search=retained.toString();
|
||||||
base.hash=current.hash;
|
base.hash=current.hash;
|
||||||
}catch(_e){}
|
}catch(_e){}
|
||||||
return base.pathname+base.search+base.hash;
|
return base.pathname+base.search+base.hash;
|
||||||
@@ -4164,7 +4167,13 @@ function _setActiveSessionUrl(sid){
|
|||||||
if(typeof window==='undefined'||!window.history||!sid) return;
|
if(typeof window==='undefined'||!window.history||!sid) return;
|
||||||
const next=_sessionUrlForSid(sid);
|
const next=_sessionUrlForSid(sid);
|
||||||
if(next && next!==(window.location.pathname+window.location.search+window.location.hash)){
|
if(next && next!==(window.location.pathname+window.location.search+window.location.hash)){
|
||||||
window.history.pushState({session_id:sid},'',next);
|
let consumeLaunchAction=false;
|
||||||
|
try{
|
||||||
|
const current=new URL(window.location.href);
|
||||||
|
consumeLaunchAction=current.searchParams.getAll('action').includes('new-chat');
|
||||||
|
}catch(_e){}
|
||||||
|
const method=consumeLaunchAction?'replaceState':'pushState';
|
||||||
|
window.history[method]({session_id:sid},'',next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ def _run_node(source: str) -> dict:
|
|||||||
return json.loads(result.stdout)
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
|
||||||
def test_new_chat_launch_action_is_consumed_before_hard_refresh():
|
def test_only_exact_new_chat_launch_action_is_consumed_before_hard_refresh():
|
||||||
source = f"""
|
source = f"""
|
||||||
const sessionsSrc = {SESSIONS_JS!r};
|
const sessionsSrc = {SESSIONS_JS!r};
|
||||||
function extractFunc(src, name) {{
|
function extractFunc(src, name) {{
|
||||||
@@ -56,22 +56,22 @@ function applyUrl(rel) {{
|
|||||||
}}
|
}}
|
||||||
global.window = {{ location: {{}} }};
|
global.window = {{ location: {{}} }};
|
||||||
global.document = {{ baseURI: 'https://example.test/app/' }};
|
global.document = {{ baseURI: 'https://example.test/app/' }};
|
||||||
applyUrl('/app/?action=new-chat&keep=1#frag');
|
applyUrl('/app/?action=new-chat&action=continue&keep=1&keep=2#frag');
|
||||||
globalThis._sessionUrlForSid = (0, eval)('(' + extractFunc(sessionsSrc, '_sessionUrlForSid') + ')');
|
globalThis._sessionUrlForSid = (0, eval)('(' + extractFunc(sessionsSrc, '_sessionUrlForSid') + ')');
|
||||||
const promoted = _sessionUrlForSid('session-123');
|
const promoted = _sessionUrlForSid('session-123');
|
||||||
const promotedUrl = new URL(promoted, 'https://example.test');
|
const promotedUrl = new URL(promoted, 'https://example.test');
|
||||||
console.log(JSON.stringify({{
|
console.log(JSON.stringify({{
|
||||||
promoted,
|
promoted,
|
||||||
launchAction: promotedUrl.searchParams.get('action'),
|
launchActions: promotedUrl.searchParams.getAll('action'),
|
||||||
keep: promotedUrl.searchParams.get('keep'),
|
keep: promotedUrl.searchParams.getAll('keep'),
|
||||||
}}));
|
}}));
|
||||||
"""
|
"""
|
||||||
payload = _run_node(source)
|
payload = _run_node(source)
|
||||||
|
|
||||||
assert payload == {
|
assert payload == {
|
||||||
"promoted": "/app/session/session-123?keep=1#frag",
|
"promoted": "/app/session/session-123?action=continue&keep=1&keep=2#frag",
|
||||||
"launchAction": None,
|
"launchActions": ["continue"],
|
||||||
"keep": "1",
|
"keep": ["1", "2"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -108,3 +108,162 @@ console.log(JSON.stringify({{
|
|||||||
"ordinarySessionLoad": False,
|
"ordinarySessionLoad": False,
|
||||||
}
|
}
|
||||||
assert "if(_shouldStartFreshPwaChat(pwaLaunchAction,urlSession)){" in BOOT_JS
|
assert "if(_shouldStartFreshPwaChat(pwaLaunchAction,urlSession)){" in BOOT_JS
|
||||||
|
|
||||||
|
|
||||||
|
def test_root_pwa_launch_replaces_source_history_before_boot_reentry():
|
||||||
|
source = f"""
|
||||||
|
const sessionsSrc = {SESSIONS_JS!r};
|
||||||
|
const bootSrc = {BOOT_JS!r};
|
||||||
|
function extractFunc(src, name) {{
|
||||||
|
const re = new RegExp('function\\\\s+' + name + '\\\\s*\\\\(');
|
||||||
|
const start = src.search(re);
|
||||||
|
if (start < 0) throw new Error(name + ' not found');
|
||||||
|
let i = src.indexOf('{{', start);
|
||||||
|
let depth = 1; i++;
|
||||||
|
while (depth > 0 && i < src.length) {{
|
||||||
|
if (src[i] === '{{') depth++;
|
||||||
|
else if (src[i] === '}}') depth--;
|
||||||
|
i++;
|
||||||
|
}}
|
||||||
|
return src.slice(start, i);
|
||||||
|
}}
|
||||||
|
function applyUrl(rel) {{
|
||||||
|
const next = new URL(rel, 'https://example.test');
|
||||||
|
window.location.href = next.href;
|
||||||
|
window.location.pathname = next.pathname;
|
||||||
|
window.location.search = next.search;
|
||||||
|
window.location.hash = next.hash;
|
||||||
|
}}
|
||||||
|
const entries = ['/app/?source=pwa&action=new-chat'];
|
||||||
|
let index = 0;
|
||||||
|
global.window = {{
|
||||||
|
location: {{}},
|
||||||
|
history: {{
|
||||||
|
state: null,
|
||||||
|
pushState(state, _title, url) {{
|
||||||
|
entries.splice(index + 1);
|
||||||
|
entries.push(url);
|
||||||
|
index++;
|
||||||
|
this.state = state;
|
||||||
|
applyUrl(url);
|
||||||
|
}},
|
||||||
|
replaceState(state, _title, url) {{
|
||||||
|
entries[index] = url;
|
||||||
|
this.state = state;
|
||||||
|
applyUrl(url);
|
||||||
|
}},
|
||||||
|
}},
|
||||||
|
}};
|
||||||
|
global.document = {{ baseURI: 'https://example.test/app/' }};
|
||||||
|
applyUrl(entries[0]);
|
||||||
|
globalThis._sessionUrlForSid = (0, eval)('(' + extractFunc(sessionsSrc, '_sessionUrlForSid') + ')');
|
||||||
|
globalThis._setActiveSessionUrl = (0, eval)('(' + extractFunc(sessionsSrc, '_setActiveSessionUrl') + ')');
|
||||||
|
globalThis._shouldStartFreshPwaChat = (0, eval)('(' + extractFunc(bootSrc, '_shouldStartFreshPwaChat') + ')');
|
||||||
|
let newSessionCalls = 0;
|
||||||
|
let loadSessionCalls = 0;
|
||||||
|
function simulatedBoot() {{
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const explicitSid = url.pathname.split('/session/')[1] || url.searchParams.get('session');
|
||||||
|
if (_shouldStartFreshPwaChat(url.searchParams.get('action'), explicitSid)) newSessionCalls++;
|
||||||
|
else if (explicitSid) loadSessionCalls++;
|
||||||
|
}}
|
||||||
|
newSessionCalls++;
|
||||||
|
_setActiveSessionUrl('session-123');
|
||||||
|
const backTarget = entries[index - 1] || entries[index];
|
||||||
|
applyUrl(backTarget);
|
||||||
|
simulatedBoot();
|
||||||
|
console.log(JSON.stringify({{entries, index, newSessionCalls, loadSessionCalls, href: window.location.href}}));
|
||||||
|
"""
|
||||||
|
payload = _run_node(source)
|
||||||
|
|
||||||
|
assert payload == {
|
||||||
|
"entries": ["/app/session/session-123?source=pwa"],
|
||||||
|
"index": 0,
|
||||||
|
"newSessionCalls": 1,
|
||||||
|
"loadSessionCalls": 1,
|
||||||
|
"href": "https://example.test/app/session/session-123?source=pwa",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("initial_url", "expected_url"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"/app/session/session-123?action=new-chat&action=continue&keep=1#frag",
|
||||||
|
"/app/session/session-123?action=continue&keep=1#frag",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"/app/?session=session-123&action=new-chat&action=continue&keep=1#frag",
|
||||||
|
"/app/session/session-123?action=continue&keep=1#frag",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_explicit_session_wins_and_replaces_stale_launch_history(initial_url, expected_url):
|
||||||
|
source = f"""
|
||||||
|
const sessionsSrc = {SESSIONS_JS!r};
|
||||||
|
const bootSrc = {BOOT_JS!r};
|
||||||
|
function extractFunc(src, name) {{
|
||||||
|
const re = new RegExp('function\\\\s+' + name + '\\\\s*\\\\(');
|
||||||
|
const start = src.search(re);
|
||||||
|
if (start < 0) throw new Error(name + ' not found');
|
||||||
|
let i = src.indexOf('{{', start);
|
||||||
|
let depth = 1; i++;
|
||||||
|
while (depth > 0 && i < src.length) {{
|
||||||
|
if (src[i] === '{{') depth++;
|
||||||
|
else if (src[i] === '}}') depth--;
|
||||||
|
i++;
|
||||||
|
}}
|
||||||
|
return src.slice(start, i);
|
||||||
|
}}
|
||||||
|
function applyUrl(rel) {{
|
||||||
|
const next = new URL(rel, 'https://example.test');
|
||||||
|
Object.assign(window.location, {{href: next.href, pathname: next.pathname, search: next.search, hash: next.hash}});
|
||||||
|
}}
|
||||||
|
const calls = [];
|
||||||
|
global.window = {{location: {{}}, history: {{
|
||||||
|
pushState(_state, _title, url) {{ calls.push(['push', url]); applyUrl(url); }},
|
||||||
|
replaceState(_state, _title, url) {{ calls.push(['replace', url]); applyUrl(url); }},
|
||||||
|
}}}};
|
||||||
|
global.document = {{baseURI: 'https://example.test/app/'}};
|
||||||
|
applyUrl({initial_url!r});
|
||||||
|
globalThis._sessionUrlForSid = (0, eval)('(' + extractFunc(sessionsSrc, '_sessionUrlForSid') + ')');
|
||||||
|
globalThis._setActiveSessionUrl = (0, eval)('(' + extractFunc(sessionsSrc, '_setActiveSessionUrl') + ')');
|
||||||
|
globalThis._shouldStartFreshPwaChat = (0, eval)('(' + extractFunc(bootSrc, '_shouldStartFreshPwaChat') + ')');
|
||||||
|
const before = new URL(window.location.href);
|
||||||
|
const explicitSid = before.pathname.split('/session/')[1] || before.searchParams.get('session');
|
||||||
|
const shouldStartFresh = _shouldStartFreshPwaChat(before.searchParams.get('action'), explicitSid);
|
||||||
|
_setActiveSessionUrl(explicitSid);
|
||||||
|
console.log(JSON.stringify({{shouldStartFresh, calls, finalUrl: window.location.pathname + window.location.search + window.location.hash}}));
|
||||||
|
"""
|
||||||
|
payload = _run_node(source)
|
||||||
|
|
||||||
|
assert payload == {
|
||||||
|
"shouldStartFresh": False,
|
||||||
|
"calls": [["replace", expected_url]],
|
||||||
|
"finalUrl": expected_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_ordinary_session_navigation_keeps_push_state_semantics():
|
||||||
|
source = f"""
|
||||||
|
const sessionsSrc = {SESSIONS_JS!r};
|
||||||
|
function extractFunc(src, name) {{
|
||||||
|
const re = new RegExp('function\\\\s+' + name + '\\\\s*\\\\(');
|
||||||
|
const start = src.search(re);
|
||||||
|
let i = src.indexOf('{{', start), depth = 1; i++;
|
||||||
|
while (depth > 0) {{ if (src[i] === '{{') depth++; else if (src[i] === '}}') depth--; i++; }}
|
||||||
|
return src.slice(start, i);
|
||||||
|
}}
|
||||||
|
global.window = {{
|
||||||
|
location: {{href: 'https://example.test/app/?keep=1', pathname: '/app/', search: '?keep=1', hash: ''}},
|
||||||
|
history: {{pushState(_state, _title, url) {{ console.log(JSON.stringify({{method: 'push', url}})); }}, replaceState() {{ throw new Error('unexpected replace'); }}}},
|
||||||
|
}};
|
||||||
|
global.document = {{baseURI: 'https://example.test/app/'}};
|
||||||
|
globalThis._sessionUrlForSid = (0, eval)('(' + extractFunc(sessionsSrc, '_sessionUrlForSid') + ')');
|
||||||
|
globalThis._setActiveSessionUrl = (0, eval)('(' + extractFunc(sessionsSrc, '_setActiveSessionUrl') + ')');
|
||||||
|
_setActiveSessionUrl('session-123');
|
||||||
|
"""
|
||||||
|
assert _run_node(source) == {
|
||||||
|
"method": "push",
|
||||||
|
"url": "/app/session/session-123?keep=1",
|
||||||
|
}
|
||||||
Reference in new issue
Block a user