feat: finish Today from the lock screen (Closes #1032)
This commit is contained in:
1 parent
ca520d2eb6
commit
11395b655a
6 files changed
+211
-52
No files matched your search
@@ -138,11 +138,11 @@ async function dispatchMessage(data, ports = []) {{
|
||||
listeners.message({{ data, ports, waitUntil: promise => {{ pending = promise; }} }});
|
||||
if (pending) await pending;
|
||||
}}
|
||||
async function dispatchNotificationClick(route, action = '', notificationId = null, tag = null, url = null) {{
|
||||
async function dispatchNotificationClick(route, action = '', notificationId = null, tag = null, url = null, actionToken = '') {{
|
||||
let pending;
|
||||
listeners.notificationclick({{
|
||||
action,
|
||||
notification: {{tag: tag || ('stackchain-update-' + notificationId), data: {{route, notificationId, ...(url ? {{url}} : {{}})}}, close: () => {{ state.notificationClosed = true; }}}},
|
||||
notification: {{tag: tag || ('stackchain-update-' + notificationId), data: {{route, notificationId, ...(url ? {{url}} : {{}}), ...(actionToken ? {{actionToken}} : {{}})}}, close: () => {{ state.notificationClosed = true; }}}},
|
||||
waitUntil: promise => {{ pending = promise; }},
|
||||
}});
|
||||
if (pending) await pending;
|
||||
@@ -507,7 +507,7 @@ def test_authenticated_resume_rearms_a_previously_purged_worker_before_flushing(
|
||||
def test_active_today_message_replaces_one_privacy_safe_lock_screen_notification():
|
||||
result = run_worker_scenario(
|
||||
"""
|
||||
await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:true,identity:'secret/repo#42'});
|
||||
await dispatchMessage({type:'stackchain-today-lock-screen',active:true,running:true,identity:'secret/repo#42',actionToken:'opaque-token-1234567890'});
|
||||
process.stdout.write(JSON.stringify(state));
|
||||
"""
|
||||
)
|
||||
@@ -522,9 +522,9 @@ def test_active_today_message_replaces_one_privacy_safe_lock_screen_notification
|
||||
"silent": True,
|
||||
"actions": [
|
||||
{"action": "pause-today", "title": "Pause"},
|
||||
{"action": "open-today", "title": "Open Today"},
|
||||
{"action": "finish-today", "title": "Finish current"},
|
||||
],
|
||||
"data": {"route": "#/my-work/today"},
|
||||
"data": {"route": "#/my-work/today", "actionToken": "opaque-token-1234567890"},
|
||||
},
|
||||
}
|
||||
]
|
||||
@@ -589,6 +589,27 @@ def test_today_lock_screen_action_opens_a_validated_one_shot_route_when_closed()
|
||||
]
|
||||
|
||||
|
||||
def test_finish_today_lock_screen_action_forwards_only_its_opaque_token():
|
||||
result = run_worker_scenario(
|
||||
"""
|
||||
state.clientMessages=[];
|
||||
state.clientList=[{
|
||||
url:'https://forge.example/dashboard/#/my-work/today',
|
||||
postMessage:message=>state.clientMessages.push(message),
|
||||
focus:async()=>state.focused.push('today'),
|
||||
}];
|
||||
await dispatchNotificationClick('#/my-work/today','finish-today',null,'stackchain-today-session',null,'opaque-token-1234567890');
|
||||
process.stdout.write(JSON.stringify(state));
|
||||
"""
|
||||
)
|
||||
|
||||
assert result["clientMessages"] == [{
|
||||
"type": "stackchain-today-timer-action",
|
||||
"action": "complete",
|
||||
"actionToken": "opaque-token-1234567890",
|
||||
}]
|
||||
|
||||
|
||||
def test_background_mutation_abort_also_cancels_stalled_csrf_lookup():
|
||||
result = run_worker_scenario(
|
||||
"""
|
||||
|
||||
@@ -25,8 +25,10 @@ const NotificationRef = {get permission(){return permission;},requestPermission:
|
||||
const actions = [];
|
||||
const locationRef = {href:'https://forge.example/dashboard/#/my-work/today'};
|
||||
const historyRef = {replaceState:(_a,_b,url)=>{locationRef.href=new URL(url, locationRef.href).href;}};
|
||||
let tokenCounter = 0;
|
||||
const tokens = ['opaque-token-1234567890', 'new-opaque-token-0987654321'];
|
||||
(async()=>{
|
||||
const lockScreen = createTodayLockScreen({storage,getLogin:()=> 'Timmy',serviceWorker,NotificationRef,control,status,locationRef,historyRef,onAction:action=>actions.push(action)});
|
||||
const lockScreen = createTodayLockScreen({storage,getLogin:()=> 'Timmy',serviceWorker,NotificationRef,control,status,locationRef,historyRef,randomToken:()=> tokens[tokenCounter++],fingerprint:async (_token, identity)=>identity.startsWith('issue:') ? 'opaque-issue-fingerprint' : 'opaque-pull-fingerprint',onAction:(action, identity)=>actions.push([action, identity])});
|
||||
%SCENARIO%
|
||||
})().catch(error=>{console.error(error);process.exit(1)});
|
||||
""".replace("%SCENARIO%", scenario)
|
||||
@@ -43,11 +45,15 @@ def test_operator_opt_in_syncs_one_privacy_safe_session_notification():
|
||||
|
||||
assert result["checked"] is True
|
||||
assert result["status"] == "Lock-screen Today controls are on."
|
||||
assert result["stored"] == [["stackchain.today-lock-screen.v1.timmy", "1"]]
|
||||
assert result["stored"] == [
|
||||
["stackchain.today-lock-screen.v1.timmy", "1"],
|
||||
["stackchain.today-lock-screen-action.v1.timmy", '{"token":"opaque-token-1234567890","fingerprint":"opaque-issue-fingerprint"}'],
|
||||
]
|
||||
assert result["messages"][-1] == {
|
||||
"type": "stackchain-today-lock-screen",
|
||||
"active": True,
|
||||
"running": True,
|
||||
"actionToken": "opaque-token-1234567890",
|
||||
}
|
||||
assert "secret" not in json.dumps(result)
|
||||
|
||||
@@ -72,8 +78,8 @@ def test_valid_notification_action_is_consumed_once_and_removed_from_url():
|
||||
result = run_scenario(r"""
|
||||
values.set('stackchain.today-lock-screen.v1.timmy','1');
|
||||
locationRef.href='https://forge.example/dashboard/?today_timer_action=pause#/my-work/today';
|
||||
const consumed = lockScreen.consumeLaunchAction();
|
||||
const second = lockScreen.consumeLaunchAction();
|
||||
const consumed = await lockScreen.consumeLaunchAction();
|
||||
const second = await lockScreen.consumeLaunchAction();
|
||||
listeners['sw-message']({data:{type:'stackchain-today-timer-action',action:'resume'}});
|
||||
process.stdout.write(JSON.stringify({consumed,second,actions,href:locationRef.href}));
|
||||
""")
|
||||
@@ -81,7 +87,41 @@ def test_valid_notification_action_is_consumed_once_and_removed_from_url():
|
||||
assert result == {
|
||||
"consumed": True,
|
||||
"second": False,
|
||||
"actions": ["pause", "resume"],
|
||||
"actions": [["pause", None], ["resume", None]],
|
||||
"href": "https://forge.example/dashboard/#/my-work/today",
|
||||
}
|
||||
|
||||
|
||||
def test_finish_action_is_bound_to_the_exact_active_identity_and_consumed_once():
|
||||
result = run_scenario(r"""
|
||||
await lockScreen.enable();
|
||||
await lockScreen.sync({identity:'issue:private/repo:42:',running:true}, true);
|
||||
const first = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
|
||||
const replay = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
|
||||
await lockScreen.sync({identity:'pull:private/repo:9:',running:true}, true);
|
||||
const stale = await lockScreen.consumeAction('complete', 'opaque-token-1234567890');
|
||||
process.stdout.write(JSON.stringify({first,replay,stale,actions,messages,stored:[...values.entries()]}));
|
||||
""")
|
||||
|
||||
assert result["first"] is True
|
||||
assert result["replay"] is False
|
||||
assert result["stale"] is False
|
||||
assert result["actions"] == [["complete", "issue:private/repo:42:"]]
|
||||
assert all("private/repo" not in json.dumps(message) for message in result["messages"])
|
||||
|
||||
|
||||
def test_cold_launch_finish_action_is_removed_from_history_before_completion():
|
||||
result = run_scenario(r"""
|
||||
await lockScreen.enable();
|
||||
await lockScreen.sync({identity:'issue:private/repo:42:',running:true}, true);
|
||||
locationRef.href='https://forge.example/dashboard/?today_timer_action=complete&today_action_token=opaque-token-1234567890#/my-work/today';
|
||||
const consumed = await lockScreen.consumeLaunchAction();
|
||||
process.stdout.write(JSON.stringify({consumed,actions,href:locationRef.href}));
|
||||
""")
|
||||
|
||||
assert result == {
|
||||
"consumed": True,
|
||||
"actions": [["complete", "issue:private/repo:42:"]],
|
||||
"href": "https://forge.example/dashboard/#/my-work/today",
|
||||
}
|
||||
|
||||
@@ -111,7 +151,10 @@ def test_lock_screen_flow_is_wired_into_the_packaged_today_journey():
|
||||
assert '<script src="static/today-lock-screen.js"></script>' in index
|
||||
assert index.index('static/today-lock-screen.js') < index.index('static/dashboard.js')
|
||||
assert 'createTodayLockScreen({' in dashboard
|
||||
assert "action === 'pause' ? timer.pause() : timer.resume()" in dashboard
|
||||
assert "action === 'complete'" in dashboard
|
||||
assert "completeTodayItem(item)" in dashboard
|
||||
assert "todayLockScreen.consumeLaunchAction()" in dashboard
|
||||
assert dashboard.index("const completeTodayItem = createTodayCompletion({") < dashboard.index("todayLockScreen.consumeLaunchAction()")
|
||||
assert "todayLockScreen.sync(snapshot, workSession.checkpointed())" in dashboard
|
||||
assert "todayLockScreen.consumeLaunchAction()" in dashboard
|
||||
assert '"static/today-lock-screen.js"' in bundle
|
||||
Reference in new issue
Block a user