diff --git a/server/public/home.html b/server/public/home.html index ce7dfba..4445df7 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -2977,7 +2977,6 @@ async function nativeSaveFile(url, name, meta){ if(ph.ok) toast('Saved to Photos · Connect album'); else if(isMedia && ph.reason==='denied') toast('Saved to Files. Allow Photos access in Settings to also save to your gallery.'); else toast('Saved to Biz Connect · '+bzLibFolder(mime)); - if(isMedia && !ph.ok && ph.reason!=='off'){ try{ pdbg('photos-fail', { reason:ph.reason, err:ph.err||'' }); }catch(_){} } // telemetry: why Photos save failed }catch(e){ toast("Couldn't save the file"); } finally{ bzDlHide(box); } return true; // handled either way — never fall through to navigation @@ -3779,28 +3778,22 @@ function reportInstall(){ }catch(_){} } let _nativeTok=null; -// TEMP push diagnostics: mirror each step to the server so we can see where iOS registration fails -// without a Mac/device console. Remove once native push is confirmed working end-to-end. -function pdbg(step, extra){ try{ postJSON('/api/v1/push-debug', Object.assign({ step, t: Date.now() }, extra||{})).catch(()=>{}); }catch(_){} - try{ console.log('[push]', step, extra||''); }catch(_){} } async function setupNativePush(){ const PN=capPlugin('PushNotifications'); const plat=nativePlatform(); - const C=window.Capacitor; - pdbg('native-setup-enter', { plat, hasPlugin: !!PN, isNative: !!(C&&C.isNativePlatform&&C.isNativePlatform()), plugins: (C&&C.Plugins)?Object.keys(C.Plugins).join(','):'' }); - if(!PN || (plat!=='ios' && plat!=='android')){ pdbg('native-setup-skip', { plat, hasPlugin: !!PN }); return false; } // not a mobile native app + if(!PN || (plat!=='ios' && plat!=='android')) return false; // not a mobile native app try{ - PN.addListener('registration', async (t)=>{ const token=t&&t.value; pdbg('registration-event', { tokenLen: token?token.length:0 }); if(!token) return; _nativeTok=token; - try{ await postJSON('/api/v1/devices',{ platform:plat, token }); pushActive=true; pdbg('device-post-ok', { plat }); console.log('[push] native device registered'); } - catch(e){ pdbg('device-post-fail', { err:String((e&&e.message)||e) }); console.warn('[push] device register failed:', e); } }); - PN.addListener('registrationError', (e)=>{ pdbg('registration-error', { err:(e&&(e.error||e.message))||JSON.stringify(e) }); console.warn('[push] native registration error:', e); }); + PN.addListener('registration', async (t)=>{ const token=t&&t.value; if(!token) return; _nativeTok=token; + try{ await postJSON('/api/v1/devices',{ platform:plat, token }); pushActive=true; console.log('[push] native device registered'); } + catch(e){ console.warn('[push] device register failed:', e); } }); + PN.addListener('registrationError', (e)=>{ console.warn('[push] native registration error:', e); }); // Tapping an OS notification opens the relevant chat (payload carries kind+id). PN.addListener('pushNotificationActionPerformed', (a)=>{ try{ const d=(a&&a.notification&&a.notification.data)||{}; if(d.id) selectChat(d.kind||'dm', d.id); }catch(_){} }); - let perm=await PN.checkPermissions(); pdbg('perm-check', { receive: perm&&perm.receive }); - if(perm.receive!=='granted'){ perm=await PN.requestPermissions(); pdbg('perm-after-request', { receive: perm&&perm.receive }); } - if(perm.receive!=='granted'){ pdbg('perm-not-granted', { receive: perm&&perm.receive }); console.log('[push] native permission not granted'); return true; } - await PN.register(); pdbg('register-called'); + let perm=await PN.checkPermissions(); + if(perm.receive!=='granted'){ perm=await PN.requestPermissions(); } + if(perm.receive!=='granted'){ console.log('[push] native permission not granted'); return true; } + await PN.register(); console.log('[push] native push registered'); - }catch(e){ pdbg('native-setup-exception', { err:String((e&&e.message)||e) }); console.warn('[push] native push setup failed:', e); } + }catch(e){ console.warn('[push] native push setup failed:', e); } return true; // handled the native path (skip Web Push regardless of outcome) } async function setupPush(){ @@ -3865,7 +3858,6 @@ async function setupNativeCall(){ // the call was answered (native media bypasses our WS, so the server can't learn it otherwise) and clear // the in-app invite. Track answered rooms so end vs decline is signalled correctly. NC.addListener('answerCall', (d)=>{ try{ - pdbg('nc-answer', { room:(d&&d.room)||'', hasUrl:!!(d&&d.livekitUrl), hasToken:!!(d&&d.livekitToken) }); if(!d||!d.room) return; _nativeAnswered.add(d.room); dismissCallInvite(d.room); @@ -3875,18 +3867,17 @@ async function setupNativeCall(){ const isGroup=(d.kind==='group'); meetReturn = isGroup ? { kind:'group', id:d.groupId } : { kind:'dm', id:d.callerId }; switchTab('meeting'); enterMeeting(d.room, false, { native:true, uuid:d.callUUID }); - }catch(e){ pdbg('nc-answer-err', { err:String((e&&e.message)||e) }); } }); + }catch(e){ console.warn('[callkit] answerCall failed:', e); } }); // Enforce the muted-by-default rule against the plugin's actual mic once the room connects (belt-and-braces // for builds whose plugin still connects the mic live). meetMic is the source of truth for the mic button. - NC.addListener('callConnected', ()=>{ pdbg('nc-connected'); if(meetNative){ try{ NC.setMuted({ muted:!meetMic }); }catch(_){} } }); - NC.addListener('audioActivated', (e)=>{ pdbg('nc-audio', { ok:!!(e&&e.ok), err:(e&&e.error)||'', route:(e&&e.route)||'' }); }); // CallKit audio-session activation + output route (debug audio) - NC.addListener('callError', (e)=>{ pdbg('nc-error', { err:(e&&e.error)||'' }); }); + NC.addListener('callConnected', ()=>{ if(meetNative){ try{ NC.setMuted({ muted:!meetMic }); }catch(_){} } }); + NC.addListener('callError', (e)=>{ console.warn('[callkit] call error:', (e&&e.error)||''); }); // Muted from the CallKit system UI → reflect it in the meeting window's mic button. NC.addListener('setMuted', (e)=>{ if(!e||meetState!=='call'||!meetNative) return; meetMic=!e.muted; try{ updateMicBtn(); setTileMute('__local', !meetMic); meetSend({type:'meeting-state', muted:!meetMic, camOff:!meetCam}); }catch(_){} }); // Ended on the CallKit system screen / plugin ended on remote hang-up. Leave the meeting window too (guarded // so our own in-app hang-up, which already called the plugin, doesn't loop). Then tell the server. NC.addListener('endCall', (d)=>{ try{ - const room=d&&d.room; pdbg('nc-end', { room:room||'', answered:_nativeAnswered.has(room) }); + const room=d&&d.room; if(!room) return; dismissCallInvite(room); if(_ncEnding){ _ncEnding=false; } @@ -3901,7 +3892,6 @@ async function setupNativeCall(){ async function callkitReportOutgoing(uuid, room, kind, peerName, hasVideo){ const NC=nativeCallPlugin(); if(!NC||!uuid) return; let tk={}; try{ tk=await postJSON('/api/meetings/token',{ room }); }catch(_){} - pdbg('nc-outgoing', { room, hasUrl:!!(tk&&tk.url), hasToken:!!(tk&&tk.token) }); try{ NC.reportOutgoingCall({ callUUID:uuid, room, kind:kind||'dm', peerName:peerName||'Call', hasVideo:!!hasVideo, url:tk.url||'', token:tk.token||'' }); }catch(_){} } // End the CallKit call (remote hung up / we left / call ended). Safe no-op off-CallKit. diff --git a/server/routes.js b/server/routes.js index 55fd644..9949b9e 100644 --- a/server/routes.js +++ b/server/routes.js @@ -412,18 +412,6 @@ route('POST', '/api/devices/remove', async (req, res) => { json(res, 200, { ok: true }); }); -// --- Push diagnostics (temporary): the native app reports each step of push setup here so we can see -// WHERE iOS registration fails without a Mac/device console. Best-effort; logs and returns 200. --- -route('POST', '/api/push-debug', async (req, res) => { - try { - const b = await readBody(req); - let uid = 'anon'; try { const u = await currentUser(req); if (u) uid = u.id; } catch (_) {} - const line = (typeof b === 'object' ? JSON.stringify(b) : String(b)).slice(0, 800); - console.log('[push-debug] user=' + uid + ' ' + line); - } catch (_) {} - json(res, 200, { ok: true }); -}); - // --- App install telemetry: records each install and, once the user signs in, who's using it. --- route('POST', '/api/telemetry/install', async (req, res) => { const { installId, platform, appVersion, os } = await readBody(req);