// ==UserScript== // @name SLINK PDA Dashboard // @namespace Considious [3853023] // @version 0.4.140 // @description Mobile-first SLINK dashboard for Torn PDA with Combat, Efficiency, Quality of Life, and shared permissions. // @author Considious [3853023] // @updateURL https://raw.githubusercontent.com/Considious/Torn-Scripts/main/SLINK-PDA/SLINK_PDA_Dashboard.user.js // @downloadURL https://raw.githubusercontent.com/Considious/Torn-Scripts/main/SLINK-PDA/SLINK_PDA_Dashboard.user.js // @match https://www.torn.com/* // @grant GM_addElement // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_notification // @connect api.torn.com // @connect api.giphy.com // @connect chatsticker.com // @connect raw.githubusercontent.com // @connect cdn.jsdelivr.net // @connect ffscouter.com // @connect weav3r.dev // @connect yata.yt // @connect prombot.co.uk // @connect torn-intel.com // @connect droqsdb.com // @connect slinkyleveling.richard-johnson554.workers.dev // @connect slinkcontributionworker.richard-johnson554.workers.dev // @connect slinkwarworker.richard-johnson554.workers.dev // @connect slinkmuggingworker.richard-johnson554.workers.dev // @run-at document-end // ==/UserScript== (async()=>{'use strict';const e='https://raw.githubusercontent.com/Considious/Torn-Scripts/main/SLINK-PDA/',t=e+'source-manifest.json',r=String('###PDA-APIKEY###').trim(),o='slink-pda-runtime-load-error',a='slink-pda-dashboard-host',n='slink-pda-dashboard:data:v1',s='slink-pda-dashboard:data:mirror:v1',i='slink-pda-dashboard:own-battle-stats:v1',l='slink-pda-dashboard:ui:v1',c='slink-pda-dashboard:alert-snoozes:v1',u='slink-pda-dashboard:targets:v1',d=new Set([n,i,l,c,u]),f='slink-pda-dashboard:runtime-cache:v1',g='slink-pda-dashboard:runtime-cache-retry:v1';let h=!1,w=null;const residentBridge=globalThis.__SLINK_PDA_STORAGE_BRIDGE__;const m=residentBridge?.protocol===1&&residentBridge.document===document&&typeof residentBridge.channel==='string'?residentBridge.channel:'slink-pda-storage-write:'+(globalThis.crypto?.randomUUID?.()||String(Date.now())+':'+Math.random());function showFailure(e){const t=String(e?.message||e||'Unknown startup error');console.error('SLINK PDA runtime failed to load:',e),document.getElementById(o)?.remove();const r=document.createElement('button');r.id=o,r.type='button',r.textContent='SLINK could not load. '+t.replace(/([?&](?:key|api_key|token)=)[^&#\\s]+/gi,'$1[redacted]').slice(0,350)+' Torn is unaffected — tap to retry',r.title='Startup files could not finish loading. Check your connection, tap Retry once, then update SLINK if it continues. Saved settings have not been cleared. [SLINK_RUNTIME_LOAD]',r.style.cssText='position:fixed;right:10px;bottom:76px;z-index:2147483647;max-width:calc(100vw - 20px);white-space:normal;overflow-wrap:anywhere;padding:10px 12px;border:2px solid #111;border-radius:8px;background:#b91c1c;color:#fff;font:bold 12px Arial,sans-serif',r.addEventListener('click',()=>{r.remove(),loadRuntime(!0)}),(document.body||document.documentElement).appendChild(r)}function isRecord(value){return Boolean(value&&'object'==typeof value&&!Array.isArray(value))}function hasData(value){return Boolean(isRecord(value)&&Object.keys(value).length)}function savedAt(value){return Math.max(0,Number(isRecord(value)?value._savedAt||value.savedAt:0)||0)}function writeMirror(value){if(isRecord(value))try{globalThis.localStorage?.setItem(s,JSON.stringify({schema:1,savedAt:Date.now(),data:value})),globalThis.localStorage?.setItem(n,JSON.stringify(value))}catch{}}async function readPersistedData(){readPersistedData.failed=!1;let primary=null,primaryFailed=!1;try{const e=nativeStorage();if(e){const value=await e.get(n,null);if(hasData(value))return writeMirror(value),value}const value=await gmRead(n,{});isRecord(value)&&(primary=value)}catch{primaryFailed=!0,readPersistedData.failed=!0}const mirror=function(){try{const e=JSON.parse(globalThis.localStorage?.getItem(s)||'null');return isRecord(e?.data)?e.data:isRecord(e)?e:null}catch{return null}}();let selected=hasData(primary)?primary:mirror||{};return hasData(mirror)&&savedAt(mirror)>savedAt(selected)&&(selected=mirror),hasData(selected)&&writeMirror(selected),selected!==mirror||selected===primary||primaryFailed||await gmWrite(n,selected),selected}async function collectBootstrap(){const[data,e]=await Promise.all([readPersistedData(),gmRead(i,null)]),t=u;let r;try{if(r=await targetJournal(),readPersistedData.failed&&!r.ops.some(e=>'migration'===e.kind))throw Error('Legacy target storage could not be read.')}catch{r={ops:[],error:'Target storage is unavailable. Reload to retry; saved targets have not been changed.'}}let o=null;try{o=JSON.parse(globalThis.localStorage?.getItem(i)||'null')}catch{}const a=[e,o,data?.caches?.ownBattleStats].filter(value=>isRecord(value)&&Number(value.total)>0).sort((e,t)=>Number(t.checkedAt||0)-Number(e.checkedAt||0))[0]||null;return{[n]:data,[i]:a,[t]:r,storageWriteEvent:m,[l]:await gmRead(l,{}),[c]:await gmRead(c,{})}}function nativeStorage(){ const store=typeof PDA_storage==='object'?PDA_storage:globalThis.PDA_storage; if(!store||typeof store.get!=='function'||typeof store.set!=='function')return null; const key=value=>{if(typeof value!=='string'||!value)throw Error('SLINK storage requires a non-empty key.');return value}; const castError=error=>/type ['"]Null['"] is not a subtype of type ['"]String['"]/i.test(String(error?.code||error?.message||error)); const failed=(operation,error)=>new Error('PDA native storage '+operation+' failed: '+String(error?.message||error?.code||error||'No acknowledgement')); async function getMany(keys){ keys.forEach(key); try { const values=typeof store.getMany==='function'?await store.getMany(keys):Object.fromEntries(await Promise.all(keys.map(async k=>[k,await store.get(k,null)]))); if(!isRecord(values))throw Error('PDA returned an unreadable storage result.'); return values; }catch(error){ // On this exact bridge cast, read a single requested record through // the documented single-key API. A full-store read must not be the only // recovery path: one unreadable unrelated record can make loadAll fail. if(castError(error)){ let singleError; if(keys.length===1){ try{return {[keys[0]]:await store.get(keys[0],null)};} catch(retry){singleError=retry;} } let allError; if(typeof store.loadAll==='function'){ try{const all=await store.loadAll();if(!isRecord(all))throw Error('PDA returned an unreadable storage result.');return Object.fromEntries(keys.map(k=>[k,all[k]??null]));} catch(retry){allError=retry;} } const describe=value=>!value?'unavailable':castError(value)?'Null/String':'other failure'; const kind=keys.every(k=>k===n)?'settings':keys.every(k=>k===i)?'battle stats':keys.every(k=>k===l)?'window preferences':keys.every(k=>k===c)?'alert snoozes':'requested records'; const failure=failed(allError?'loadAll read':'read',allError||singleError||error); failure.message+=' [SLINK_NATIVE_READ: record='+kind+'; batch='+describe(error)+'; single='+(keys.length===1?describe(singleError):'not attempted for bulk read')+'; all='+describe(allError)+']'; throw failure; } throw failed('read',error); } } async function setMany(values){ if(!isRecord(values))throw Error('SLINK storage requires a record.'); const keys=Object.keys(values);keys.forEach(key); try{ if(typeof store.setMany==='function')await store.setMany(values); else for(const k of keys)await store.set(k,values[k]); }catch(error){ if(castError(error)&&typeof store.setMany==='function'){ // Retry only this structural bridge error with explicit single-key writes. // Quota/network errors do not trigger retries or another storage backend. try{for(const k of keys)await store.set(k,values[k]);} catch(retry){throw failed('single-key write',retry);} }else throw failed('write',error); } // The optional downloaded-code cache is disposable and can be large. Verify // user records, not an extra full cache read on every page startup. if(typeof f==='string'&&keys.every(k=>k===f))return; const saved=await getMany(keys); for(const k of keys)if(JSON.stringify(saved[k])!==JSON.stringify(values[k]))throw Error('PDA native storage did not retain the '+(k===n?'settings':k===i?'battle stats':k===l?'window preferences':k===c?'alert snoozes':'target record')+' write. Keep this page open and retry; existing stored data was not cleared.'); } return { get:async(k,fallback)=>{key(k);return (await getMany([k]))[k]??fallback}, set:(k,value)=>{key(k);return setMany({[k]:value})}, getMany,setMany, ...(typeof store.list==='function'?{list:()=>store.list()}:{}), ...(typeof store.loadAll==='function'?{loadAll:()=>store.loadAll()}:{}), ...(typeof store.delete==='function'?{delete:k=>store.delete(key(k))}:{}) }; }async function targetJournal(request={}){const e=nativeStorage(),t=u+':op:';async function read(){let r;if(e)if('function'==typeof e.list&&'function'==typeof e.getMany){const o=(await e.list()).filter(key=>key.startsWith(t));if(r=o.length?await e.getMany(o):{},o.some(key=>null==r?.[key]))throw Error('Target journal read incomplete.')}else{if('function'!=typeof e.loadAll)throw Error('Native target storage enumeration unavailable.');r=await e.loadAll()}else{r={};const e=globalThis.localStorage;if(!e||'function'!=typeof e.key)throw Error('Target storage unavailable.');for(let o=0;okey.startsWith(t));if(o.some(([key,e])=>!isRecord(e)||1!==e.schema||key!==t+e.id))throw Error('Target journal is damaged or unsupported.');return Object.fromEntries(o)}const r=await read(),o={};if(!request.readOnly)for(const e of request.ops||[]){if(!isRecord(e)||1!==e.schema||!/^[a-zA-Z0-9:_-]{1,160}$/.test(e.id||'')||!['seed','add','patch','remove','migration'].includes(e.kind))throw Error('Invalid target operation.');const key=t+e.id;if(r[key]&&JSON.stringify(r[key])!==JSON.stringify(e))throw Error('Target operation identity collision.');r[key]||(o[key]=e)}if(Object.keys(o).length)if(e&&'function'==typeof e.setMany)await e.setMany(o);else for(const[key,t]of Object.entries(o))e?await e.set(key,t):globalThis.localStorage.setItem(key,JSON.stringify(t));const a=Object.keys(o).length?await read():r;for(const e of request.readOnly?[]:request.ops||[])if(JSON.stringify(a[t+e.id])!==JSON.stringify(e))throw Error('Target journal write was not retained.');return{ops:Object.values(a)}}function mergeChanges(e,current,t){if(JSON.stringify(e)===JSON.stringify(current))return t;if(!isRecord(e)||!isRecord(current)||!isRecord(t))return current;const r={...t};for(const key of new Set([...Object.keys(e),...Object.keys(current)]))['__proto__','constructor','prototype'].includes(key)||(Object.hasOwn(current,key)?r[key]=mergeChanges(e[key],current[key],t[key]):Object.hasOwn(e,key)&&delete r[key]);return r}async function gmRead(key,e=null){const t=nativeStorage();if(t){const value=await t.get(key,null);if(null!=value)return value}let r;try{r='function'==typeof globalThis.GM_getValue?await Promise.resolve(globalThis.GM_getValue(key,e)):e}catch(t){if(d.has(key))throw t;return e}return t&&isRecord(r)&&Object.keys(r).length&&d.has(key)&&null==await t.get(key,null)&&await t.set(key,r),r}async function gmWrite(key,value){const e=nativeStorage();if(e)await e.set(key,value);else{if('function'!=typeof globalThis.GM_setValue)throw Error('Browser storage unavailable.');if(await Promise.resolve(globalThis.GM_setValue(key,value)),'function'==typeof globalThis.GM_getValue&&JSON.stringify(await globalThis.GM_getValue(key,null))!==JSON.stringify(value))throw Error('Browser storage write was not retained.')}}function validBundle(value){return Boolean(isRecord(value)&&1===value.schema&&[2,3].includes(value.storageProtocol)&&'string'==typeof value.runtimeSource&&value.runtimeSource.length>1e3&&Array.isArray(value.addonSources)&&value.addonSources.every(source=>'string'==typeof source&&source.length>100))}async function requestSource(e){if('function'==typeof globalThis.PDA_httpGet){const t=await globalThis.PDA_httpGet(e),r=Number(t?.status||t?.statusCode||200);if(r<200||r>=300)throw new Error('SLINK source download returned HTTP '+r+'.');const o=String(t?.data??t?.responseText??'');if(t?.error||!o.trim()||/^error[, :]|^Exception:/i.test(o))throw new Error('SLINK source connection failed.');return o}if('function'==typeof globalThis.GM_xmlhttpRequest)return await new Promise((t,r)=>{globalThis.GM_xmlhttpRequest({method:'GET',url:e,timeout:2e4,onload:e=>e.status>=200&&e.status<300?t(String(e.responseText||'')):r(new Error('SLINK source download returned HTTP '+e.status+'.')),onerror:()=>r(new Error('SLINK source download failed.')),ontimeout:()=>r(new Error('SLINK source download timed out.'))})});if('function'==typeof globalThis.fetch){const t='function'==typeof AbortController?new AbortController:null,r=globalThis.setTimeout(()=>t?.abort(),8e3);try{const r=await globalThis.fetch(e,{signal:t?.signal,credentials:'omit'});if(!r.ok)throw new Error('SLINK source download returned HTTP '+r.status+'.');return await r.text()}finally{globalThis.clearTimeout(r)}}throw new Error('No SLINK source download transport is available.')}async function fetchSource(e){const mirror=e.replace('https://raw.githubusercontent.com/Considious/Torn-Scripts/','https://cdn.jsdelivr.net/gh/Considious/Torn-Scripts@'),t=h?[mirror,e]:[e,mirror]; // Only the script's public source URL is mirrored; never API keys or user data. let r;for(const e of[...new Set(t)]){let t;try{const r=await Promise.race([requestSource(e),new Promise((e,r)=>{t=globalThis.setTimeout(()=>r(new Error('SLINK source download timed out.')),9e3)})]);if(!String(r).trim())throw new Error('SLINK source download was empty.');return h=e===mirror,r}catch(e){r=e}finally{globalThis.clearTimeout(t)}}throw r||new Error('SLINK source download unavailable.')}function safeSourcePath(value){const e=String(value||'').trim();if(!/^src\/[A-Za-z0-9._/-]+$/.test(e)||e.includes('..'))throw new Error('The SLINK source manifest contains an invalid path.');return e}function parseSourceManifest(response){let manifest;try{manifest=JSON.parse(response)}catch{throw new Error('The SLINK source manifest was not valid JSON.')}if('direct-source'!==manifest?.mode||![2,3].includes(manifest.storageProtocol)||!Array.isArray(manifest.fragments)||!manifest.fragments.length)throw new Error('The SLINK source manifest is incomplete.');return manifest}async function readSourceManifest(timeoutMs=0){const request=fetchSource(t+'?t='+Date.now());if(!timeoutMs)return parseSourceManifest(await request);let timer;try{return parseSourceManifest(await Promise.race([request,new Promise((resolve,reject)=>{timer=globalThis.setTimeout(()=>reject(new Error('SLINK manifest check timed out.')),timeoutMs)})]))}finally{globalThis.clearTimeout(timer)}}async function fetchBundle(a){const r=Date.now();a=a||await readSourceManifest();const n=a.fragments.map(safeSourcePath),s=(a.startupAddons||[]).map(e=>safeSourcePath(e?.source)),revision=String(a.revision||'unversioned'),i='?v='+encodeURIComponent(revision)+'&t='+r,l=[...n,...s],sources=new Array(l.length);let c=0,u=!1;await Promise.all(Array.from({length:Math.min(2,l.length)},async()=>{for(;!u&&csource.length<100))throw new Error('A SLINK runtime source file was empty or incomplete.');if(addonSources.some(source=>source.length<100))throw new Error('A SLINK startup add-on was empty or incomplete.');const runtimeSource=d.join(''),f=await async function(runtimeSource,addonSources){const e=[runtimeSource,...addonSources].join('\n;\n');try{const t=(new TextEncoder).encode(e),r=await globalThis.crypto.subtle.digest('SHA-256',t);return[...new Uint8Array(r)].map(e=>e.toString(16).padStart(2,'0')).join('')}catch{let t=2166136261;for(let r=0;r>>0).toString(16)}}(runtimeSource,addonSources);return{schema:1,storageProtocol:a.storageProtocol,revision:revision,signature:f,runtimeSource:runtimeSource,addonSources:addonSources,savedAt:Date.now()}}async function waitUntilReady(){const e=Date.now()+2e4;for(;Date.now()globalThis.setTimeout(e,100))}throw new Error('Runtime did not signal readiness within 20 seconds.')} async function executeBrowserSource(source){ const add=typeof GM_addElement==='function'?GM_addElement:globalThis.GM?.addElement?.bind(globalThis.GM); if(typeof add!=='function')throw Error('Desktop userscript execution requires GM_addElement support. Update this script and use a userscript manager that supports it. [SLINK_BROWSER_EXECUTION_UNAVAILABLE]'); const eventName=m+':browser',executionEvent=m+':executed'; const request=typeof GM_xmlhttpRequest==='function'?GM_xmlhttpRequest:globalThis.GM_xmlhttpRequest; const notify=typeof GM_notification==='function'?GM_notification:globalThis.GM_notification; const menu=typeof GM_registerMenuCommand==='function'?GM_registerMenuCommand:globalThis.GM_registerMenuCommand; const allowed=new Set(['https://api.torn.com','https://ffscouter.com','https://weav3r.dev','https://yata.yt','https://prombot.co.uk:8443','https://torn-intel.com','https://droqsdb.com','https://api.giphy.com','https://chatsticker.com','https://slinkcontributionworker.richard-johnson554.workers.dev','https://slinkyleveling.richard-johnson554.workers.dev','https://slinkwarworker.richard-johnson554.workers.dev','https://slinkmuggingworker.richard-johnson554.workers.dev']); // Keep GM privileges in the installed userscript context. The page receives // only this bounded service transport, not unrestricted GM APIs. if(!executeBrowserSource.bound){ executeBrowserSource.bound=true; const reply=value=>document.dispatchEvent(new CustomEvent(eventName+':ack',{detail:JSON.stringify(value)})); document.addEventListener(eventName,event=>{ let job;try{job=JSON.parse(String(event.detail||''));}catch{return;} if(!job||typeof job.id!=='number')return; if(job.action==='menu'){ if(typeof menu==='function'&&['Open SLINK PDA Dashboard','Reset SLINK PDA layout'].includes(job.title))menu(job.title,()=>reply({id:job.id,event:'click'})); return; } if(job.action==='notify'){ if(typeof notify==='function')notify({...job.options,onclick:()=>reply({id:job.id,event:'click'})}); return; } if(job.action!=='http'||typeof request!=='function')return reply({id:job.id,error:'Userscript HTTP transport is unavailable.'}); let url;try{url=new URL(job.url);}catch{return reply({id:job.id,error:'Invalid SLINK service URL.'});} const method=String(job.method||'GET').toUpperCase(); if(!allowed.has(url.origin)||url.username||url.password||!['GET','POST'].includes(method)||(method==='POST'&&!url.hostname.endsWith('.workers.dev')))return reply({id:job.id,error:'SLINK blocked an unsupported service request.'}); try{request({method,url:url.href,headers:job.headers||{},data:job.data,anonymous:true,timeout:30000, onload:r=>reply({id:job.id,response:{status:r.status,statusText:r.statusText||'',responseText:r.responseText||'',responseHeaders:r.responseHeaders||''}}), onerror:()=>reply({id:job.id,error:'The userscript manager could not reach the service.'}), ontimeout:()=>reply({id:job.id,timeout:true,error:'The service request timed out.'})});}catch{reply({id:job.id,error:'The userscript manager rejected the service request before an HTTP response was received. [SLINK_BROWSER_REQUEST_FAILED]'});} }); } // Installed only in the browser execution branch. Native PDA execution and // its scoped storage/HTTP objects are untouched. const prelude='globalThis.SLINK_PDA_BROWSER_TRANSPORT||=('+function(name){ const pending=new Map();let sequence=0; document.addEventListener(name+':ack',event=>{let reply;try{reply=JSON.parse(String(event.detail||''));}catch{return;}const job=pending.get(reply.id);if(!job)return;if(reply.event==='click'){job.click?.();return;}clearTimeout(job.timer);pending.delete(reply.id);if(reply.error)(reply.timeout?job.options.ontimeout:job.options.onerror)?.({error:reply.error});else job.options.onload?.(reply.response);}); const send=job=>document.dispatchEvent(new CustomEvent(name,{detail:JSON.stringify(job)})); return { request(options){const id=++sequence,job={options,timer:setTimeout(()=>{pending.delete(id);options.ontimeout?.();},31000)};pending.set(id,job);send({id,action:'http',method:options.method,url:options.url,headers:options.headers,data:options.data});return {abort(){clearTimeout(job.timer);pending.delete(id);}};}, menu(title,click){const id=++sequence;pending.set(id,{click});send({id,action:'menu',title});}, notify(options){const id=++sequence;pending.set(id,{click:options.onclick,timer:setTimeout(()=>pending.delete(id),Math.max(1000,Math.min(60000,Number(options.timeout)||12000))+1000)});send({id,action:'notify',options:{title:String(options.title||''),text:String(options.text||''),tag:options.tag,timeout:options.timeout,silent:options.silent}});} }; }.toString()+')('+JSON.stringify(eventName)+');'; const code='try {\n'+prelude+'\n'+source+'\ndocument.dispatchEvent(new CustomEvent('+JSON.stringify(executionEvent)+',{detail:JSON.stringify({ok:true})}));\n}catch(error){document.dispatchEvent(new CustomEvent('+JSON.stringify(executionEvent)+',{detail:JSON.stringify({ok:false,message:String(error.message||error)})}));}\n//# sourceURL=SLINK_PDA_Browser_Bundle.js'; await new Promise((resolve,reject)=>{ let node;const finish=error=>{clearTimeout(timer);document.removeEventListener(executionEvent,done);node?.remove?.();error?reject(error):resolve();}; const done=event=>{let result;try{result=JSON.parse(String(event.detail||''));}catch{return;}finish(result.ok?null:Error('SLINK browser runtime failed: '+result.message));}; const timer=setTimeout(()=>finish(Error('The userscript manager did not confirm runtime execution. [SLINK_BROWSER_EXECUTION_UNCONFIRMED]')),20000); document.addEventListener(executionEvent,done); try{Promise.resolve(add('script',{textContent:code})).then(element=>{node=element;if(!node)finish(Error('The userscript manager could not insert the SLINK runtime. [SLINK_BROWSER_EXECUTION_UNAVAILABLE]'));else node.remove?.();},finish);}catch(error){finish(error);} }); } async function executeBundle(e){const t=document.getElementById(a),o='true'===t?.dataset?.slinkReady;if(o&&'true'===t?.dataset?.slinkAddonsReady)return;t&&(t.dataset.slinkAddonsReady='false');const sources=['globalThis.__SLINK_PDA_ADDONS_READY__=false;'];if(!o){const t='globalThis.__SLINK_PDA_BOOTSTRAP__='+JSON.stringify({...await collectBootstrap(),sourceRevision:e.revision})+';';sources.push(t+'\n'+e.runtimeSource.split('###PDA-APIKEY###').join(r)+'\n//# sourceURL=SLINK_PDA_Direct_Runtime.js\n')}e.addonSources.forEach((source,e)=>{sources.push(source.split('###PDA-APIKEY###').join(r)+'\n//# sourceURL=SLINK_PDA_Direct_Addon_'+(e+1)+'.js\n')}),sources.push(`globalThis.__SLINK_PDA_ADDONS_READY__=true;\n(() => {\nconst slinkReadyHost=document.getElementById(${JSON.stringify(a)});\nif(slinkReadyHost) slinkReadyHost.dataset.slinkAddonsReady='true';\n})();`),await async function(source){'function'==typeof globalThis.PDA_evaluateJavascript?await globalThis.PDA_evaluateJavascript(source):await executeBrowserSource(source)}(sources.join('\n;\n')),await waitUntilReady()}function loadRuntime(force=!1){return w||(w=(async function(force=!1){const cached=await gmRead(f,null).catch(()=>null),retry='1'===globalThis.sessionStorage?.getItem(g);if(retry){if(globalThis.sessionStorage?.removeItem(g),!validBundle(cached))return showFailure(new Error('No known-working SLINK runtime is cached yet.'));try{return void await executeBundle(cached)}catch(error){return showFailure(error)}}let bundle=null;if(!force&&validBundle(cached)){try{const manifest=await readSourceManifest(2500);if(cached.revision===String(manifest.revision||'unversioned')&&cached.storageProtocol===manifest.storageProtocol){await executeBundle(cached);return}bundle=await fetchBundle(manifest)}catch(error){console.warn('[SLINK PDA] Live source check failed; using the last-known-working runtime.',error);try{await executeBundle(cached);return}catch(cachedError){return showFailure(cachedError)}}}if(!bundle)try{bundle=await fetchBundle()}catch(error){if(!validBundle(cached))return showFailure(error);console.warn('[SLINK PDA] Source download failed; using the last-known-working runtime.',error);try{return void await executeBundle(cached)}catch(cachedError){return showFailure(cachedError)}}try{await executeBundle(bundle);try{await gmWrite(f,bundle)}catch{console.warn('[SLINK PDA] Runtime cache not saved; running source remains active.')}}catch(error){if(validBundle(cached)&&cached.signature!==bundle.signature)try{return globalThis.sessionStorage?.setItem(g,'1'),void globalThis.location.reload()}catch{}showFailure(error)}})(force).finally(()=>{w=null}),w)}!function(){let e=Promise.resolve();const resident=globalThis.__SLINK_PDA_STORAGE_BRIDGE__;if(resident?.protocol===1&&resident.document===document&&resident.channel===m)return; let storagePaused=false,nativeOutstanding=false,storageReadFailure=''; const storageLabel=()=>nativeStorage()?"PDA's native storage":"The userscript's storage"; const pausedStorageError=()=>new Error(storageLabel()+(nativeOutstanding ? " is still not responding. Further saves are paused, not queued. Keep this page open; retry local saving only after storage responds. [SLINK_STORAGE_NATIVE_STALLED]" : " failed. Automatic saves are paused; your changes remain on this page. Use Retry saving local changes in Access to try again. [SLINK_STORAGE_PAUSED]")+(storageReadFailure?' '+storageReadFailure:'')); function runStorageJob(run){ if(storagePaused)return Promise.reject(pausedStorageError()); nativeOutstanding=true;storageReadFailure=''; const operation=Promise.resolve().then(run); return new Promise((resolve,reject)=>{ let expired=false; const timer=setTimeout(()=>{ expired=true;storagePaused=true; reject(new Error(storageLabel()+" did not complete this save within 25 seconds. Further saves are paused, not queued. [SLINK_STORAGE_NATIVE_TIMEOUT]")); },25000); operation.then(value=>{ clearTimeout(timer);nativeOutstanding=false; if(expired)storagePaused=false; resolve(value); },error=>{ clearTimeout(timer);nativeOutstanding=false; const readContext=String(error?.message||error).match(/\[SLINK_NATIVE_READ:[^\]]*\]/); if(readContext)storageReadFailure=readContext[0]; if(expired||/type ['"]Null['"] is not a subtype of type ['"]String['"]|QuotaExceeded|quota exceeded/i.test(String(error?.message||error)))storagePaused=true; reject(error); }); }); } const acknowledge=(payload,e,t='',value,phase)=>{try{document.dispatchEvent(new CustomEvent(m+':ack',{detail:JSON.stringify({id:payload.id,ok:e,backend:nativeStorage()?'pda':'browser',error:t,value:value,phase:phase})}))}catch{}};document.addEventListener(m,event=>{let payload;try{payload=JSON.parse(String(event?.detail||''))}catch{return}if(isRecord(payload)&&payload.action==='retry'){if(!nativeOutstanding)storagePaused=false;return;}if(isRecord(payload)&&d.has(payload.key)&&(!payload.remove||payload.key===n)&&(payload.remove||isRecord(payload.value))){if(storagePaused){acknowledge(payload,false,pausedStorageError().message);return;}if(!payload.remove&&payload.key!==u)if(payload.key===n)writeMirror(payload.value);else try{globalThis.localStorage?.setItem(payload.key,JSON.stringify(payload.value))}catch{}if(payload.protocol===2)acknowledge(payload,false,'',undefined,'queued');e=e.catch(()=>{}).then(()=>runStorageJob(async()=>{if(payload.protocol===2)acknowledge(payload,false,'',undefined,'working');if(payload.key===u)return targetJournal(payload.value);if(payload.remove){const e=nativeStorage();if(e){if('function'!=typeof e.delete)throw Error('Storage delete unavailable.');await e.delete(payload.key)}else{if('function'!=typeof globalThis.GM_deleteValue)throw Error('Storage delete unavailable.');await Promise.resolve(globalThis.GM_deleteValue(payload.key))}try{globalThis.localStorage?.removeItem(s),globalThis.localStorage?.removeItem(n)}catch{}}else{const e=await gmRead(payload.key,null);if(payload.key===i&&Number(e?.checkedAt||0)>Number(payload.value?.checkedAt||0))return;let value=payload.value;if(payload.key===n&&isRecord(e)&&isRecord(payload.base)){const t=mergeChanges(payload.base.settings,value.settings,e.settings||{}),r=mergeChanges(payload.base.targetList,value.caches?.targetList||{},e.caches?.targetList||{});value={...savedAt(e)>savedAt(value)?e:value,settings:t,caches:{...savedAt(e)>savedAt(value)?e.caches:value.caches,targetList:r},_savedAt:Math.max(savedAt(e),savedAt(value))+1}}else if(savedAt(e)>savedAt(value))return;if(payload.base?.protected&&e)for(const key of Object.keys(payload.base.protected))value[key]=mergeChanges(payload.base.protected[key],payload.value[key],Object.hasOwn(e,key)?e[key]:payload.value[key]);await gmWrite(payload.key,value)}})).then(value=>acknowledge(payload,!0,'',value),e=>acknowledge(payload,!1,String(e?.message||e?.code||'Storage write failed')))}});Object.defineProperty(globalThis,'__SLINK_PDA_STORAGE_BRIDGE__',{value:Object.freeze({protocol:1,document,channel:m}),configurable:true});}(),await loadRuntime()})();