chrome extension for skeeting tweets at the sky
1async function ie(t,e,r,n,o){try{let a=t.jwt.split("."),i=_e(a[0]),c=JSON.parse(i).jwk,d=Array.from(crypto.getRandomValues(new Uint8Array(16))).map(v=>v.toString(16).padStart(2,"0")).join(""),h={typ:"dpop+jwt",alg:"ES256",jwk:c},w=Math.floor(Date.now()/1e3),u={jti:d,htm:e,htu:r,iat:w,exp:w+30,nonce:void 0,ath:void 0};n&&(u.nonce=n),o&&(u.ath=o);let f=ce(JSON.stringify(h)),p=ce(JSON.stringify(u)),g=`${f}.${p}`,m=await crypto.subtle.importKey("pkcs8",Ue(t.key),{name:"ECDSA",namedCurve:"P-256"},!1,["sign"]),y=await crypto.subtle.sign({name:"ECDSA",hash:{name:"SHA-256"}},m,new TextEncoder().encode(g)),S=le(y);return`${g}.${S}`}catch(a){throw a}}function ce(t){return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function le(t){let e=new Uint8Array(t),r="";for(let n=0;n<e.byteLength;n++)r+=String.fromCharCode(e[n]);return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function _e(t){let e=t.replace(/-/g,"+").replace(/_/g,"/"),r=e.padEnd(e.length+(4-e.length%4)%4,"=");return atob(r)}function Ue(t){let e=t.replace(/-/g,"+").replace(/_/g,"/"),r=e.padEnd(e.length+(4-e.length%4)%4,"="),n=atob(r),o=new Uint8Array(n.length);for(let a=0;a<n.length;a++)o[a]=n.charCodeAt(a);return o.buffer}async function Ae(t){try{let e=new TextEncoder().encode(t),r=await crypto.subtle.digest("SHA-256",e);return le(r)}catch(e){throw e}}async function De(t){try{if(!t){let{did:n}=await j();t=n}let e=`/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(t)}&collection=app.bsky.actor.profile&rkey=self`;return await J("GET",e,null)}catch(e){throw e}}function Pe(t){var e,r,n,o;try{if((o=(n=(r=(e=t==null?void 0:t.raw)==null?void 0:e.value)==null?void 0:r.avatar)==null?void 0:n.ref)!=null&&o.$link){let a=t.did||t.raw.uri.split("/")[2],i=t.raw.value.avatar.ref.$link;return(async()=>{let{session:c}=await j();return c.info.aud})().then(c=>`${c}xrpc/com.atproto.sync.getBlob?did=${encodeURIComponent(a)}&cid=${encodeURIComponent(i)}`)}return null}catch{return null}}async function z(t){var e,r;try{let n=await De(t),o={did:n.uri.split("/")[2],handle:null,displayName:((e=n.value)==null?void 0:e.displayName)||null,description:((r=n.value)==null?void 0:r.description)||null,avatar:"",raw:n},a=await Pe(o);if(a&&(o.avatar=a),o.did)try{let i=await J("GET",`/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(o.did)}`,null);i&&i.handle&&(o.handle=i.handle)}catch{}return o}catch(n){throw n}}async function j(){var a;let t=await chrome.storage.local.get(["atcute-oauth:sessions"]);if(!t||!t["atcute-oauth:sessions"])throw new Error("No OAuth session found");let e=t["atcute-oauth:sessions"],r=Object.keys(e);if(!r.length)throw new Error("No OAuth sessions available");let n=r[0],o=e[n].value;if(!((a=o==null?void 0:o.token)!=null&&a.access))throw new Error("Invalid OAuth session: missing access token");return{did:n,session:o,accessToken:o.token.access}}async function $e(){let t=await chrome.storage.local.get(["atcute-oauth:states"]);if(!t||!t["atcute-oauth:states"])throw new Error("No OAuth states found");let e=t["atcute-oauth:states"],r=null,n=0;if(Object.entries(e).forEach(([o,a])=>{var s;let i=a;(s=i.value)!=null&&s.dpopKey&&i.expiresAt&&i.expiresAt>n&&(r=i.value.dpopKey,n=i.expiresAt)}),!r)throw new Error("No DPoP key found in states");return r}async function Te(t,e){let n=(await chrome.storage.local.get(["atcute-oauth:dpopNonces"]))["atcute-oauth:dpopNonces"]||{};n[t]={value:e,expiresAt:Date.now()+6e5},await chrome.storage.local.set({"atcute-oauth:dpopNonces":n})}async function Le(t){let r=(await chrome.storage.local.get(["atcute-oauth:dpopNonces"]))["atcute-oauth:dpopNonces"];return r&&r[t]&&r[t].value?r[t].value:null}async function J(t,e,r){try{let{did:n,session:o,accessToken:a}=await j(),i=await $e();if(!e.startsWith("/xrpc/com.atproto."))throw new Error("OAuth tokens can only be used with PDS endpoints (/xrpc/com.atproto.*)");let s=o.info.aud;if(!s)throw new Error("No PDS URL (aud) found in session");let c=s.endsWith("/")&&e.startsWith("/")?`${s}${e.substring(1)}`:`${s}${e}`,h=new URL(c).origin,w=await Le(h),u=await Ae(a),f=await ie(i,t,c,w||null,u),p={method:t,headers:{"Content-Type":"application/json",Authorization:`DPoP ${a}`,DPoP:f},body:void 0};r&&(p.body=JSON.stringify(r));let g=await fetch(c,p),m=g.headers.get("dpop-nonce");if(m&&m!==w&&(await Te(h,m),(g.status===400||g.status===401)&&(await g.clone().json()).error==="use_dpop_nonce")){let S=await ie(i,t,c,m,u),v={...p,headers:{...p.headers,DPoP:S}};g=await fetch(c,v)}if(!g.ok){let y;try{y=await g.json()}catch{y={error:"Failed to parse error response",message:await g.text()}}throw new Error(`PDS HTTP Error: ${JSON.stringify(y)}`)}return await g.json()}catch(n){throw n}}async function de(t){try{let{did:e}=await j(),r=new Date().toISOString();return await J("POST","/xrpc/com.atproto.repo.createRecord",{repo:e,collection:"app.bsky.feed.post",record:{$type:"app.bsky.feed.post",text:t,createdAt:r}})}catch(e){throw e}}var W=class t{name;controller;signal;static storage;constructor(e){if(!t.storage)try{t.storage=chrome.storage}catch{try{t.storage=browser.storage}catch{throw"Unsupported browser"}}this.name=e.name,this.controller=new AbortController,this.signal=this.controller.signal,this.sessions=this.createStore("sessions",({token:r})=>r.refresh?null:r.expires_at??null),this.states=this.createStore("states",r=>Date.now()+6e5),this.dpopNonces=this.createStore("dpopNonces",r=>Date.now()+6e5)}sessions;states;dpopNonces;dispose(){this.controller.abort()}createStore(e,r){let n=`${this.name}:${e}`,o=async s=>{try{await t.storage.local.set({[n]:s})}catch{}},a=async()=>{if(this.signal.aborted)throw new Error("store closed");try{return(await t.storage.local.get(n))[n]||{}}catch{return{}}},i=null;return i=setInterval(async()=>{if(!this.signal.aborted)try{let s=await a(),c=Date.now(),d=!1;for(let h in s){let w=s[h].expiresAt;w!==null&&c>w&&(d=!0,delete s[h])}d&&await o(s)}catch(s){if(s.message.includes("Extension context invalidated.")){i&&clearInterval(i);return}}},1e4),this.signal.addEventListener("abort",()=>clearInterval(i)),{async get(s){let c=await a(),d=c[s];if(!d)return;let h=d.expiresAt;if(h!==null&&Date.now()>h){delete c[s],await o(c);return}return d.value},async set(s,c){let d=await a();d[s]={expiresAt:r(c),value:c},await o(d)},async delete(s){let c=await a();c[s]!==void 0&&(delete c[s],await o(c))},async keys(){let s=await a();return Object.keys(s)}}}},O,ne,L,M=t=>{({client_id:O,redirect_uri:ne}=t.metadata),L=new W({name:t.storageName??"atcute-oauth"})},b=class extends Error{name="ResolverError"},$=class extends Error{sub;name="TokenRefreshError";constructor(e,r,n){super(r,n),this.sub=e}},R=class extends Error{response;data;name="OAuthResponseError";error;description;constructor(e,r){var c,d;let n=ue((c=he(r))==null?void 0:c.error),o=ue((d=he(r))==null?void 0:d.error_description),a=n?`"${n}"`:"unknown",i=o?`: ${o}`:"",s=`OAuth ${a} error${i}`;super(s),this.response=e,this.data=r,this.error=n,this.description=o}get status(){return this.response.status}get headers(){return this.response.headers}},G=class extends Error{response;status;name="FetchResponseError";constructor(e,r,n){super(n),this.response=e,this.status=r}},ue=t=>typeof t=="string"?t:void 0,he=t=>typeof t=="object"&&t!==null&&!Array.isArray(t)?t:void 0,Ce=t=>Ie(t,"#atproto_pds","AtprotoPersonalDataServer"),Ie=(t,e,r)=>{var a;let n=t.id+e,o=(a=t.service)==null?void 0:a.find(i=>i.id===e||i.id===n);if(!(!o||o.type!==r||typeof o.serviceEndpoint!="string"))return Oe(o.serviceEndpoint)},Oe=t=>{let e;try{e=new URL(t)}catch{return}let r=e.protocol;if(e.hostname&&(r==="http:"||r==="https:"))return t},je="https://public.api.bsky.app",H=t=>{var e;return(e=t.get("content-type"))==null?void 0:e.split(";")[0]},Be=t=>t.startsWith("did:"),Re=/^([a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*(?:\.[a-zA-Z]{2,}))$/,Ne=async t=>{let e=je+`/xrpc/com.atproto.identity.resolveHandle?handle=${t}`,r=await fetch(e);if(r.status===400)throw new b("domain handle not found");if(!r.ok)throw new b("directory is unreachable");return(await r.json()).did},Me=async t=>{let e=t.indexOf(":",4),r=t.slice(4,e),n=t.slice(e+1),o;if(r==="plc"){let a=await fetch(`https://plc.directory/${t}`);if(a.status===404)throw new b("did not found in directory");if(!a.ok)throw new b("directory is unreachable");o=await a.json()}else if(r==="web"){if(!Re.test(n))throw new b("invalid identifier");let a=await fetch(`https://${n}/.well-known/did.json`);if(!a.ok)throw new b("did document is unreachable");o=await a.json()}else throw new b("unsupported did method");return o},He=async t=>{let e=new URL("/.well-known/oauth-protected-resource",t),r=await fetch(e,{redirect:"manual",headers:{accept:"application/json"}});if(r.status!==200||H(r.headers)!=="application/json")throw new b("unexpected response");let n=await r.json();if(n.resource!==e.origin)throw new b("unexpected issuer");return n},Ke=async t=>{let e=new URL("/.well-known/oauth-authorization-server",t),r=await fetch(e,{redirect:"manual",headers:{accept:"application/json"}});if(r.status!==200||H(r.headers)!=="application/json")throw new b("unexpected response");let n=await r.json();if(n.issuer!==e.origin)throw new b("unexpected issuer");if(!n.client_id_metadata_document_supported)throw new b("authorization server does not support 'client_id_metadata_document'");if(!n.pushed_authorization_request_endpoint)throw new b("authorization server does not support 'pushed_authorization request'");if(n.response_types_supported&&!n.response_types_supported.includes("code"))throw new b("authorization server does not support 'code' response type");return n},fe=async t=>{let e;Be(t)?e=t:e=await Ne(t);let r=await Me(e),n=Ce(r);if(!n)throw new b("missing pds endpoint");return{identity:{id:e,raw:t,pds:new URL(n)},metadata:await Fe(n)}},Fe=async t=>{var o;let e=await He(t);if(((o=e.authorization_servers)==null?void 0:o.length)!==1)throw new b("expected exactly one authorization server in the listing");let r=e.authorization_servers[0],n=await Ke(r);if(n.protected_resources&&!n.protected_resources.includes(e.resource))throw new b("server is not in authorization server's jurisdiction");return n},qe="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict",ze=(t=21)=>{let e="",r=t;for(;r--;)e+=qe[Math.random()*64|0];return e},N=new TextEncoder,pe=navigator.locks,C=t=>{let e=[];for(let r=0;r<t.byteLength;r+=32768)e.push(String.fromCharCode.apply(null,t.subarray(r,r+32768)));return btoa(e.join("")).replace(/=/g,"").replace(/\+/g,"-").replace(/\//g,"_")},Je=t=>{try{let e=atob(t.replace(/-/g,"+").replace(/_/g,"/").replace(/\s/g,"")),r=new Uint8Array(e.length);for(let n=0;n<e.length;n++)r[n]=e.charCodeAt(n);return r}catch(e){throw new TypeError("invalid base64url",{cause:e})}},ge=async t=>{let e=N.encode(t),r=await crypto.subtle.digest("SHA-256",e);return C(new Uint8Array(r))},we=t=>C(crypto.getRandomValues(new Uint8Array(t))),We=()=>we(16),Ge=async()=>{let t=we(32);return{verifier:t,challenge:await ge(t),method:"S256"}};function A(t,e){if(t==null){e??="value is null/empty";let r=Error().stack;if(r){let n=r.split(`
2`);if(n.length>2){let o=n[2].trimStart();throw o=o.replace(/^at/,""),o=o.trimStart(),`${o}:
3${typeof t}: ${e}`}}throw`${typeof t}: ${e}`}return t}var me={name:"ECDSA",namedCurve:"P-256"},Ve=async()=>{let t=await crypto.subtle.generateKey(me,!0,["sign","verify"]),e=await crypto.subtle.exportKey("pkcs8",t.privateKey),{ext:r,key_ops:n,...o}=await crypto.subtle.exportKey("jwk",t.publicKey);return{typ:"ES256",key:C(new Uint8Array(e)),jwt:C(N.encode(JSON.stringify({typ:"dpop+jwt",alg:"ES256",jwk:o})))}},Ze=(t,e)=>{let r=e.jwt,n=crypto.subtle.importKey("pkcs8",Je(e.key),me,!0,["sign"]),o=(a,i,s,c)=>{let d=Date.now()/1e3|0,h={iss:t,iat:d,jti:ze(12),htm:a,htu:i,nonce:s,ath:c};return C(N.encode(JSON.stringify(h)))};return async(a,i,s,c)=>{let d=o(a,i,s,c),h=await crypto.subtle.sign({name:"ECDSA",hash:{name:"SHA-256"}},await n,N.encode(r+"."+d)),w=C(new Uint8Array(h));return r+"."+d+"."+w}},ye=(t,e,r)=>{let n=A(L),o=A(n.dpopNonces),a=Ze(t,e);return async(i,s)=>{let c=s==null&&i instanceof Request?i:new Request(i,s),d=c.headers.get("authorization"),h=d!=null&&d.startsWith("DPoP ")?await ge(d.slice(5)):void 0,{method:w,url:u}=c,{origin:f}=new URL(u),p;try{p=await o.get(f)}catch{}let g=await a(w,u,p,h);c.headers.set("dpop",g);let m=await fetch(c),y=m.headers.get("dpop-nonce");if(!y||y===p)return m;try{await o.set(f,y)}catch{}if(!await Xe(m,r)||i===c||(s==null?void 0:s.body)instanceof ReadableStream)return m;let S=await a(w,u,y,h),v=new Request(i,s);return v.headers.set("dpop",S),await fetch(v)}},Xe=async(t,e)=>{if((e===void 0||!e)&&t.status===401){let r=t.headers.get("www-authenticate");if(r!=null&&r.startsWith("DPoP"))return r.includes('error="use_dpop_nonce"')}if((e===void 0||e)&&t.status===400&&H(t.headers)==="application/json")try{let r=await t.clone().json();return typeof r=="object"&&(r==null?void 0:r.error)==="use_dpop_nonce"}catch{return!1}return!1},Ye=(t,e)=>{let r={};for(let n=0,o=e.length;n<o;n++){let a=e[n];r[a]=t[a]}return r},T=class{#t;#e;constructor(e,r){this.#t=e,this.#e=ye(O,r,!0)}async request(e,r){let n=this.#t[`${e}_endpoint`];if(!n)throw new Error(`no endpoint for ${e}`);let o=await this.#e(n,{method:"post",headers:{"content-type":"application/json"},body:JSON.stringify({...r,client_id:O})});if(H(o.headers)!=="application/json")throw new G(o,2,"unexpected content-type");let a=await o.json();if(o.ok)return a;throw new R(o,a)}async revoke(e){try{await this.request("revocation",{token:e})}catch{}}async exchangeCode(e,r){let n=await this.request("token",{grant_type:"authorization_code",redirect_uri:ne,code:e,code_verifier:r});try{return await this.#n(n)}catch(o){throw await this.revoke(n.access_token),o}}async refresh({sub:e,token:r}){if(!r.refresh)throw new $(e,"no refresh token available");let n=await this.request("token",{grant_type:"refresh_token",refresh_token:r.refresh});if(!n.sub)throw new $(e,"missing value for sub in token response");if(e!==n.sub)throw new $(e,`sub mismatch in token response; got ${n.sub}`);try{return this.#r(n)}catch(o){throw await this.revoke(n.access_token),o}}#r(e){if(!e.sub)throw new TypeError("missing sub field in token response");if(!e.scope)throw new TypeError("missing scope field in token response");if(e.token_type!=="DPoP")throw new TypeError("token response returned a non-dpop token");return{scope:e.scope,refresh:e.refresh_token,access:e.access_token,type:e.token_type,expires_at:typeof e.expires_in=="number"?Date.now()+e.expires_in*1e3:void 0}}async#n(e){let r=e.sub;if(!r)throw new TypeError("missing sub field in token response");let n=this.#r(e),o=await fe(r);if(o.metadata.issuer!==this.#t.issuer)throw new TypeError(`issuer mismatch; got ${o.metadata.issuer}`);return{token:n,info:{sub:r,aud:o.identity.pds.href,server:Ye(o.metadata,["issuer","authorization_endpoint","introspection_endpoint","pushed_authorization_request_endpoint","revocation_endpoint","token_endpoint"])}}}},B=new Map,V=async(t,e)=>{var s,c;(s=e==null?void 0:e.signal)==null||s.throwIfAborted();let r=nt;e!=null&&e.noCache?r=et:e!=null&&e.allowStale&&(r=Qe);let n;for(;n=B.get(t);){try{let{isFresh:d,value:h}=await n;if(d||r(h))return h}catch{}(c=e==null?void 0:e.signal)==null||c.throwIfAborted()}let o=async()=>{let d=A(L),h=await A(d.sessions).get(t);if(h&&r(h))return{isFresh:!1,value:h};let w=await tt(t,h);return await ke(t,w),{isFresh:!0,value:w}},a;if(pe?a=pe.request(`atcute-oauth:${t}`,o):a=o(),a=a.finally(()=>B.delete(t)),B.has(t))throw new Error("concurrent request for the same key");B.set(t,a);let{value:i}=await a;return i},ke=async(t,e)=>{try{let r=A(L);await A(r.sessions).set(t,e)}catch(r){throw await rt(e),r}},be=async t=>{let e=A(L);await A(e.sessions).delete(t)},Qe=()=>!0,et=()=>!1,tt=async(t,e)=>{if(e===void 0)throw new $(t,"session deleted by another tab");let{dpopKey:r,info:n,token:o}=e,a=new T(n.server,r);try{let i=await a.refresh({sub:n.sub,token:o});return{dpopKey:r,info:n,token:i}}catch(i){throw i instanceof R&&i.status===400&&i.error==="invalid_grant"?new $(t,"session was revoked",{cause:i}):i}},rt=async({dpopKey:t,info:e,token:r})=>{await new T(e.server,t).revoke(r.refresh??r.access)},nt=({token:t})=>{let e=t.expires_at;return e==null||Date.now()+6e4<=e},ot=async({metadata:t,identity:e,scope:r})=>{let n=We(),o=await Ge(),a=await Ve(),i={redirect_uri:ne,code_challenge:o.challenge,code_challenge_method:o.method,state:n,login_hint:e==null?void 0:e.raw,response_mode:"fragment",response_type:"code",display:"page",scope:r},s=A(L);await A(s.states).set(n,{dpopKey:a,metadata:t,verifier:o.verifier});let d=await new T(t,a).request("pushed_authorization_request",i),h=new URL(t.authorization_endpoint);return h.searchParams.set("client_id",O),h.searchParams.set("request_uri",d.request_uri),h},Z=class{session;#t;#e;constructor(e){this.session=e,this.#t=ye(O,e.dpopKey,!1)}get sub(){return this.session.info.sub}getSession(e){let r=V(this.session.info.sub,e);return r.then(n=>{this.session=n}).finally(()=>{this.#e=void 0}),this.#e=r,r}async signOut(){let e=this.session.info.sub;try{let{dpopKey:r,info:n,token:o}=await V(e,{allowStale:!0});await new T(n.server,r).revoke(o.refresh??o.access)}finally{await be(e)}}async handle(e,r){await this.#e;let n=new Headers(r==null?void 0:r.headers),o=this.session,a=new URL(e,o.info.aud);n.set("authorization",`${o.token.type} ${o.token.access}`);let i=await this.#t(a,{...r,headers:n});if(!at(i))return i;try{this.#e?o=await this.#e:o=await this.getSession()}catch{return i}return(r==null?void 0:r.body)instanceof ReadableStream?i:(a=new URL(e,o.info.aud),n.set("authorization",`${o.token.type} ${o.token.access}`),await this.#t(a,{...r,headers:n}))}},at=t=>{if(t.status!==401)return!1;let e=t.headers.get("www-authenticate");return e!=null&&(e.startsWith("Bearer ")||e.startsWith("DPoP "))&&e.includes('error="invalid_token"')};function E(t,e){if(t==null){e??="value is null/empty";let r=Error().stack;if(r){let n=r.split(`
4`);if(n.length>2){let o=n[2].trimStart();throw o=o.replace(/^at/,""),o=o.trimStart(),`${o}:
5${typeof t}: ${e}`}}throw`${typeof t}: ${e}`}return t}function st(t){let e=window.open(t,"_blank");return e&&e.focus(),e}var X=class{container;timer;constructor(){this.timer=null,this.container=document.createElement("div"),this.container.classList.add("biskuvi-toast-container"),document.body.appendChild(this.container)}show(e,r="info",n=3e3){if(document.hidden)return;this.timer&&clearTimeout(this.timer);let o=new Y(e,r),a=this.container.lastChild;if(a)try{this.container.removeChild(a)}catch(i){i.name!=="NotFoundError"&&void 0}this.timer=setTimeout(()=>{o.element.style.animation="slideOut 250ms ease-in",setTimeout(()=>{this.container.removeChild(o.element)},250)},n),this.container.appendChild(o.element)}},Y=class{element;constructor(e,r){this.element=document.createElement("div"),this.element.classList.add("biskuvi-toast",r),this.element.textContent=e}},k={userHandle:"skeet_user_handle",userDid:"skeet_user_did",language:"skeet_language",colorScheme:"skeet_color_scheme",bookmarkServerUrl:"skeet_bookmark_server_url",bookmarkFeedUrl:"skeet_bookmark_feed_url",clientMetadataJsonUrl:"skeet_client_metadata_json_url",oauthCallbackUrl:"skeet_oauth_callback_url"};async function it(t){switch(t){case 1:return new Q;default:throw"Not implemented"}}var Q=class{async isBookmarked(e){return(await(await this.fetch("/xrpc/app.biskuvi.bookmark.isBookmarked",e)).json()).is_bookmarked===!0}async arePostsBookmarked(e){let n=await(await this.fetch("/xrpc/app.biskuvi.bookmark.arePostsBookmarked",null,{uris:e})).json();return E(n.uris)}async addBookmark(e){await this.fetch("/xrpc/app.biskuvi.bookmark.addBookmark",e)}async removeBookmark(e){await this.fetch("/xrpc/app.biskuvi.bookmark.removeBookmark",e)}async fetch(e,r,n){let o=E(D.bookmarkServerUrl)+e;r&&(o+=`?uri=${r}`);let a=E(D.oAuthUserAgent),i;if(n?i=await a.handle(o,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(n)}):i=await a.handle(o),!i.ok)throw new Error("Invalid response status");return i}};function ct(){try{return E(chrome)}catch{try{return E(browser)}catch{throw"Unsupported browser"}}}var oe=ct();async function ve(t){return await oe.storage.local.set(t)}async function K(t,e=[]){let r=t;return e.length>0&&(r=r.filter(n=>!e.includes(n))),await oe.storage.local.get(r)}async function ee(t){return await oe.storage.local.remove(t)}var lt={bskyUrl:"https://bsky.app",bookmarkPageUrlAlias:"/bookmarks",handleResolverUrl:"https://api.bsky.app",didResolverUrl:"https://plc.directory"},I={language:"en",bookmarkServerUrl:"https://bookmarks.bskv.site",bookmarkFeedUrl:"https://bsky.app/profile/bookmarks.bskv.site/feed/bookmarks",clientMetadataJsonUrl:"https://skeet.fyi/client-metadata.json",oauthCallbackUrl:"https://skeet.fyi/oauth-callback"},D=class t{static userDid;static userHandle;static language;static bookmarkServerUrl;static bookmarkFeedUrl;static clientMetadataJsonUrl;static oauthCallbackUrl;static root;static oAuthSession;static oAuthUserAgent;static bookmarkManager;static toaster;static fallbackIsBookmarked;static async init(){t.bookmarkManager=await it(1);let e=await K(Object.values(k));t.userDid=e[k.userDid],t.language=e[k.language]||I.language,t.bookmarkServerUrl=e[k.bookmarkServerUrl]||I.bookmarkServerUrl,t.bookmarkFeedUrl=e[k.bookmarkFeedUrl]||I.bookmarkFeedUrl,t.clientMetadataJsonUrl=e[k.clientMetadataJsonUrl]||I.clientMetadataJsonUrl,t.oauthCallbackUrl=e[k.oauthCallbackUrl]||I.oauthCallbackUrl,t.toaster=new X}};async function dt(t){let e;try{e=await fetch(t)}catch(r){throw`getDid: ${r.message}`}if(!e.ok)throw`response is not OK: ${e.status}`;return await e.json()}async function ut(t){let e=lt.didResolverUrl+"/"+t;return E(await dt(e))}async function Ee(t){if(!t.startsWith("did:plc:"))throw"identity does not begin with did:plc:";let e=E((await ut(t)).alsoKnownAs[0]),r="at://";return e.startsWith(r)&&(e=e.substring(r.length)),e}function F(){return{metadata:{client_id:E(D.clientMetadataJsonUrl),redirect_uri:E(D.oauthCallbackUrl)}}}function l(t,e,r){try{chrome.runtime.sendMessage({type:"log_to_background",logType:t,message:`[POPUP] ${e}`,data:r})}catch{}}async function xe(t){var o;l("info","Popup handling OAuth callback",t);let{code:e,state:r,iss:n}=t;if(!e||!r||!n)return l("error","Missing required OAuth parameters",{code:!!e,state:!!r,iss:!!n}),{success:!1,error:"Missing required OAuth parameters"};try{l("info","Initializing OAuth"),await D.init(),M(F()),l("info","Getting state data from storage",{state:r});let a=await L.states.get(r);if(!a){l("error","No state data found for state",{state:r});try{let s=await chrome.storage.local.get(null);l("info","All storage contents for debugging",s);let c=s["atcute-oauth:states"]||{};l("info","Available states",Object.keys(c))}catch(s){l("error","Error checking storage",s)}return{success:!1,error:"Invalid state parameter or session expired"}}l("info","Found state data",{stateDataExists:!!a,dpopKeyExists:!!(a!=null&&a.dpopKey),verifierExists:!!(a!=null&&a.verifier),metadataExists:!!(a!=null&&a.metadata)}),l("info","Creating OAuth client");let i=new T(a.metadata,a.dpopKey);try{l("info","Exchanging code for tokens",{code:e.substring(0,10)+"...",verifier:a.verifier.substring(0,10)+"..."});let s=await i.exchangeCode(e,a.verifier);l("info","Exchanged code for tokens successfully",{tokenExists:!!(s!=null&&s.token),infoExists:!!(s!=null&&s.info),sub:(o=s==null?void 0:s.info)==null?void 0:o.sub}),l("info","Saving session"),await ke(s.info.sub,s);let c=s.info.sub;l("info","Resolving handle from DID",{did:c});let d=await Ee(c);return l("info","Storing user info in storage",{did:c,handle:d}),await ve({[k.userDid]:c,[k.userHandle]:d}),l("info","Sending auth_complete message",{did:c,handle:d}),chrome.runtime.sendMessage({type:"auth_complete",data:{did:c,handle:d}},h=>{chrome.runtime.lastError?l("warning","Error sending auth_complete message (expected if no listeners)",chrome.runtime.lastError):l("info","auth_complete message sent successfully",h)}),l("info","Authentication successful!",{did:c,handle:d}),{success:!0,data:{did:c,handle:d}}}catch(s){return l("error","Error exchanging code for tokens",{error:s.toString(),stack:s.stack}),{success:!1,error:s.message||"Error exchanging code for tokens"}}}catch(a){return l("error","Error handling OAuth callback",{error:a.toString(),stack:a.stack}),{success:!1,error:a.message||"Error handling OAuth callback"}}}chrome.runtime.onMessage.addListener((t,e,r)=>{try{if(t.type==="popup_oauth_callback")return l("info","Popup received OAuth callback",t.data),xe(t.data).then(n=>{l("info","OAuth callback result",n),r(n),n.success&&(l("info","Updating UI after successful login",{did:n.data.did,handle:n.data.handle}),ae(n.data.did,n.data.handle))}).catch(n=>{l("error","Error in handleOAuthCallback",{error:n.toString(),stack:n.stack}),r({success:!1,error:n.message||"Unknown error"})}),!0;t.type==="auth_status_update"&&(l("info","Received auth status update",t.data),t.data.authenticated&&t.data.did&&l("info","Auth status update indicates successful login",{did:t.data.did}))}catch(n){l("error","Error handling message in popup",{error:n.toString(),stack:n.stack,message:t}),r({success:!1,error:n.toString()})}});function ae(t,e){if(!document.getElementById("content")){l("warning","Cannot update UI - popup elements not found");return}l("info","Updating UI elements for logged in state");let r=document.getElementById("login-container"),n=document.getElementById("skeet"),o=document.getElementById("logoutLink"),a=document.getElementById("status");if(r&&n&&o){r.classList.add("hidden"),n.classList.remove("hidden"),o.classList.remove("hidden"),l("info","Setting up logout button");let i=o.cloneNode(!0);o.parentNode.replaceChild(i,o),i.addEventListener("click",async()=>{l("info","Logout button clicked");try{try{await D.init(),M(F());let c=await V(t,{allowStale:!0});await new Z(c).signOut()}catch(c){l("warning","Error during signOut, falling back to deleteSession",c),await be(t)}await ee([k.userDid,k.userHandle]),l("info","Logout successful, updating UI"),r.classList.remove("hidden"),n.classList.add("hidden"),i.classList.add("hidden");let s=document.getElementById("user-section");s&&(s.innerHTML="")}catch(s){l("error","Error during logout",s),a&&(a.innerText=s.toString())}}),z(t).then(({displayName:s,avatar:c})=>{l("info","Got user profile",{displayName:s,avatar:c}),re({displayName:s,userHandle:e,avatar:c}),te()}).catch(s=>{l("error","Error getting user profile:",s),re({displayName:e,userHandle:e,avatar:""}),te()}),l("info","UI updated successfully")}else l("warning","Some UI elements not found",{loginContainer:!!r,skeetContainer:!!n,logoutLink:!!o})}async function ht(){l("info","Checking for stored OAuth callback data");let t=await K(["oauth_callback_data"]);if(t.oauth_callback_data){l("info","Found stored OAuth callback data",t.oauth_callback_data);let e=t.oauth_callback_data.timestamp||0,r=Date.now(),n=r-5*60*1e3;if(e>n){l("info","Processing stored OAuth callback data",{age:(r-e)/1e3,maxAgeSeconds:5*60});let o=await xe(t.oauth_callback_data);o.success?(l("info","Successfully processed stored OAuth data, updating UI"),ae(o.data.did,o.data.handle)):l("warning","Failed to process stored OAuth data",o),l("info","Clearing stored OAuth callback data"),await ee(["oauth_callback_data"])}else l("info","Stored OAuth callback data is too old, removing it",{age:(r-e)/1e3,maxAgeSeconds:5*60}),await ee(["oauth_callback_data"])}else l("info","No stored OAuth callback data found"),await pt()}async function pt(){l("info","Checking for existing OAuth session");try{await D.init(),M(F());let t=await chrome.storage.local.get(["atcute-oauth:sessions"]);if(t&&t["atcute-oauth:sessions"]){let e=t["atcute-oauth:sessions"],r=Object.keys(e);if(r.length>0){let n=r[0];l("info","Found existing session for DID:",n);let o=null,a=await K([k.userHandle]);if(a[k.userHandle])o=a[k.userHandle],l("info","Found stored handle:",o);else try{o=await Ee(n),l("info","Resolved handle from DID:",o),await ve({[k.userDid]:n,[k.userHandle]:o})}catch(i){l("error","Error resolving handle from DID:",i)}if(o)return l("info","Updating UI with existing session data"),ae(n,o),!0}}return l("info","No existing session found"),!1}catch(t){return l("error","Error checking existing session:",t),!1}}function te(){let t=document.getElementById("skeet-text"),e=document.getElementById("char-count"),r=document.getElementById("skeet-button"),n="",o=-1,a=0,i=!1,s=document.querySelectorAll("#mention");for(let u of s)u.remove();let c=document.createElement("button");c.id="mention",c.className="text-primary-500 dark:text-primary-400 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 p-2 mr-2 hidden border-none rounded-md",c.textContent="@",c.title="Add mention",e.parentNode.insertBefore(c,e),chrome.tabs.query({active:!0,currentWindow:!0},async u=>{var f,p,g;if((f=u[0])!=null&&f.url){n=u[0].url;let m=n.match(/https?:\/\/(x\.com|twitter\.com)\/([^/]+)\/status\/(\d+)/);if(m){i=!0;let[y,S,v,_]=m,P=`x/${v}/status/${_}`;try{let[U]=await chrome.scripting.executeScript({target:{tabId:u[0].id},func:()=>{let x=document.querySelector('[data-testid="tweetText"]');return x?x.textContent:null}});if(U!=null&&U.result){let x=U.result.trim(),q=`#SKEETED
6${P}`,se=300-q.length-2,Se=x.length>se?`${x.substring(0,se-3)}...`:x;t.value=`${Se}
7
8${q}`}else t.value=`#SKEETED
9${P}`;d()}catch{t.value=`#SKEETED
10${P}`,d()}}else t.value=`${n}
11
12`,d();try{let S=new URL(n).hostname,v=await chrome.storage.local.get(["atcute-oauth:sessions"]);l("info","Session data for domain check",v);let _=v["atcute-oauth:sessions"];if(_){let P=Object.keys(_)[0],U=_[P];if((g=(p=U==null?void 0:U.value)==null?void 0:p.token)!=null&&g.access){let x=await fetch(`https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${S}`,{headers:{Authorization:`Bearer ${U.value.token.access}`,Accept:"application/json"}});x.ok&&(await x.json()).did&&c.classList.remove("hidden")}}}catch{}}});function d(){let u=t.value,f=u.match(/https?:\/\/(x\.com|twitter\.com)\/([^/]+)\/status\/(\d+)/);if(f){let[g,m,y]=f;u=u.replace(g,`x/${m}/status/${y}`)}else n&&u.includes(n)&&(u=u.replace(n,"x".repeat(39)));let p=300-u.length;e.textContent=p,r.disabled=u.trim().length===0||p<0,p<0?(e.classList.add("text-red-500","dark:text-red-400"),e.classList.remove("text-gray-500","dark:text-gray-400")):(e.classList.remove("text-red-500","dark:text-red-400"),e.classList.add("text-gray-500","dark:text-gray-400"))}function h(){let f=`@${new URL(n).hostname}`,p=t.selectionStart;if(o===p-a)t.value=t.value.slice(0,o)+t.value.slice(o+a),t.selectionStart=o,t.selectionEnd=o,o=-1,a=0;else{let g=t.value.slice(0,p),m=t.value.slice(p);t.value=g+f+m,o=p,a=f.length,t.selectionStart=p+f.length,t.selectionEnd=p+f.length}d(),t.focus()}async function w(){try{r.disabled=!0,r.innerHTML='<span class="inline-block animate-pulse">...</span>';let u=t.value.trim(),f=u.match(/https?:\/\/(x\.com|twitter\.com)\/([^/]+)\/status\/(\d+)/);if(f){let[g,m]=f,y=`x/${g}/status/${m}`,v=u.split(`
13`).filter(x=>x.trim()).filter(x=>!x.includes("x.com")&&!x.includes("twitter.com")&&x!=="#SKEETED").join(`
14`).trim(),_=`#SKEETED
15${y}`,P=300-_.length-2,U=v.length>P?`${v.substring(0,P-3)}...`:v;u=U?`${U}
16
17${_}`:_}let p=await de(u);if(i&&(p!=null&&p.uri)){let[g,m]=p.uri.split("/").slice(-3),y=`https://bsky.app/profile/${g}/post/${m}`;t.value="",d();let S=document.createElement("div");S.className="mt-12 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg",S.innerHTML=`
18 <div class="flex items-center gap-2 p-2 bg-white dark:bg-gray-900 rounded border border-gray-200 dark:border-gray-700">
19 <div class="flex-grow font-mono text-xs text-gray-800 dark:text-gray-200 overflow-hidden text-ellipsis whitespace-nowrap">
20 #SKEETED
21 ${y}
22 </div>
23 <button id="copy-button" class="flex-shrink-0 text-primary-500 dark:text-primary-400 hover:text-primary-600 dark:hover:text-primary-300 p-2 border-none">
24 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
25 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
26 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
27 </svg>
28 </button>
29 </div>
30 `;let v=document.getElementById("skeet");v.innerHTML="",v.appendChild(S);let _=document.getElementById("copy-button");_.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(`#SKEETED
31${y}`),_.innerHTML=`
32 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
33 <polyline points="20 6 9 17 4 12"></polyline>
34 </svg>
35 `,setTimeout(()=>{_.innerHTML=`
36 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
37 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
38 <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
39 </svg>
40 `},2e3)}catch{}})}else setTimeout(()=>{t.value="",d(),r.innerHTML="Skeet",r.disabled=!1},2e3)}catch(u){r.innerHTML="Skeet",r.disabled=!1;let f=document.createElement("div");f.className="mt-3 p-2 text-sm text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/50 rounded-lg text-center",f.textContent=u.message||"Failed to post",r.parentNode.appendChild(f),setTimeout(()=>f.remove(),3e3)}}t.addEventListener("input",d),c.addEventListener("click",h),r.addEventListener("click",w)}function ft(){chrome.storage.local.get(["theme"],({theme:e})=>{e==="dark"||!e&&window.matchMedia("(prefers-color-scheme: dark)").matches?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")}),document.getElementById("theme-toggle").addEventListener("click",()=>{let e=document.documentElement.classList.toggle("dark");chrome.storage.local.set({theme:e?"dark":"light"})}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",e=>{chrome.storage.local.get(["theme"],({theme:r})=>{r||(e.matches?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark"))})})}function re(t){let e=document.getElementById("user-section");e.innerHTML=`
41 <div class="flex items-end justify-between">
42 <img src="${t.avatar}" alt="Profile Avatar" class="w-8 rounded-full">
43 <div class="ml-[3px]">
44 <div class="font-medium text-xs text-gray-900 dark:text-gray-100">${t.displayName}</div>
45 <div class="text-xs text-gray-500 dark:text-gray-400">@${t.userHandle}</div>
46 </div>
47 </div>
48 `}(async()=>{let t=await K([k.userDid,k.userHandle,k.colorScheme]);ft();let e=E(document.getElementById("login-container")),r=E(document.getElementById("loginForm")),n=E(document.getElementById("skeet")),o=E(document.getElementById("logoutLink")),a=document.getElementById("user-section"),i=E(document.getElementById("identityInput")),s=E(document.getElementById("loginBtn")),c=E(document.getElementById("status")),d=t[k.userDid],h=t[k.userHandle];if(await ht(),d&&h){e.classList.add("hidden"),n.classList.remove("hidden"),o.classList.remove("hidden");let{displayName:w,avatar:u}=await z(d);l("info","Got user profile",{displayName:w,avatar:u}),re({displayName:w,userHandle:h,avatar:u}),te()}s.addEventListener("click",async w=>{w.preventDefault(),s.disabled=!0,s.style.cursor="progress",c.innerText="Setting up login page ...",i.disabled=!0,i.style.cursor="progress";try{await D.init(),M(F());let{identity:u,metadata:f}=await fe(i.value),p=await ot({metadata:f,identity:u,scope:"atproto transition:generic"});E(st(E(p))).focus(),c.innerText="Check opened window / tab"}catch(u){c.innerText=u,s.style.cursor="",s.disabled=!1,i.disabled=!1,i.style.cursor=""}return!1})})();