@stacksjs/socials 0.75.445 → 0.75.450
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/drivers/index.d.ts +1 -0
- package/dist/drivers/stacks-issuer.d.ts +47 -0
- package/dist/index.js +2 -2
- package/package.json +7 -7
package/dist/drivers/index.d.ts
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { AbstractProvider } from '../abstract';
|
|
2
|
+
import type { ProviderConfig } from '../abstract';
|
|
3
|
+
import type { ProviderInterface, SocialUser } from '../types';
|
|
4
|
+
export declare interface StacksIssuerConfig extends ProviderConfig {
|
|
5
|
+
issuer: string
|
|
6
|
+
scopes?: string[]
|
|
7
|
+
resource?: string | string[]
|
|
8
|
+
authorizeUrl?: string
|
|
9
|
+
tokenUrl?: string
|
|
10
|
+
revokeUrl?: string
|
|
11
|
+
userInfoUrl: string
|
|
12
|
+
}
|
|
13
|
+
export declare interface StacksIssuerTokens {
|
|
14
|
+
accessToken: string
|
|
15
|
+
refreshToken: string | null
|
|
16
|
+
expiresIn: number | null
|
|
17
|
+
scopes: string[]
|
|
18
|
+
}
|
|
19
|
+
export declare class StacksIssuerError extends Error {
|
|
20
|
+
public readonly code: string;
|
|
21
|
+
public readonly status: number;
|
|
22
|
+
constructor(code: string, status: number);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sign in with another Stacks application that runs `oauthProvider`: one
|
|
26
|
+
* Stacks app as the identity of record, others as relying parties. Always
|
|
27
|
+
* Authorization Code with S256 PKCE and client_secret_basic, the profile the
|
|
28
|
+
* provider supports. The provider has no id_token; the person comes from the
|
|
29
|
+
* issuer's userinfo endpoint, keyed by its pairwise `sub`.
|
|
30
|
+
*
|
|
31
|
+
* Keep the refresh token from {@link getTokens} when the relying party reads
|
|
32
|
+
* the profile again later (continuous profile sync); `getAccessToken` alone
|
|
33
|
+
* drops it.
|
|
34
|
+
*/
|
|
35
|
+
export declare class StacksIssuerProvider extends AbstractProvider implements ProviderInterface {
|
|
36
|
+
protected scopeSeparator: string;
|
|
37
|
+
constructor(config: StacksIssuerConfig);
|
|
38
|
+
protected validateConfig(): void;
|
|
39
|
+
protected getTokenUrl(): string;
|
|
40
|
+
getAuthUrl(): Promise<string>;
|
|
41
|
+
getTokens(code: string): Promise<StacksIssuerTokens>;
|
|
42
|
+
getAccessToken(code: string): Promise<string>;
|
|
43
|
+
refreshTokens(refreshToken: string): Promise<StacksIssuerTokens>;
|
|
44
|
+
revokeToken(token: string, hint?: 'refresh_token' | 'access_token'): Promise<void>;
|
|
45
|
+
getClaims(token: string): Promise<Record<string, unknown>>;
|
|
46
|
+
getUserByToken(token: string): Promise<SocialUser>;
|
|
47
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var
|
|
3
|
-
`),redirectUrl:this.native?"":this.redirectUrl||(y.services.apple?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:y.services.apple?.scopes??["name","email"]};return this.setScopes(e.scopes),e}async getAuthUrl(){if(this.native)throw new l("A native Apple provider redeems the app's codes; the web flow needs the Service ID");let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();this.validateConfig();let s={client_id:t,redirect_uri:r,scope:i.join(" "),state:e,response_type:"code",...this.parameters};if(i.length>0)s.response_mode="form_post";return`${this.baseUrl}/auth/authorize?${new URLSearchParams(s).toString()}`}async getAccessToken(e){let{clientId:t,redirectUrl:r}=this.getConfig();this.validateConfig();let i=await fetch(`${this.baseUrl}/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,...this.native?{}:{redirect_uri:r},client_id:t,client_secret:this.generateClientSecret()})}),s=await i.json();if(!i.ok||s.error)throw Error(`Apple OAuth error: ${s.error_description??s.error??`HTTP ${i.status}`}`);if(!s.id_token)throw Error("Apple OAuth error: token response contained no id_token");return s.id_token}async getUserByToken(e,t={}){let{clientId:r}=this.getConfig(),i;try{i=await _(e,{jwksUrl:`${this.baseUrl}/auth/keys`,issuer:this.baseUrl,audience:r,nonce:t.nonce})}catch(o){throw Error(`Apple OAuth error: ${o instanceof Error?o.message:String(o)}`)}let s=typeof i.email==="string"?i.email:null,n=null;if(i.email_verified===!0||i.email_verified==="true")n=!0;else if(i.email_verified===!1||i.email_verified==="false")n=!1;return{id:String(i.sub),nickname:null,name:"",email:s,emailVerified:n,avatar:null,token:e,raw:i}}generateClientSecret(){let{clientId:e,teamId:t,keyId:r,privateKey:i}=this.getConfig(),s=Math.floor(Date.now()/1000),n={alg:"ES256",kid:r,typ:"JWT"},o={iss:t,iat:s,exp:s+3600,aud:this.baseUrl,sub:e},a=`${this.base64urlJson(n)}.${this.base64urlJson(o)}`,c;try{c=Ce(i)}catch(p){throw new l(`Apple private key could not be parsed: ${p instanceof Error?p.message:String(p)}`)}let u=Ae("sha256",ne.from(a),{key:c,dsaEncoding:"ieee-p1363"});return`${a}.${u.toString("base64url")}`}base64urlJson(e){return ne.from(JSON.stringify(e)).toString("base64url")}validateConfig(){let{clientId:e,teamId:t,keyId:r,privateKey:i,redirectUrl:s}=this.getConfig();if(!e)throw new l(this.native?"Apple client ID (the app's bundle ID) not provided":"Apple client ID (Service ID) not provided");if(!t)throw new l("Apple team ID not provided");if(!r)throw new l("Apple key ID not provided");if(!i)throw new l("Apple private key not provided");if(!s&&!this.native)throw new l("Apple redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/auth/token`}}function Ue(e){let t=/^at:\/\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(String(e||"").trim());if(!t)throw Error(`"${e}" is not a Bluesky post URI.`);return{repo:t[1],collection:t[2],rkey:t[3]}}class oe extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="BlueskyApiError"}get isAuthError(){return this.status===400||this.status===401||this.status===403}}var Re=new TextEncoder;function m(e){return Re.encode(e).length}function $e(e){let t=[],r=/https?:\/\/[^\s<>"']+/g;for(let o of e.matchAll(r)){let a=o[0].replace(/[),.;!?]+$/,"");t.push({byteStart:m(e.slice(0,o.index)),byteEnd:m(e.slice(0,o.index))+m(a),type:"link",value:a})}let i=(o,a)=>t.some((c)=>c.type==="link"&&o<c.byteEnd&&a>c.byteStart),s=/(^|\s)(#[A-Za-z0-9_]+)/g;for(let o of e.matchAll(s)){let a=o[1],c=o[2];if(a===void 0||c===void 0)continue;if(/^#\d+$/.test(c))continue;let u=(o.index??0)+a.length,p=m(e.slice(0,u)),d=p+m(c);if(i(p,d))continue;t.push({byteStart:p,byteEnd:d,type:"tag",value:c.slice(1)})}let n=/(^|\s)(@[a-z0-9][a-z0-9.-]*\.[a-z]{2,})/gi;for(let o of e.matchAll(n)){let a=o[1],c=o[2];if(a===void 0||c===void 0)continue;let u=(o.index??0)+a.length,p=m(e.slice(0,u)),d=p+m(c);if(i(p,d))continue;t.push({byteStart:p,byteEnd:d,type:"mention",value:c.slice(1).replace(/\.+$/,"")})}return t.sort((o,a)=>o.byteStart-a.byteStart)}class C{provider="bluesky";characterLimit=300;service;constructor(e={}){this.service=e.service||"https://bsky.social"}async createSession(e){let t=e.identifier.trim(),r=e.password.trim();if(!t)throw Error("Bluesky identifier is required.");if(!r)throw Error("Bluesky app password is required.");let i=await this.post("/xrpc/com.atproto.server.createSession",{identifier:t,password:r}),s=await this.getProfile({did:i.did,handle:i.handle,accessToken:i.accessJwt,refreshToken:i.refreshJwt}).catch(()=>{return});return{did:i.did,handle:i.handle,displayName:s?.displayName,accessJwt:i.accessJwt,refreshJwt:i.refreshJwt}}async refreshSession(e){if(!e)throw Error("Bluesky refresh token is required.");let t=await this.post("/xrpc/com.atproto.server.refreshSession",void 0,{authorization:`Bearer ${e}`});return{did:t.did,handle:t.handle,accessJwt:t.accessJwt,refreshJwt:t.refreshJwt}}async publish(e,t){let r=e.did||e.handle;if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");if(!r)throw Error("Bluesky identity DID or handle is required.");if(t.text.length>this.characterLimit)throw Error(`Bluesky posts must be ${this.characterLimit} characters or fewer.`);let i={$type:"app.bsky.feed.post",text:t.text,createdAt:t.scheduledAt||new Date().toISOString()};if(t.langs?.length)i.langs=t.langs;if(t.reply)i.reply=t.reply;let s=t.facets??await this.buildFacets(t.text);if(s.length)i.facets=s;if(t.external)i.embed={$type:"app.bsky.embed.external",external:{uri:t.external.uri,title:t.external.title,description:t.external.description||""}};let n=(t.media||[]).filter((a)=>a.bytes?.length).slice(0,4);if(n.length){let a=[];for(let c of n){let u=await this.uploadBlob(e,c.bytes,c.mimeType||"image/jpeg");a.push({image:u,alt:c.altText||""})}i.embed={$type:"app.bsky.embed.images",images:a}}let o=await this.post("/xrpc/com.atproto.repo.createRecord",{repo:r,collection:"app.bsky.feed.post",record:i},{authorization:`Bearer ${e.accessToken}`});return{provider:this.provider,uri:o.uri,cid:o.cid,url:this.toPostUrl(e.handle,o.uri)}}async postMetrics(e,t){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");if(t.length===0)return[];let r=new URL(`${this.service}/xrpc/app.bsky.feed.getPosts`);for(let s of t.slice(0,25))r.searchParams.append("uris",s);return((await this.request(r,{headers:{authorization:`Bearer ${e.accessToken}`}})).posts||[]).map((s)=>({uri:s.uri,likeCount:s.likeCount||0,repostCount:s.repostCount||0,replyCount:s.replyCount||0}))}async timeline(e,t={}){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");let r=new URL(`${this.service}/xrpc/app.bsky.feed.getTimeline`);if(r.searchParams.set("limit",String(Math.min(Math.max(t.limit||30,1),100))),t.cursor)r.searchParams.set("cursor",t.cursor);let i=await this.request(r,{headers:{authorization:`Bearer ${e.accessToken}`}});return{cursor:i.cursor,items:(i.feed||[]).flatMap((s)=>{let n=s.post;if(!n?.uri||!n.author?.handle)return[];return[{uri:n.uri,authorHandle:n.author.handle,authorName:n.author.displayName,authorAvatar:n.author.avatar,postUrl:this.toPostUrl(n.author.handle,n.uri),body:n.record?.text||"",postedAt:n.record?.createdAt||new Date().toISOString(),likeCount:n.likeCount||0,repostCount:n.repostCount||0,replyCount:n.replyCount||0}]})}}async getProfile(e){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");let t=e.did||e.handle;if(!t)throw Error("Bluesky identity DID or handle is required.");let r=new URL(`${this.service}/xrpc/app.bsky.actor.getProfile`);return r.searchParams.set("actor",t),await this.request(r,{headers:{authorization:`Bearer ${e.accessToken}`}})}async buildFacets(e){let t=[];for(let r of $e(e)){let i=null;if(r.type==="link")i={$type:"app.bsky.richtext.facet#link",uri:r.value};else if(r.type==="tag")i={$type:"app.bsky.richtext.facet#tag",tag:r.value};else if(r.type==="mention"){let s=await this.resolveHandle(r.value);if(s)i={$type:"app.bsky.richtext.facet#mention",did:s}}if(i)t.push({index:{byteStart:r.byteStart,byteEnd:r.byteEnd},features:[i]})}return t}async resolveHandle(e){try{let t=new URL(`${this.service}/xrpc/com.atproto.identity.resolveHandle`);return t.searchParams.set("handle",e),(await this.request(t,{})).did||null}catch{return null}}async uploadBlob(e,t,r){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");if(t.length>1e6)throw Error("Bluesky images must be 1MB or smaller.");return(await this.request(new URL(`${this.service}/xrpc/com.atproto.repo.uploadBlob`),{method:"POST",headers:{"content-type":r,authorization:`Bearer ${e.accessToken}`},body:new Uint8Array(t)})).blob}async listAuthoredPosts(e,t={}){let r=e.did||e.handle;if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");if(!r)throw Error("Bluesky identity DID or handle is required.");let i=new URL(`${this.service}/xrpc/com.atproto.repo.listRecords`);if(i.searchParams.set("repo",r),i.searchParams.set("collection","app.bsky.feed.post"),i.searchParams.set("limit",String(Math.min(Math.max(t.limit||100,1),100))),t.cursor)i.searchParams.set("cursor",t.cursor);let s=await this.request(i,{headers:{authorization:`Bearer ${e.accessToken}`}});return{cursor:s.cursor,posts:(s.records||[]).filter((n)=>n?.uri).map((n)=>({uri:n.uri,cid:n.cid,text:n.value?.text,postedAt:n.value?.createdAt,url:e.handle?this.toPostUrl(e.handle,n.uri):void 0}))}}async deletePost(e,t){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");let{repo:r,collection:i,rkey:s}=Ue(t.uri);await this.post("/xrpc/com.atproto.repo.deleteRecord",{repo:r,collection:i,rkey:s},{authorization:`Bearer ${e.accessToken}`})}async post(e,t,r={}){return await this.request(new URL(`${this.service}${e}`),{method:"POST",headers:{...t===void 0?{}:{"content-type":"application/json"},...r},...t===void 0?{}:{body:JSON.stringify(t)}})}async request(e,t){let r=await fetch(e,t),i=await r.text();if(!r.ok)throw new oe(`Bluesky API failed (${r.status}): ${i||r.statusText}`,r.status,i);return i?JSON.parse(i):{}}toPostUrl(e,t){let r=t.split("/").pop();return`https://bsky.app/profile/${e}/post/${r}`}}import{fetcher as ae}from"@stacksjs/api";import{config as P}from"@stacksjs/config";var A="v24.0";class V extends g{baseUrl="https://www.facebook.com";apiUrl="https://graph.facebook.com";get graphVersion(){return P.services.facebook?.graphVersion??A}getConfig(){let e={clientId:this.clientId||(P.services.facebook?.clientId??""),clientSecret:this.clientSecret||(P.services.facebook?.clientSecret??""),redirectUrl:this.redirectUrl||(P.services.facebook?.redirectUrl??""),scopes:P.services.facebook?.scopes??["email","public_profile"]};return this.setScopes(e.scopes),e}async getAuthUrl(){let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/${this.graphVersion}/dialog/oauth?${new URLSearchParams({client_id:t,redirect_uri:r,scope:i.join(","),response_type:"code",...await this.pkceFields(),...this.parameters,state:e}).toString()}`}async getAccessToken(e){let{clientId:t,clientSecret:r,redirectUrl:i}=this.getConfig();this.validateConfig();let s=await ae.get(`${this.apiUrl}/${this.graphVersion}/oauth/access_token?${new URLSearchParams({client_id:t,client_secret:r,redirect_uri:i,code:e,...this.pkceTokenFields()}).toString()}`);if(s.data.error)throw Error(`Facebook OAuth error: ${s.data.error.message}`);return s.data.access_token}async getUserByToken(e){let t=await ae.withHeaders({Authorization:`Bearer ${e}`}).get(`${this.apiUrl}/${this.graphVersion}/me?${new URLSearchParams({fields:"id,name,email,picture"}).toString()}`);return{id:t.data.id,nickname:null,name:t.data.name,email:t.data.email??null,avatar:t.data.picture?.data.url??null,token:e,raw:t.data}}validateConfig(){let{clientId:e,clientSecret:t,redirectUrl:r}=this.getConfig();if(!e)throw new l("Facebook client ID not provided");if(!t)throw new l("Facebook client secret not provided");if(!r)throw new l("Facebook redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/${this.graphVersion}/oauth/access_token`}}import{fetcher as z}from"@stacksjs/api";import{config as U}from"@stacksjs/config";class M extends g{baseUrl="https://github.com";apiUrl="https://api.github.com";getConfig(){let e={clientId:this.clientId||(U.services.github?.clientId??""),clientSecret:this.clientSecret||(U.services.github?.clientSecret??""),redirectUrl:this.redirectUrl||(U.services.github?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:U.services.github?.scopes??["read:user","user:email"]};return this.setScopes(e.scopes),e}async getAuthUrl(){let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/login/oauth/authorize?${new URLSearchParams({client_id:t,redirect_uri:r,scope:i.join(" "),state:e,response_type:"code",...await this.pkceFields(),...this.parameters}).toString()}`}async getAccessToken(e){let{clientId:t,clientSecret:r,redirectUrl:i}=this.getConfig();this.validateConfig();let s=await z.post(`${this.baseUrl}/login/oauth/access_token`,{client_id:t,client_secret:r,code:e,redirect_uri:i,...this.pkceTokenFields()});if(s.data.error)throw Error(`GitHub OAuth error: ${s.data.error_description}`);return s.data.access_token}async getUserByToken(e){let[t,r]=await Promise.all([z.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${e}`}).get(`${this.apiUrl}/user`),z.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${e}`}).get(`${this.apiUrl}/user/emails`)]),i=this.pickEmail(r.data);return{id:t.data.id.toString(),nickname:t.data.login,name:t.data.name??t.data.login,email:i?.email??t.data.email??null,emailVerified:i?i.verified:null,avatar:t.data.avatar_url,token:e,raw:t.data}}pickEmail(e){if(!Array.isArray(e)||e.length===0)return null;let t=e.find((i)=>i.primary&&i.verified),r=e.find((i)=>i.verified);return t??r??e.find((i)=>i.primary)??e[0]??null}getEmail(e){return this.pickEmail(e)?.email??null}validateConfig(){let{clientId:e,clientSecret:t,redirectUrl:r}=this.getConfig();if(!e)throw new l("GitHub client ID not provided");if(!t)throw new l("GitHub client secret not provided");if(!r)throw new l("GitHub redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/login/oauth/access_token`}}import{fetcher as ce}from"@stacksjs/api";import{config as R}from"@stacksjs/config";class J extends g{baseUrl="https://accounts.google.com";apiUrl="https://www.googleapis.com";tokenUrl="https://oauth2.googleapis.com/token";tokenInfoUrl="https://oauth2.googleapis.com/tokeninfo";certsUrl="https://www.googleapis.com/oauth2/v3/certs";issuers=["https://accounts.google.com","accounts.google.com"];getConfig(){let e={clientId:this.clientId||(R.services.google?.clientId??""),clientSecret:this.clientSecret||(R.services.google?.clientSecret??""),redirectUrl:this.redirectUrl||(R.services.google?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:R.services.google?.scopes??["openid","email"]};return this.setScopes(e.scopes),e}async getAuthUrl(){let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/o/oauth2/v2/auth?${new URLSearchParams({client_id:t,redirect_uri:r,scope:i.join(" "),state:e,response_type:"code",access_type:"offline",prompt:"consent",...await this.pkceFields(),...this.parameters}).toString()}`}async getAccessToken(e){let{clientId:t,clientSecret:r,redirectUrl:i}=this.getConfig();this.validateConfig();let s=await ce.post(this.tokenUrl,{client_id:t,client_secret:r,code:e,redirect_uri:i,grant_type:"authorization_code",...this.pkceTokenFields()});if(s.data.error)throw Error(`Google OAuth error: ${s.data.error_description}`);return s.data.access_token}async getUserByToken(e){await this.assertTokenWasIssuedToUs(e);let t=await ce.withHeaders({Authorization:`Bearer ${e}`}).get(`${this.apiUrl}/oauth2/v2/userinfo`);return{id:t.data.id,nickname:t.data.given_name,name:t.data.name,email:t.data.email,emailVerified:typeof t.data.verified_email==="boolean"?t.data.verified_email:null,avatar:t.data.picture,token:e,raw:t.data}}async getUserByIdToken(e,t={}){let{clientId:r}=this.getConfig(),i;try{i=await _(e,{jwksUrl:this.certsUrl,issuer:this.issuers,audience:r,nonce:t.nonce})}catch(n){throw Error(`Google OAuth error: ${n instanceof Error?n.message:String(n)}`)}let s=typeof i.email==="string"?i.email:"";return{id:String(i.sub),nickname:typeof i.given_name==="string"?i.given_name:null,name:typeof i.name==="string"?i.name:"",email:s,emailVerified:typeof i.email_verified==="boolean"?i.email_verified:i.email_verified==="true"?!0:i.email_verified==="false"?!1:null,avatar:typeof i.picture==="string"?i.picture:null,token:e,raw:i}}async assertTokenWasIssuedToUs(e){let{clientId:t}=this.getConfig(),r=await fetch(`${this.tokenInfoUrl}?access_token=${encodeURIComponent(e)}`);if(!r.ok)throw Error(`Google OAuth error: access token was not accepted by tokeninfo (HTTP ${r.status})`);let i=await r.json();if(i.error)throw Error(`Google OAuth error: ${i.error_description??i.error}`);let s=i.aud??i.audience;if(!s)throw Error("Google OAuth error: tokeninfo did not report an audience for the access token");if(s!==t)throw Error("Google OAuth error: access token was issued to a different client")}validateConfig(){let{clientId:e,clientSecret:t,redirectUrl:r}=this.getConfig();if(!e)throw new l("Google client ID not provided");if(!t)throw new l("Google client secret not provided");if(!r)throw new l("Google redirect URL not provided")}getTokenUrl(){return this.tokenUrl}}class v extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="InstagramApiError"}get isAuthError(){return this.status===401||this.status===403||this.status===190}}class j{provider="instagram";characterLimit=2200;graphVersion;authBase;graphBase;constructor(e={}){this.graphVersion=e.graphVersion||A,this.authBase=e.authBase||"https://www.facebook.com",this.graphBase=e.graphBase||"https://graph.facebook.com"}getAuthUrl(e){let t=new URLSearchParams({client_id:e.clientId,redirect_uri:e.redirectUrl,scope:e.scopes.join(","),state:e.state,response_type:"code"});return`${this.authBase}/${this.graphVersion}/dialog/oauth?${t.toString()}`}async exchangeCode(e){let t=await this.graph("/oauth/access_token",{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:e.clientId,client_secret:e.clientSecret,redirect_uri:e.redirectUrl,code:e.code}).toString()});if(!t.access_token)throw new v("Facebook did not return an access token.",400,JSON.stringify(t));return{accessToken:t.access_token,expiresIn:t.expires_in}}async resolveAccount(e){let t=new URLSearchParams({fields:"name,access_token,instagram_business_account{id,username}"}),r=await this.graph(`/me/accounts?${t.toString()}`,{method:"GET"},e),i=(r.data||[]).find((n)=>n.instagram_business_account?.id),s=i?.instagram_business_account;if(!s?.id||!i?.access_token)throw new v("No Instagram Business account is linked to your Facebook Pages.",400,JSON.stringify(r));return{igUserId:s.id,username:s.username,pageAccessToken:i.access_token}}async publish(e,t){if(!e.accessToken)throw Error("Instagram access token is missing for this identity.");let r=e.did;if(!r)throw Error("Instagram account id is required to publish.");let i=t.media?.[0];if(!i?.url)throw Error("Instagram requires an image to post.");if(t.text.length>this.characterLimit)throw Error(`Instagram captions must be ${this.characterLimit} characters or fewer.`);let s=await this.graph(`/${r}/media`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({image_url:i.url,caption:t.text}).toString()},e.accessToken);if(!s.id)throw new v("Instagram did not return a media container id.",400,JSON.stringify(s));let n=await this.graph(`/${r}/media_publish`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({creation_id:s.id}).toString()},e.accessToken),o=await this.graph(`/${n.id}?fields=permalink`,{method:"GET"},e.accessToken).catch(()=>{return});return{provider:this.provider,uri:n.id,url:o?.permalink}}async timeline(e,t={}){return{items:[]}}async graph(e,t,r){let i=new Headers(t.headers);if(r)i.set("authorization",`Bearer ${r}`);let s=await fetch(`${this.graphBase}/${this.graphVersion}${e}`,{...t,headers:i}),n=await s.text(),o={};try{o=n?JSON.parse(n):{}}catch{o={}}if(!s.ok||o?.error){let a=o?.error?.message||n||s.statusText;throw new v(`Instagram API failed (${s.status}): ${a}`,s.status,n)}return o}}class f extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="LinkedInApiError"}get isAuthError(){return this.status===401||this.status===403}}class G{provider="linkedin";characterLimit=3000;apiVersion;authBase;apiBase;constructor(e={}){this.apiVersion=e.apiVersion||"202405",this.authBase=e.authBase||"https://www.linkedin.com",this.apiBase=e.apiBase||"https://api.linkedin.com"}getAuthUrl(e){let t=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUrl,scope:e.scopes.join(" "),state:e.state});return`${this.authBase}/oauth/v2/authorization?${t.toString()}`}async exchangeCode(e){let t=new URLSearchParams({grant_type:"authorization_code",code:e.code,redirect_uri:e.redirectUrl,client_id:e.clientId,client_secret:e.clientSecret}),r=await this.request(`${this.authBase}/oauth/v2/accessToken`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:t.toString()});if(!r.access_token)throw new f("LinkedIn did not return an access token.",400,JSON.stringify(r));return{accessToken:r.access_token,expiresIn:r.expires_in,scope:r.scope}}async getProfile(e){if(!e)throw Error("LinkedIn access token is required.");let t=await this.request(`${this.apiBase}/v2/userinfo`,{headers:{authorization:`Bearer ${e}`}});if(!t.sub)throw new f("LinkedIn profile is missing a subject id.",400,JSON.stringify(t));return{sub:t.sub,name:t.name,picture:t.picture}}async publish(e,t){if(!e.accessToken)throw Error("LinkedIn access token is missing for this identity.");let r=e.did;if(!r)throw Error("LinkedIn member URN is required to publish.");if(t.text.length>this.characterLimit)throw Error(`LinkedIn posts must be ${this.characterLimit} characters or fewer.`);let i={author:r,commentary:Oe(t.text),visibility:"PUBLIC",distribution:{feedDistribution:"MAIN_FEED",targetEntities:[],thirdPartyDistributionChannels:[]},lifecycleState:"PUBLISHED",isReshareDisabledByAuthor:!1};if(t.external)i.content={article:{source:t.external.uri,title:t.external.title,description:t.external.description||""}};let s=await fetch(`${this.apiBase}/rest/posts`,{method:"POST",headers:{authorization:`Bearer ${e.accessToken}`,"content-type":"application/json","linkedin-version":this.apiVersion,"x-restli-protocol-version":"2.0.0"},body:JSON.stringify(i)}),n=await s.text();if(!s.ok)throw new f(`LinkedIn API failed (${s.status}): ${n||s.statusText}`,s.status,n);let o=s.headers.get("x-restli-id")||s.headers.get("x-linkedin-id")||"";return{provider:this.provider,uri:o,url:o?`https://www.linkedin.com/feed/update/${o}`:void 0}}async timeline(e,t={}){return{items:[]}}async listAuthoredPosts(e,t={}){if(!e.accessToken)throw Error("LinkedIn access token is missing for this identity.");let r=e.did;if(!r)throw Error("LinkedIn member URN is required to list posts.");let i=Math.min(Math.max(t.limit||50,1),100),s=Number(t.cursor||0)||0,n=new URL(`${this.apiBase}/rest/posts`);n.searchParams.set("q","author"),n.searchParams.set("author",r),n.searchParams.set("count",String(i)),n.searchParams.set("start",String(s));let o;try{o=await this.request(n.toString(),{headers:{authorization:`Bearer ${e.accessToken}`,"linkedin-version":this.apiVersion,"x-restli-protocol-version":"2.0.0"}})}catch(c){if(c instanceof f&&(c.status===401||c.status===403))throw new f("LinkedIn will not list this account's posts - the Posts author finder needs the r_member_social permission, which this app does not hold.",c.status,c.body);throw c}let a=(o.elements||[]).filter((c)=>c?.id).map((c)=>({uri:String(c.id),text:c.commentary,postedAt:c.createdAt?new Date(c.createdAt).toISOString():void 0,url:`https://www.linkedin.com/feed/update/${c.id}`}));return{cursor:a.length===i?String(s+i):void 0,posts:a}}async deletePost(e,t){if(!e.accessToken)throw Error("LinkedIn access token is missing for this identity.");let r=String(t.uri||"").trim();if(!r)throw Error("A LinkedIn post URN is required to delete a post.");let i=await fetch(`${this.apiBase}/rest/posts/${encodeURIComponent(r)}`,{method:"DELETE",headers:{authorization:`Bearer ${e.accessToken}`,"linkedin-version":this.apiVersion,"x-restli-protocol-version":"2.0.0"}});if(!i.ok&&i.status!==404){let s=await i.text().catch(()=>"");throw new f(`LinkedIn API failed (${i.status}): ${s||i.statusText}`,i.status,s)}}async request(e,t){let r=await fetch(e,t),i=await r.text();if(!r.ok)throw new f(`LinkedIn API failed (${r.status}): ${i||r.statusText}`,r.status,i);return i?JSON.parse(i):{}}}function Oe(e){return e.replace(/[\\|{}@[\]()<>#*_~]/g,"\\$&")}class le extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="MastodonApiError"}get isAuthError(){return this.status===401||this.status===403}}function Ee(e){let t=String(e||"").trim().replace(/\/+$/,"");if(!t)throw Error("Mastodon instance URL is required.");let r=/^https?:\/\//i.test(t)?t:`https://${t}`;try{let i=new URL(r);return`${i.protocol}//${i.host}`}catch{throw Error("Mastodon instance URL is invalid.")}}class K{provider="mastodon";characterLimit=500;instanceOf(e){return Ee(e.did||"")}tokenOf(e){if(!e.accessToken)throw Error("Mastodon access token is missing for this identity.");return e.accessToken}async verifyCredentials(e){let t=await this.request(`${this.instanceOf(e)}/api/v1/accounts/verify_credentials`,{headers:{authorization:`Bearer ${this.tokenOf(e)}`}});return{accountId:t.id,username:t.username,displayName:t.display_name||void 0,url:t.url}}async uploadMedia(e,t,r,i){let s=new FormData;if(s.set("file",new Blob([new Uint8Array(t)],{type:r||"image/jpeg"}),"upload"),i)s.set("description",i);return(await this.request(`${this.instanceOf(e)}/api/v2/media`,{method:"POST",headers:{authorization:`Bearer ${this.tokenOf(e)}`},body:s})).id}async publish(e,t){let r=this.instanceOf(e),i=this.tokenOf(e);if(t.text.length>this.characterLimit)throw Error(`Mastodon posts must be ${this.characterLimit} characters or fewer.`);let s=[];for(let a of(t.media||[]).slice(0,4)){let{bytes:c,mimeType:u}=a;if(!c?.length&&a.url){let p=await fetch(a.url);if(!p.ok)continue;c=new Uint8Array(await p.arrayBuffer()),u=u||p.headers.get("content-type")||"image/jpeg"}if(c?.length)s.push(await this.uploadMedia(e,c,u||"image/jpeg",a.altText))}let n={status:t.text,visibility:"public"};if(s.length)n.media_ids=s;if(t.reply?.parent?.uri)n.in_reply_to_id=t.reply.parent.uri;let o=await this.request(`${r}/api/v1/statuses`,{method:"POST",headers:{authorization:`Bearer ${i}`,"content-type":"application/json"},body:JSON.stringify(n)});return{provider:this.provider,uri:o.id,cid:o.id,url:o.url||o.uri}}async timeline(e,t={}){return{items:[]}}async listAuthoredPosts(e,t={}){let r=this.instanceOf(e),{accountId:i}=await this.verifyCredentials(e),s=new URL(`${r}/api/v1/accounts/${encodeURIComponent(i)}/statuses`);if(s.searchParams.set("limit",String(Math.min(Math.max(t.limit||40,1),40))),s.searchParams.set("exclude_reblogs","true"),t.cursor)s.searchParams.set("max_id",t.cursor);let o=(await this.request(s.toString(),{headers:{authorization:`Bearer ${this.tokenOf(e)}`}})||[]).filter((a)=>a?.id).map((a)=>({uri:a.id,cid:a.id,text:a.content,postedAt:a.created_at,url:a.url}));return{cursor:o.length?o[o.length-1]?.uri:void 0,posts:o}}async deletePost(e,t){let r=String(t.cid||Be(t.uri)||"").trim();if(!r)throw Error("A status id is required to delete a post.");await this.request(`${this.instanceOf(e)}/api/v1/statuses/${encodeURIComponent(r)}`,{method:"DELETE",headers:{authorization:`Bearer ${this.tokenOf(e)}`}})}async request(e,t){let r=await fetch(e,t),i=await r.text();if(!r.ok)throw new le(`Mastodon API failed (${r.status}): ${i||r.statusText}`,r.status,i);return i?JSON.parse(i):{}}}function Be(e){return String(e||"").replace(/\/+$/,"").split("/").pop()||""}class k extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="ThreadsApiError"}get isAuthError(){return this.status===401||this.status===403||this.status===190}}class Q{provider="threads";characterLimit=500;graphVersion;authBase;graphBase;constructor(e={}){this.graphVersion=e.graphVersion||"v1.0",this.authBase=e.authBase||"https://threads.net",this.graphBase=e.graphBase||"https://graph.threads.net"}getAuthUrl(e){let t=new URLSearchParams({client_id:e.clientId,redirect_uri:e.redirectUrl,scope:e.scopes.join(","),response_type:"code",state:e.state});return`${this.authBase}/oauth/authorize?${t.toString()}`}async exchangeCode(e){let t=await fetch(`${this.graphBase}/oauth/access_token`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:e.clientId,client_secret:e.clientSecret,grant_type:"authorization_code",redirect_uri:e.redirectUrl,code:e.code}).toString()}),r=await t.text(),i={};try{i=r?JSON.parse(r):{}}catch{i={}}if(!t.ok||i?.error||!i?.access_token){let s=i?.error_message||i?.error?.message||r||t.statusText;throw new k(`Threads token exchange failed (${t.status}): ${s}`,t.status,r)}return{accessToken:i.access_token,userId:i.user_id!=null?String(i.user_id):void 0,expiresIn:i.expires_in}}async resolveAccount(e){let t=new URLSearchParams({fields:"id,username"}),r=await this.graph(`/me?${t.toString()}`,{method:"GET"},e);if(!r.id)throw new k("Could not resolve the Threads account for this token.",400,JSON.stringify(r));return{threadsUserId:r.id,username:r.username,accessToken:e}}async publish(e,t){if(!e.accessToken)throw Error("Threads access token is missing for this identity.");let r=e.did;if(!r)throw Error("Threads account id is required to publish.");if(t.text.length>this.characterLimit)throw Error(`Threads posts must be ${this.characterLimit} characters or fewer.`);let i=t.media?.[0],s=new URLSearchParams({text:t.text});if(i?.url)s.set("media_type","IMAGE"),s.set("image_url",i.url);else s.set("media_type","TEXT");let n=await this.graph(`/${r}/threads`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:s.toString()},e.accessToken);if(!n.id)throw new k("Threads did not return a media container id.",400,JSON.stringify(n));let o=await this.graph(`/${r}/threads_publish`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({creation_id:n.id}).toString()},e.accessToken);if(!o.id)throw new k("Threads did not return a published post id.",400,JSON.stringify(o));let a=await this.graph(`/${o.id}?fields=permalink`,{method:"GET"},e.accessToken).catch(()=>{return});return{provider:this.provider,uri:o.id,url:a?.permalink}}async timeline(e,t={}){return{items:[]}}async graph(e,t,r){let i=new Headers(t.headers);if(r)i.set("authorization",`Bearer ${r}`);let s=await fetch(`${this.graphBase}/${this.graphVersion}${e}`,{...t,headers:i}),n=await s.text(),o={};try{o=n?JSON.parse(n):{}}catch{o={}}if(!s.ok||o?.error){let a=o?.error?.message||n||s.statusText;throw new k(`Threads API failed (${s.status}): ${a}`,s.status,n)}return o}}import{Buffer as De}from"buffer";import{fetcher as de}from"@stacksjs/api";import{config as O}from"@stacksjs/config";class W extends g{baseUrl="https://twitter.com";apiUrl="https://api.twitter.com";_usesPKCE=!0;getConfig(){let e={clientId:this.clientId||(O.services.twitter?.clientId??""),clientSecret:this.clientSecret||(O.services.twitter?.clientSecret??""),redirectUrl:this.redirectUrl||(O.services.twitter?.redirectUrl??""),scopes:O.services.twitter?.scopes??["users.read","tweet.read"]};return this.setScopes(e.scopes),e}async getAuthUrl(){let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/i/oauth2/authorize?${new URLSearchParams({client_id:t,redirect_uri:r,scope:i.join(" "),response_type:"code",...this.parameters,...await this.pkceFields(),state:e}).toString()}`}async getAccessToken(e){let{clientId:t,clientSecret:r,redirectUrl:i}=this.getConfig();this.validateConfig();let s=this.pkceTokenFields(),n=De.from(`${t}:${r}`).toString("base64"),o=await de.asForm().withHeaders({Authorization:`Basic ${n}`}).post(`${this.apiUrl}/2/oauth2/token`,{code:e,grant_type:"authorization_code",redirect_uri:i,...s});if(o.data.error)throw Error(`Twitter OAuth error: ${o.data.error_description}`);return o.data.access_token}async getUserByToken(e){let t=await de.withHeaders({Authorization:`Bearer ${e}`}).get(`${this.apiUrl}/2/users/me?user.fields=profile_image_url`);return{id:t.data.id,nickname:t.data.username,name:t.data.name,email:t.data.email??null,avatar:t.data.profile_image_url??null,token:e,raw:t.data}}validateConfig(){let{clientId:e,clientSecret:t,redirectUrl:r}=this.getConfig();if(!e)throw new l("Twitter client ID not provided");if(!t)throw new l("Twitter client secret not provided");if(!r)throw new l("Twitter redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/2/oauth2/token`}}class w extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="TwitterApiError"}get isAuthError(){return this.status===401||this.status===403}}function ue(e){let t="";for(let r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}class b{provider="twitter";characterLimit=280;apiBase;authorizeBase;constructor(e={}){this.apiBase=e.apiBase||"https://api.twitter.com",this.authorizeBase=e.authorizeBase||"https://twitter.com"}async createAuthorization(e){let t=ue(crypto.getRandomValues(new Uint8Array(32))),r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)),i=ue(new Uint8Array(r)),s=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUrl,scope:e.scopes.join(" "),state:e.state,code_challenge:i,code_challenge_method:"S256"});return{url:`${this.authorizeBase}/i/oauth2/authorize?${s.toString()}`,codeVerifier:t}}async exchangeCode(e){return this.tokenRequest(new URLSearchParams({grant_type:"authorization_code",code:e.code,redirect_uri:e.redirectUrl,code_verifier:e.codeVerifier,client_id:e.clientId}),e.clientId,e.clientSecret)}async refreshAccessToken(e){return this.tokenRequest(new URLSearchParams({grant_type:"refresh_token",refresh_token:e.refreshToken,client_id:e.clientId}),e.clientId,e.clientSecret)}async getProfile(e){let t=await this.request(`${this.apiBase}/2/users/me?user.fields=username,name`,{headers:{authorization:`Bearer ${e}`}});if(!t.data?.id||!t.data.username)throw new w("Twitter did not return the authenticated user.",400,JSON.stringify(t));return{id:t.data.id,username:t.data.username,name:t.data.name}}async uploadMedia(e,t,r){let i=new FormData;i.set("media",new Blob([new Uint8Array(t)],{type:r||"image/jpeg"})),i.set("media_category","tweet_image");let s=await this.request(`${this.apiBase}/2/media/upload`,{method:"POST",headers:{authorization:`Bearer ${e}`},body:i}),n=s.data?.id||s.media_id_string||s.id;if(!n)throw new w("Twitter did not return a media id.",400,JSON.stringify(s));return n}async publish(e,t){if(!e.accessToken)throw Error("Twitter access token is missing for this identity.");if(t.text.length>this.characterLimit)throw Error(`Twitter posts must be ${this.characterLimit} characters or fewer.`);let r=[],i=t.media?.[0];if(i){let{bytes:a,mimeType:c}=i;if(!a?.length&&i.url){let u=await fetch(i.url);if(u.ok)a=new Uint8Array(await u.arrayBuffer()),c=c||u.headers.get("content-type")||"image/jpeg"}if(a?.length)r.push(await this.uploadMedia(e.accessToken,a,c||"image/jpeg"))}let s={text:t.text};if(r.length)s.media={media_ids:r};if(t.reply?.parent?.uri)s.reply={in_reply_to_tweet_id:t.reply.parent.uri};let n=await this.request(`${this.apiBase}/2/tweets`,{method:"POST",headers:{authorization:`Bearer ${e.accessToken}`,"content-type":"application/json"},body:JSON.stringify(s)}),o=n.data?.id;if(!o)throw new w("Twitter did not return a tweet id.",400,JSON.stringify(n));return{provider:this.provider,uri:o,cid:o,url:e.handle?`https://x.com/${e.handle}/status/${o}`:`https://x.com/i/web/status/${o}`}}async timeline(e,t={}){return{items:[]}}async listAuthoredPosts(e,t={}){if(!e.accessToken)throw Error("Twitter access token is missing for this identity.");let r=e.did;if(!r)throw Error("Twitter user id is required to list posts.");let i=new URL(`${this.apiBase}/2/users/${encodeURIComponent(r)}/tweets`);if(i.searchParams.set("max_results",String(Math.min(Math.max(t.limit||100,5),100))),i.searchParams.set("tweet.fields","created_at"),t.cursor)i.searchParams.set("pagination_token",t.cursor);let s=await this.request(i.toString(),{headers:{authorization:`Bearer ${e.accessToken}`}});return{cursor:s.meta?.next_token,posts:(s.data||[]).filter((n)=>n?.id).map((n)=>({uri:n.id,cid:n.id,text:n.text,postedAt:n.created_at,url:`https://x.com/i/web/status/${n.id}`}))}}async deletePost(e,t){if(!e.accessToken)throw Error("Twitter access token is missing for this identity.");let r=String(t.uri||"").trim();if(!r)throw Error("A tweet id is required to delete a post.");let i=await this.request(`${this.apiBase}/2/tweets/${encodeURIComponent(r)}`,{method:"DELETE",headers:{authorization:`Bearer ${e.accessToken}`}});if(i.data&&i.data.deleted===!1)throw new w(`X refused to delete tweet ${r}.`,400,JSON.stringify(i))}async tokenRequest(e,t,r){let i={"content-type":"application/x-www-form-urlencoded"};if(r)i.authorization=`Basic ${btoa(`${t}:${r}`)}`;let s=await this.request(`${this.apiBase}/2/oauth2/token`,{method:"POST",headers:i,body:e.toString()});if(!s.access_token)throw new w("Twitter did not return an access token.",400,JSON.stringify(s));return{accessToken:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,scope:s.scope}}async request(e,t){let r=await fetch(e,t),i=await r.text();if(!r.ok)throw new w(`Twitter API failed (${r.status}): ${i||r.statusText}`,r.status,i);return i?JSON.parse(i):{}}}import{config as Le}from"@stacksjs/config";var E=["clientId","clientSecret","redirectUrl"],I=Object.freeze({google:{name:"google",label:"Google",driver:J,required:E,postCallback:!1,pkce:!1,nonce:!1},github:{name:"github",label:"GitHub",driver:M,required:E,postCallback:!1,pkce:!1,nonce:!1},facebook:{name:"facebook",label:"Facebook",driver:V,required:E,postCallback:!1,pkce:!1,nonce:!1},twitter:{name:"twitter",label:"X",driver:W,required:E,postCallback:!1,pkce:!0,nonce:!1},apple:{name:"apple",label:"Apple",driver:H,required:["clientId","teamId","keyId","privateKey","redirectUrl"],postCallback:!0,pkce:!1,nonce:!0}});function pe(e){return Le?.services?.[e]}function Fe(e){return typeof e==="string"&&e in I}function he(e){if(!Fe(e))return!1;let t=pe(e);if(!t)return!1;return I[e].required.every((r)=>Boolean(t[r]))}function ir(){return Object.keys(I).filter(he).map((e)=>I[e])}function sr(e){if(!he(e))return null;let t=I[e],r=pe(e)??{};return new t.driver({clientSecret:"",...r,clientId:String(r.clientId??""),redirectUrl:String(r.redirectUrl??"")})}class Ne{accessToken;refreshToken;expiresIn;approvedScopes;constructor(e,t=null,r=null,i=[]){this.accessToken=e;this.refreshToken=t;this.expiresIn=r;this.approvedScopes=i}}function ar(e){return typeof e?.deletePost==="function"}function cr(e){return typeof e?.listAuthoredPosts==="function"}import{buildSessionHandoffCodeUrl as qe,buildSessionHandoffUrl as He}from"@stacksjs/composables";function X(e,t=[]){if(!e)return!1;if(e.startsWith("//"))return!1;if(e.startsWith("/"))return!0;try{let r=new URL(e);if(r.protocol!=="http:"&&r.protocol!=="https:")return!1;return t.includes(r.host)}catch{return!1}}function ur(e,t={}){let r=t.redirectTo??"/";if(!X(r,t.allowedHosts??[]))throw Error(`[socials] refusing to hand a session to ${r}: relative paths are always allowed; an absolute URL needs its host in allowedHosts.`);return new Response(null,{status:302,headers:{Location:He(r,e),"Cache-Control":"no-store","Referrer-Policy":"no-referrer"}})}function pr(e,t={}){let r=t.redirectTo??"/";if(!X(r,t.allowedHosts??[]))throw Error(`[socials] refusing to hand a session to ${r}: relative paths are always allowed; an absolute URL needs its host in allowedHosts.`);return new Response(null,{status:302,headers:{Location:qe(r,e),"Cache-Control":"no-store","Referrer-Policy":"no-referrer"}})}function hr(e,t={}){let r=t.redirectTo??"/login";if(!X(r,t.allowedHosts??[]))throw Error(`[socials] refusing to redirect to ${r}`);let i=r.includes("?")?"&":"?",s=`${r}${i}social_error=${encodeURIComponent(e)}`;return new Response(null,{status:302,headers:{Location:s,"Cache-Control":"no-store","Referrer-Policy":"no-referrer"}})}import{Buffer as ge}from"buffer";import{createHash as Ve,randomBytes as ze,timingSafeEqual as Me}from"crypto";function T(e){return Ve("sha256").update(e).digest("hex")}var Je=/^[a-f0-9]{64}$/,je=/^[a-f0-9]{64}$/,Ge=/^[\w.:-]{1,64}$/,Ke=3600000,fe=null;function wr(e){fe=e}function me(){return fe??Xe()}function Z(e=32){return ze(e).toString("hex")}function Qe(e,t){if(e.length!==t.length)return!1;return Me(ge.from(e,"utf8"),ge.from(t,"utf8"))}function Y(e){return`stacks_oauth_${T(e).slice(0,24)}`}async function We(){try{let{config:e}=await import("@stacksjs/config");return String(e.app?.url??"").startsWith("https:")}catch{return!1}}function we(e,t,r,i){return[`${e}=${t}`,`Path=${i.path}`,"HttpOnly",`SameSite=${i.sameSite}`,`Max-Age=${Math.max(0,Math.floor(r))}`,...i.secure?["Secure"]:[]].join("; ")}async function ye(e={}){let t=e.sameSite??"Lax",r=t==="None"?!0:e.secure??await We();return{path:e.path??"/",sameSite:t,secure:r}}async function yr(e){if(!Ge.test(e.purpose??""))throw TypeError("[socials] issueOAuthFlow needs a purpose of 1-64 word characters, dots, colons or dashes.");if(!Number.isFinite(e.ttlMs)||e.ttlMs<=0)throw TypeError("[socials] issueOAuthFlow needs a positive ttlMs.");if(e.challenge!==void 0&&!je.test(e.challenge))throw TypeError("[socials] issueOAuthFlow challenge must be the lowercase hex SHA-256 of the verifier.");if(e.userId!=null&&(!Number.isSafeInteger(e.userId)||e.userId<=0))throw TypeError("[socials] issueOAuthFlow userId must be a positive integer.");let t=Math.min(e.ttlMs,Ke),r=Z(),s=(e.challenge?!1:e.bindBrowser!==!1)?Z():null,n=e.nonce===!0?Z():typeof e.nonce==="string"&&e.nonce?e.nonce:void 0,o=new Date,a=new Date(o.getTime()+t),c=me();try{await c.prune(o)}catch{}await c.insert({stateDigest:T(r),purpose:e.purpose,browserDigest:e.challenge??(s?T(s):null),userId:e.userId??null,nonce:n??null,data:e.data??null,expiresAt:a});let u=s?we(Y(r),s,t/1000,await ye(e.cookie)):null;return{state:r,cookie:u,secret:s,...n!==void 0&&{nonce:n},expiresAt:a}}async function kr(e,t){if(typeof e!=="string"||!Je.test(e))return null;let r=me(),i=T(e),s=await r.find(i);if(!s)return null;if(s.expiresAt.getTime()<=Date.now())return await r.take(i).catch(()=>!1),null;if(s.purpose!==t.purpose)return null;if(s.browserDigest!==null){let n=t.browserSecret;if(typeof n!=="string"||n.length===0||n.length>512)return null;if(!Qe(T(n),s.browserDigest))return null}if(!await r.take(i))return null;return{purpose:s.purpose,userId:s.userId,nonce:s.nonce,data:s.data,expiresAt:s.expiresAt}}function br(e,t){if(!e||!t)return"";let r=Y(t);for(let i of e.split(";")){let s=i.trim();if(s.startsWith(`${r}=`))return s.slice(r.length+1)}return""}async function Sr(e,t={}){return we(Y(e),"",0,await ye(t))}function Pr(){let e=new Map;return{records:e,async insert(t){e.set(t.stateDigest,{...t})},async find(t){let r=e.get(t);return r?{...r}:void 0},async take(t){return e.delete(t)},async prune(t){for(let[r,i]of e)if(i.expiresAt.getTime()<=t.getTime())e.delete(r)}}}var B="oauth_flows";function Xe(){let e=()=>import("@stacksjs/database/runtime");return{async insert(t){let{db:r,sqlDateTime:i}=await e();await r.insertInto(B).values({state_digest:t.stateDigest,purpose:t.purpose,browser_digest:t.browserDigest,user_id:t.userId,nonce:t.nonce,data:t.data?JSON.stringify(t.data):null,expires_at:i(t.expiresAt),created_at:i(new Date)}).execute()},async find(t){let{db:r,parseSqlDateTime:i}=await e(),s=await r.primary.selectFrom(B).where("state_digest","=",t).selectAll().executeTakeFirst();if(!s)return;let n=null;if(typeof s.data==="string"&&s.data)try{let o=JSON.parse(s.data);n=o&&typeof o==="object"?o:null}catch{n=null}return{stateDigest:String(s.state_digest),purpose:String(s.purpose),browserDigest:typeof s.browser_digest==="string"&&s.browser_digest?s.browser_digest:null,userId:s.user_id==null?null:Number(s.user_id),nonce:typeof s.nonce==="string"&&s.nonce?s.nonce:null,data:n,expiresAt:i(s.expires_at)??new Date(0)}},async take(t){let{db:r,mutationCount:i}=await e(),s=await r.deleteFrom(B).where("state_digest","=",t).execute();return i(s)===1},async prune(t){let{db:r,sqlDateTime:i}=await e();await r.deleteFrom(B).where("expires_at","<",i(t)).execute()}}}var Ze={bluesky:["identifier","password","service"],twitter:["clientId","clientSecret","accessToken","refreshToken"],linkedin:["accessToken","memberUrn","clientId","clientSecret","apiVersion"],mastodon:["accessToken","instanceUrl"],instagram:["accessToken","accountId","clientId","clientSecret"],threads:["accessToken","userId","clientId","clientSecret"]},Ye={bluesky:["identifier","password"],twitter:["clientId","clientSecret","accessToken"],linkedin:["accessToken","memberUrn"],mastodon:["accessToken","instanceUrl"],instagram:["accessToken","accountId"],threads:["accessToken","userId"]};function et(e){return e.toUpperCase().replace(/[^A-Z0-9]/g,"")}function D(e,t,r){let i=r.replace(/[A-Z]/g,(s)=>`_${s}`).toUpperCase();return`SOCIALS_${et(e)}_${t.toUpperCase()}_${i}`}class h extends Error{constructor(e){super(e);this.name="SocialIdentityError"}}async function L(){let{config:e,overridesReady:t}=await import("@stacksjs/config");return await t,e.socials??{}}function ke(e,t){let r=Object.keys(e.identities??{});if(t){if(!r.includes(t))throw new h(`Unknown social identity '${t}'. config/socials.ts declares: ${r.length?r.join(", "):"none"}.`);return t}if(e.default){if(!r.includes(e.default))throw new h(`config/socials.ts sets default: '${e.default}', which is not one of its identities: ${r.length?r.join(", "):"none"}.`);return e.default}if(r.length===1)return r[0];if(r.length===0)throw new h("config/socials.ts declares no identities, so there is no account to post as.");throw new h(`config/socials.ts declares ${r.length} identities (${r.join(", ")}) and no default, so a call has to name one.`)}function tt(e,t,r,i=process.env){let n=(e.identities?.[r]??{}).credentials?.[t]??{},o={};for(let a of Ze[t]){let c=(n[a]??i[D(r,t,a)]??"").trim();if(c)o[a]=c}return o}function ee(e,t,r,i=process.env){let s=ke(e,r),n=tt(e,t,s,i),o=Ye[t].filter((a)=>!n[a]);if(o.length){let a=o.map((c)=>D(s,t,c)).join(", ");throw new h(`Identity '${s}' is not configured for ${t}: ${o.join(", ")} ${o.length===1?"is":"are"} missing. Set ${a}, or supply them inline in config/socials.ts.`)}return n}async function be(e){return ke(await L(),e)}async function S(e,t,r){return ee(await L(),e,t,r)}function Ir(e,t){return S("bluesky",e,t)}function Tr(e,t){return S("twitter",e,t)}function xr(e,t){return S("linkedin",e,t)}function _r(e,t){return S("mastodon",e,t)}function Cr(e,t){return S("instagram",e,t)}function Ar(e,t){return S("threads",e,t)}import{randomBytes as rt}from"crypto";function Er(e){return e==="twitter"}var it=["tweet.read","tweet.write","users.read","offline.access"],st="127.0.0.1",Se="/callback";function Br(e){if(!Number.isInteger(e)||e<1||e>65535)throw new h(`${e} is not a port a redirect can listen on.`);return`http://${st}:${e}${Se}`}function Dr(){return rt(16).toString("hex")}function Lr(e,t){let r;try{r=new URL(e)}catch{return{ok:!1,reason:"not-the-callback",message:`Not a URL: ${e}`}}if(r.pathname!==Se)return{ok:!1,reason:"not-the-callback",message:`Ignoring a request for ${r.pathname}.`};let i=r.searchParams.get("error");if(i){let o=r.searchParams.get("error_description");return{ok:!1,reason:"declined",message:o?`${i}: ${o}`:i}}let s=r.searchParams.get("state")??"";if(!t||s!==t)return{ok:!1,reason:"state-mismatch",message:"The redirect carried the wrong state, so it did not come from this run."};let n=r.searchParams.get("code")??"";if(!n)return{ok:!1,reason:"no-code",message:"The redirect carried no authorization code."};return{ok:!0,code:n}}function Fr(e,t,r){let i=[];for(let[s,n]of Object.entries(r)){if(!n)continue;i.push(`${D(e,t,s)}=${n}`)}return i}async function Nr(e){let t=new b,r=await t.exchangeCode(e);if(!r.accessToken)throw new h("X returned no access token for that code.");let i=Boolean(r.refreshToken),s=await t.getProfile(r.accessToken);return{handle:s.username,accountId:s.id,accessToken:r.accessToken,refreshToken:r.refreshToken,expiresIn:r.expiresIn,scope:r.scope,renewable:i}}async function qr(e){return new b().createAuthorization({clientId:e.clientId,redirectUrl:e.redirectUrl,scopes:[...it],state:e.state})}function Hr(e,t){let r=(i)=>i.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");return`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${r(e)}</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;display:grid;place-items:center;min-height:100vh;color:#111}main{max-width:28rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 .5rem}p{margin:0;color:#555}@media(prefers-color-scheme:dark){body{background:#111;color:#eee}p{color:#aaa}}</style></head><body><main><h1>${r(e)}</h1><p>${r(t)}</p></main></body></html>`}var nt={bluesky:null,twitter:null,linkedin:"memberUrn",mastodon:"instanceUrl",instagram:"accountId",threads:"userId"};function ot(e,t){if(e==="bluesky")return t.service?{service:t.service}:{};if(e==="linkedin")return t.apiVersion?{apiVersion:t.apiVersion}:{};return{}}function at(e,t={}){switch(e){case"bluesky":return new C(t);case"linkedin":return new G(t);case"twitter":return new b;case"mastodon":return new K;case"instagram":return new j;case"threads":return new Q;default:throw new h(`No publishing driver for '${String(e)}'.`)}}function ct(e,t,r){let i=nt[e],s={handle:(t??"").trim()};if(r.accessToken)s.accessToken=r.accessToken;if(r.refreshToken)s.refreshToken=r.refreshToken;if(i&&r[i])s.did=r[i];return s}function lt(e,t,r){if(!r)return;if(r.includes(t))return;throw new h(`Identity '${e}' is not set up for ${t}. config/socials.ts lists: ${r.length?r.join(", "):"none"}.`)}async function dt(e,t,r){let i=await L(),s=await be(t),n=i.identities?.[s]??{};lt(s,e,n.platforms);let o=ee(i,e,s,r),a=ot(e,o);if(e==="bluesky"){let c=await new C(a).createSession({identifier:o.identifier,password:o.password});return{name:s,platform:e,identity:{handle:c.handle,did:c.did,accessToken:c.accessJwt,refreshToken:c.refreshJwt},options:a}}return{name:s,platform:e,identity:ct(e,n.handle??"",o),options:a}}async function Jr(e,t,r,i){let s=await dt(e,r,i);return at(s.platform,s.options).publish(s.identity,t)}export{st as AUTHORIZE_HOST,Se as AUTHORIZE_PATH,g as AbstractProvider,H as AppleProvider,oe as BlueskyApiError,C as BlueskyPublishingDriver,l as ConfigException,nt as DRIVER_DID_FIELD,V as FacebookProvider,M as GitHubProvider,J as GoogleProvider,v as InstagramApiError,j as InstagramPublishingDriver,Ie as InvalidStateException,f as LinkedInApiError,G as LinkedInPublishingDriver,A as META_GRAPH_VERSION,le as MastodonApiError,K as MastodonPublishingDriver,Ye as REQUIRED_SOCIAL_CREDENTIALS,Ze as SOCIAL_CREDENTIAL_FIELDS,I as SOCIAL_PROVIDERS,h as SocialIdentityError,it as TWITTER_PUBLISH_SCOPES,k as ThreadsApiError,Q as ThreadsPublishingDriver,Ne as Token,w as TwitterApiError,W as TwitterProvider,b as TwitterPublishingDriver,lt as assertPlatformDeclared,Hr as callbackPage,Br as callbackUri,Sr as clearOAuthFlowCookie,Nr as completeTwitterAuthorization,ir as configuredSocialProviders,kr as consumeOAuthFlow,Xe as databaseOAuthFlowStore,$e as detectFacetCandidates,ot as driverOptions,Fr as envLines,D as envName,et as envSegment,Oe as escapeLinkedInText,Er as isAuthorizablePlatform,X as isSafeHandoffTarget,he as isSocialProviderConfigured,Fe as isSocialProviderName,yr as issueOAuthFlow,Pr as memoryOAuthFlowStore,Dr as newState,Ee as normalizeInstance,Y as oauthFlowCookieName,T as oauthFlowDigest,Ue as parseAtUri,ee as pickCredentials,ke as pickIdentity,Jr as publishAs,at as publishingDriver,Lr as readCallback,tt as readCredentials,br as readOAuthFlowCookie,Ir as resolveBlueskyCredentials,be as resolveIdentityName,Cr as resolveInstagramCredentials,xr as resolveLinkedInCredentials,_r as resolveMastodonCredentials,S as resolveSocialCredentials,Ar as resolveThreadsCredentials,Tr as resolveTwitterCredentials,pr as socialHandoffCodeRedirect,hr as socialHandoffFailureRedirect,ur as socialHandoffRedirect,sr as socialProvider,dt as socialPublishTarget,L as socialsConfig,qr as startTwitterAuthorization,ar as supportsDeletion,cr as supportsEnumeration,ct as toDriverIdentity,wr as useOAuthFlowStore};
|
|
2
|
+
var Ie=import.meta.require;import{Buffer as _}from"buffer";class h{clientId;clientSecret;redirectUrl;parameters={};_scopes=[];scopeSeparator=",";_stateless=!1;_usesPKCE=!1;_codeVerifier=null;_state=null;user=null;constructor(e){this.clientId=e.clientId,this.clientSecret=e.clientSecret,this.redirectUrl=e.redirectUrl}async getCodeFields(e=null){let t={client_id:this.clientId,redirect_uri:this.redirectUrl,scope:this.formatScopes(this.getScopes(),this.scopeSeparator),response_type:"code"};if(this.usesState())t.state=e;return{...t,...await this.pkceFields(),...this.parameters}}formatScopes(e,t){return e.join(t)}async userFromToken(e){return{...await this.getUserByToken(e),token:e}}scopes(e){let t=Array.isArray(e)?e:[e];return this._scopes=[...new Set([...this._scopes,...t])],this}setScopes(e){let t=Array.isArray(e)?e:[e];return this._scopes=[...new Set(t)],this}getScopes(){return this._scopes}setRedirectUrl(e){if(typeof e!=="string"||e.length===0)throw Error("[socials] setRedirectUrl requires a non-empty string");let t;try{t=new URL(e)}catch{throw Error(`[socials] setRedirectUrl: invalid URL: ${e}`)}if(t.protocol!=="https:"&&t.protocol!=="http:")throw Error(`[socials] setRedirectUrl protocol must be http(s)://, got ${t.protocol}`);return this.redirectUrl=e,this}withState(e){if(typeof e!=="string"||e.length===0)throw Error("[socials] withState requires a non-empty string");return this._state=e,this}resolveState(){return this._state??this.getState()}usesState(){return!this._stateless}validateState(e,t){if(typeof e!=="string"||typeof t!=="string")return!1;if(e.length===0||t.length===0)return!1;if(e.length!==t.length)return!1;try{let{timingSafeEqual:r}=Ie("crypto");return r(_.from(e,"utf8"),_.from(t,"utf8"))}catch{let r=0;for(let i=0;i<e.length;i++)r|=e.charCodeAt(i)^t.charCodeAt(i);return r===0}}isStateless(){return this._stateless}stateless(){return this._stateless=!0,this}getState(){let e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e).map((t)=>t.toString(16).padStart(2,"0")).join("")}usesPKCE(){return this._usesPKCE}enablePKCE(){return this._usesPKCE=!0,this}getCodeVerifier(){if(this._codeVerifier===null){let e=new Uint8Array(32);crypto.getRandomValues(e),this._codeVerifier=_.from(e).toString("base64url")}return this._codeVerifier}getPkceVerifier(){return this._codeVerifier}withCodeVerifier(e){if(!e)throw Error("A PKCE code verifier must be a non-empty string");return this._usesPKCE=!0,this._codeVerifier=e,this}async pkceFields(){if(!this.usesPKCE())return{};return{code_challenge:await this.getCodeChallenge(),code_challenge_method:this.getCodeChallengeMethod()}}pkceTokenFields(){if(!this.usesPKCE())return{};if(this._codeVerifier===null)throw Error("PKCE is enabled but no code verifier is known for this exchange. Pass the one from getPkceVerifier() back with withCodeVerifier().");return{code_verifier:this._codeVerifier}}async getCodeChallenge(){let t=new TextEncoder().encode(this.getCodeVerifier()),r=await crypto.subtle.digest("SHA-256",t);return _.from(new Uint8Array(r)).toString("base64url")}getCodeChallengeMethod(){return"S256"}with(e){return this.parameters=e,this}async buildAuthUrlFromBase(e,t){let r=new URLSearchParams(await this.getCodeFields(t));return`${e}?${r.toString()}`}}import{Buffer as oe}from"buffer";import{createPrivateKey as Ue,sign as Ae}from"crypto";import{config as y}from"@stacksjs/config";class Te extends Error{constructor(e="Invalid state"){super(e);this.name="InvalidStateException"}}class l extends Error{constructor(e){super(e);this.name="ConfigException"}}import{Buffer as q}from"buffer";var ne=new Map,xe=600000,_e=60000;async function ie(e,t){let r=await fetch(e);if(!r.ok)throw Error(`id_token verification failed: could not fetch signing keys from ${e} (HTTP ${r.status})`);let i=await r.json(),s=Array.isArray(i.keys)?i.keys:[];return ne.set(e,{keys:s,fetchedAt:t}),s}async function Ce(e,t,r){let i=ne.get(e),s=i!==void 0&&r-i.fetchedAt<xe,n=s?i.keys:await ie(e,r),o=n.find((a)=>a.kid===t);if(!o&&s&&r-i.fetchedAt>=_e)n=await ie(e,r),o=n.find((a)=>a.kid===t);if(!o)throw Error(`id_token verification failed: no signing key matches kid "${t}"`);if(o.kty!=="RSA")throw Error(`id_token verification failed: signing key "${t}" is ${o.kty}, expected RSA`);return o}function se(e,t){try{return JSON.parse(q.from(e,"base64url").toString("utf8"))}catch{throw Error(`id_token verification failed: ${t} is not valid JSON`)}}async function C(e,t){let r=e.split(".");if(r.length!==3)throw Error("id_token verification failed: malformed token");let[i,s,n]=r,o=se(i,"header");if(o.alg!=="RS256")throw Error(`id_token verification failed: unsupported algorithm ${JSON.stringify(o.alg)}, expected RS256`);if(typeof o.kid!=="string"||o.kid==="")throw Error("id_token verification failed: header has no kid");let a=t.now??Date.now(),c=await Ce(t.jwksUrl,o.kid,a),u=await crypto.subtle.importKey("jwk",{kty:"RSA",n:c.n,e:c.e,alg:"RS256",ext:!0},{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["verify"]);if(!await crypto.subtle.verify("RSASSA-PKCS1-v1_5",u,q.from(n,"base64url"),q.from(`${i}.${s}`)))throw Error("id_token verification failed: signature does not match the issuer's key");let d=se(s,"payload"),N=(t.clockSkewSeconds??60)*1000,re=Array.isArray(t.issuer)?t.issuer:[t.issuer];if(!re.includes(d.iss))throw Error(`id_token verification failed: issuer is ${JSON.stringify(d.iss)}, expected ${re.map((z)=>JSON.stringify(z)).join(" or ")}`);let ve=Array.isArray(t.audience)?t.audience:[t.audience];if(!(Array.isArray(d.aud)?d.aud:[d.aud]).some((z)=>ve.includes(z)))throw Error("id_token verification failed: token is addressed to a different client");if(typeof d.exp!=="number"||d.exp*1000+N<=a)throw Error("id_token verification failed: token has expired");if(typeof d.iat==="number"&&d.iat*1000-N>a)throw Error("id_token verification failed: token was issued in the future");if(typeof d.nbf==="number"&&d.nbf*1000-N>a)throw Error("id_token verification failed: token is not valid yet");if(t.nonce!==void 0&&d.nonce!==t.nonce)throw Error("id_token verification failed: nonce does not match");if(typeof d.sub!=="string"||d.sub==="")throw Error("id_token verification failed: token has no subject");return d}class H extends h{baseUrl="https://appleid.apple.com";teamId="";keyId="";privateKey="";native=!1;constructor(e){super(e);this.teamId=e.teamId??"",this.keyId=e.keyId??"",this.privateKey=e.privateKey??"",this.native=e.native===!0}getConfig(){let e={clientId:this.clientId||(y.services.apple?.clientId??""),teamId:this.teamId||(y.services.apple?.teamId??""),keyId:this.keyId||(y.services.apple?.keyId??""),privateKey:(this.privateKey||(y.services.apple?.privateKey??"")).replace(/\\n/g,`
|
|
3
|
+
`),redirectUrl:this.native?"":this.redirectUrl||(y.services.apple?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:y.services.apple?.scopes??["name","email"]};return this.setScopes(e.scopes),e}async getAuthUrl(){if(this.native)throw new l("A native Apple provider redeems the app's codes; the web flow needs the Service ID");let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();this.validateConfig();let s={client_id:t,redirect_uri:r,scope:i.join(" "),state:e,response_type:"code",...this.parameters};if(i.length>0)s.response_mode="form_post";return`${this.baseUrl}/auth/authorize?${new URLSearchParams(s).toString()}`}async getAccessToken(e){let{clientId:t,redirectUrl:r}=this.getConfig();this.validateConfig();let i=await fetch(`${this.baseUrl}/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:e,...this.native?{}:{redirect_uri:r},client_id:t,client_secret:this.generateClientSecret()})}),s=await i.json();if(!i.ok||s.error)throw Error(`Apple OAuth error: ${s.error_description??s.error??`HTTP ${i.status}`}`);if(!s.id_token)throw Error("Apple OAuth error: token response contained no id_token");return s.id_token}async getUserByToken(e,t={}){let{clientId:r}=this.getConfig(),i;try{i=await C(e,{jwksUrl:`${this.baseUrl}/auth/keys`,issuer:this.baseUrl,audience:r,nonce:t.nonce})}catch(o){throw Error(`Apple OAuth error: ${o instanceof Error?o.message:String(o)}`)}let s=typeof i.email==="string"?i.email:null,n=null;if(i.email_verified===!0||i.email_verified==="true")n=!0;else if(i.email_verified===!1||i.email_verified==="false")n=!1;return{id:String(i.sub),nickname:null,name:"",email:s,emailVerified:n,avatar:null,token:e,raw:i}}generateClientSecret(){let{clientId:e,teamId:t,keyId:r,privateKey:i}=this.getConfig(),s=Math.floor(Date.now()/1000),n={alg:"ES256",kid:r,typ:"JWT"},o={iss:t,iat:s,exp:s+3600,aud:this.baseUrl,sub:e},a=`${this.base64urlJson(n)}.${this.base64urlJson(o)}`,c;try{c=Ue(i)}catch(p){throw new l(`Apple private key could not be parsed: ${p instanceof Error?p.message:String(p)}`)}let u=Ae("sha256",oe.from(a),{key:c,dsaEncoding:"ieee-p1363"});return`${a}.${u.toString("base64url")}`}base64urlJson(e){return oe.from(JSON.stringify(e)).toString("base64url")}validateConfig(){let{clientId:e,teamId:t,keyId:r,privateKey:i,redirectUrl:s}=this.getConfig();if(!e)throw new l(this.native?"Apple client ID (the app's bundle ID) not provided":"Apple client ID (Service ID) not provided");if(!t)throw new l("Apple team ID not provided");if(!r)throw new l("Apple key ID not provided");if(!i)throw new l("Apple private key not provided");if(!s&&!this.native)throw new l("Apple redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/auth/token`}}function Re(e){let t=/^at:\/\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(String(e||"").trim());if(!t)throw Error(`"${e}" is not a Bluesky post URI.`);return{repo:t[1],collection:t[2],rkey:t[3]}}class ae extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="BlueskyApiError"}get isAuthError(){return this.status===400||this.status===401||this.status===403}}var $e=new TextEncoder;function m(e){return $e.encode(e).length}function Oe(e){let t=[],r=/https?:\/\/[^\s<>"']+/g;for(let o of e.matchAll(r)){let a=o[0].replace(/[),.;!?]+$/,"");t.push({byteStart:m(e.slice(0,o.index)),byteEnd:m(e.slice(0,o.index))+m(a),type:"link",value:a})}let i=(o,a)=>t.some((c)=>c.type==="link"&&o<c.byteEnd&&a>c.byteStart),s=/(^|\s)(#[A-Za-z0-9_]+)/g;for(let o of e.matchAll(s)){let a=o[1],c=o[2];if(a===void 0||c===void 0)continue;if(/^#\d+$/.test(c))continue;let u=(o.index??0)+a.length,p=m(e.slice(0,u)),d=p+m(c);if(i(p,d))continue;t.push({byteStart:p,byteEnd:d,type:"tag",value:c.slice(1)})}let n=/(^|\s)(@[a-z0-9][a-z0-9.-]*\.[a-z]{2,})/gi;for(let o of e.matchAll(n)){let a=o[1],c=o[2];if(a===void 0||c===void 0)continue;let u=(o.index??0)+a.length,p=m(e.slice(0,u)),d=p+m(c);if(i(p,d))continue;t.push({byteStart:p,byteEnd:d,type:"mention",value:c.slice(1).replace(/\.+$/,"")})}return t.sort((o,a)=>o.byteStart-a.byteStart)}class U{provider="bluesky";characterLimit=300;service;constructor(e={}){this.service=e.service||"https://bsky.social"}async createSession(e){let t=e.identifier.trim(),r=e.password.trim();if(!t)throw Error("Bluesky identifier is required.");if(!r)throw Error("Bluesky app password is required.");let i=await this.post("/xrpc/com.atproto.server.createSession",{identifier:t,password:r}),s=await this.getProfile({did:i.did,handle:i.handle,accessToken:i.accessJwt,refreshToken:i.refreshJwt}).catch(()=>{return});return{did:i.did,handle:i.handle,displayName:s?.displayName,accessJwt:i.accessJwt,refreshJwt:i.refreshJwt}}async refreshSession(e){if(!e)throw Error("Bluesky refresh token is required.");let t=await this.post("/xrpc/com.atproto.server.refreshSession",void 0,{authorization:`Bearer ${e}`});return{did:t.did,handle:t.handle,accessJwt:t.accessJwt,refreshJwt:t.refreshJwt}}async publish(e,t){let r=e.did||e.handle;if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");if(!r)throw Error("Bluesky identity DID or handle is required.");if(t.text.length>this.characterLimit)throw Error(`Bluesky posts must be ${this.characterLimit} characters or fewer.`);let i={$type:"app.bsky.feed.post",text:t.text,createdAt:t.scheduledAt||new Date().toISOString()};if(t.langs?.length)i.langs=t.langs;if(t.reply)i.reply=t.reply;let s=t.facets??await this.buildFacets(t.text);if(s.length)i.facets=s;if(t.external)i.embed={$type:"app.bsky.embed.external",external:{uri:t.external.uri,title:t.external.title,description:t.external.description||""}};let n=(t.media||[]).filter((a)=>a.bytes?.length).slice(0,4);if(n.length){let a=[];for(let c of n){let u=await this.uploadBlob(e,c.bytes,c.mimeType||"image/jpeg");a.push({image:u,alt:c.altText||""})}i.embed={$type:"app.bsky.embed.images",images:a}}let o=await this.post("/xrpc/com.atproto.repo.createRecord",{repo:r,collection:"app.bsky.feed.post",record:i},{authorization:`Bearer ${e.accessToken}`});return{provider:this.provider,uri:o.uri,cid:o.cid,url:this.toPostUrl(e.handle,o.uri)}}async postMetrics(e,t){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");if(t.length===0)return[];let r=new URL(`${this.service}/xrpc/app.bsky.feed.getPosts`);for(let s of t.slice(0,25))r.searchParams.append("uris",s);return((await this.request(r,{headers:{authorization:`Bearer ${e.accessToken}`}})).posts||[]).map((s)=>({uri:s.uri,likeCount:s.likeCount||0,repostCount:s.repostCount||0,replyCount:s.replyCount||0}))}async timeline(e,t={}){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");let r=new URL(`${this.service}/xrpc/app.bsky.feed.getTimeline`);if(r.searchParams.set("limit",String(Math.min(Math.max(t.limit||30,1),100))),t.cursor)r.searchParams.set("cursor",t.cursor);let i=await this.request(r,{headers:{authorization:`Bearer ${e.accessToken}`}});return{cursor:i.cursor,items:(i.feed||[]).flatMap((s)=>{let n=s.post;if(!n?.uri||!n.author?.handle)return[];return[{uri:n.uri,authorHandle:n.author.handle,authorName:n.author.displayName,authorAvatar:n.author.avatar,postUrl:this.toPostUrl(n.author.handle,n.uri),body:n.record?.text||"",postedAt:n.record?.createdAt||new Date().toISOString(),likeCount:n.likeCount||0,repostCount:n.repostCount||0,replyCount:n.replyCount||0}]})}}async getProfile(e){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");let t=e.did||e.handle;if(!t)throw Error("Bluesky identity DID or handle is required.");let r=new URL(`${this.service}/xrpc/app.bsky.actor.getProfile`);return r.searchParams.set("actor",t),await this.request(r,{headers:{authorization:`Bearer ${e.accessToken}`}})}async buildFacets(e){let t=[];for(let r of Oe(e)){let i=null;if(r.type==="link")i={$type:"app.bsky.richtext.facet#link",uri:r.value};else if(r.type==="tag")i={$type:"app.bsky.richtext.facet#tag",tag:r.value};else if(r.type==="mention"){let s=await this.resolveHandle(r.value);if(s)i={$type:"app.bsky.richtext.facet#mention",did:s}}if(i)t.push({index:{byteStart:r.byteStart,byteEnd:r.byteEnd},features:[i]})}return t}async resolveHandle(e){try{let t=new URL(`${this.service}/xrpc/com.atproto.identity.resolveHandle`);return t.searchParams.set("handle",e),(await this.request(t,{})).did||null}catch{return null}}async uploadBlob(e,t,r){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");if(t.length>1e6)throw Error("Bluesky images must be 1MB or smaller.");return(await this.request(new URL(`${this.service}/xrpc/com.atproto.repo.uploadBlob`),{method:"POST",headers:{"content-type":r,authorization:`Bearer ${e.accessToken}`},body:new Uint8Array(t)})).blob}async listAuthoredPosts(e,t={}){let r=e.did||e.handle;if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");if(!r)throw Error("Bluesky identity DID or handle is required.");let i=new URL(`${this.service}/xrpc/com.atproto.repo.listRecords`);if(i.searchParams.set("repo",r),i.searchParams.set("collection","app.bsky.feed.post"),i.searchParams.set("limit",String(Math.min(Math.max(t.limit||100,1),100))),t.cursor)i.searchParams.set("cursor",t.cursor);let s=await this.request(i,{headers:{authorization:`Bearer ${e.accessToken}`}});return{cursor:s.cursor,posts:(s.records||[]).filter((n)=>n?.uri).map((n)=>({uri:n.uri,cid:n.cid,text:n.value?.text,postedAt:n.value?.createdAt,url:e.handle?this.toPostUrl(e.handle,n.uri):void 0}))}}async deletePost(e,t){if(!e.accessToken)throw Error("Bluesky access token is missing for this identity.");let{repo:r,collection:i,rkey:s}=Re(t.uri);await this.post("/xrpc/com.atproto.repo.deleteRecord",{repo:r,collection:i,rkey:s},{authorization:`Bearer ${e.accessToken}`})}async post(e,t,r={}){return await this.request(new URL(`${this.service}${e}`),{method:"POST",headers:{...t===void 0?{}:{"content-type":"application/json"},...r},...t===void 0?{}:{body:JSON.stringify(t)}})}async request(e,t){let r=await fetch(e,t),i=await r.text();if(!r.ok)throw new ae(`Bluesky API failed (${r.status}): ${i||r.statusText}`,r.status,i);return i?JSON.parse(i):{}}toPostUrl(e,t){let r=t.split("/").pop();return`https://bsky.app/profile/${e}/post/${r}`}}import{fetcher as ce}from"@stacksjs/api";import{config as P}from"@stacksjs/config";var A="v24.0";class V extends h{baseUrl="https://www.facebook.com";apiUrl="https://graph.facebook.com";get graphVersion(){return P.services.facebook?.graphVersion??A}getConfig(){let e={clientId:this.clientId||(P.services.facebook?.clientId??""),clientSecret:this.clientSecret||(P.services.facebook?.clientSecret??""),redirectUrl:this.redirectUrl||(P.services.facebook?.redirectUrl??""),scopes:P.services.facebook?.scopes??["email","public_profile"]};return this.setScopes(e.scopes),e}async getAuthUrl(){let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/${this.graphVersion}/dialog/oauth?${new URLSearchParams({client_id:t,redirect_uri:r,scope:i.join(","),response_type:"code",...await this.pkceFields(),...this.parameters,state:e}).toString()}`}async getAccessToken(e){let{clientId:t,clientSecret:r,redirectUrl:i}=this.getConfig();this.validateConfig();let s=await ce.get(`${this.apiUrl}/${this.graphVersion}/oauth/access_token?${new URLSearchParams({client_id:t,client_secret:r,redirect_uri:i,code:e,...this.pkceTokenFields()}).toString()}`);if(s.data.error)throw Error(`Facebook OAuth error: ${s.data.error.message}`);return s.data.access_token}async getUserByToken(e){let t=await ce.withHeaders({Authorization:`Bearer ${e}`}).get(`${this.apiUrl}/${this.graphVersion}/me?${new URLSearchParams({fields:"id,name,email,picture"}).toString()}`);return{id:t.data.id,nickname:null,name:t.data.name,email:t.data.email??null,avatar:t.data.picture?.data.url??null,token:e,raw:t.data}}validateConfig(){let{clientId:e,clientSecret:t,redirectUrl:r}=this.getConfig();if(!e)throw new l("Facebook client ID not provided");if(!t)throw new l("Facebook client secret not provided");if(!r)throw new l("Facebook redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/${this.graphVersion}/oauth/access_token`}}import{fetcher as M}from"@stacksjs/api";import{config as R}from"@stacksjs/config";class j extends h{baseUrl="https://github.com";apiUrl="https://api.github.com";getConfig(){let e={clientId:this.clientId||(R.services.github?.clientId??""),clientSecret:this.clientSecret||(R.services.github?.clientSecret??""),redirectUrl:this.redirectUrl||(R.services.github?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:R.services.github?.scopes??["read:user","user:email"]};return this.setScopes(e.scopes),e}async getAuthUrl(){let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/login/oauth/authorize?${new URLSearchParams({client_id:t,redirect_uri:r,scope:i.join(" "),state:e,response_type:"code",...await this.pkceFields(),...this.parameters}).toString()}`}async getAccessToken(e){let{clientId:t,clientSecret:r,redirectUrl:i}=this.getConfig();this.validateConfig();let s=await M.post(`${this.baseUrl}/login/oauth/access_token`,{client_id:t,client_secret:r,code:e,redirect_uri:i,...this.pkceTokenFields()});if(s.data.error)throw Error(`GitHub OAuth error: ${s.data.error_description}`);return s.data.access_token}async getUserByToken(e){let[t,r]=await Promise.all([M.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${e}`}).get(`${this.apiUrl}/user`),M.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${e}`}).get(`${this.apiUrl}/user/emails`)]),i=this.pickEmail(r.data);return{id:t.data.id.toString(),nickname:t.data.login,name:t.data.name??t.data.login,email:i?.email??t.data.email??null,emailVerified:i?i.verified:null,avatar:t.data.avatar_url,token:e,raw:t.data}}pickEmail(e){if(!Array.isArray(e)||e.length===0)return null;let t=e.find((i)=>i.primary&&i.verified),r=e.find((i)=>i.verified);return t??r??e.find((i)=>i.primary)??e[0]??null}getEmail(e){return this.pickEmail(e)?.email??null}validateConfig(){let{clientId:e,clientSecret:t,redirectUrl:r}=this.getConfig();if(!e)throw new l("GitHub client ID not provided");if(!t)throw new l("GitHub client secret not provided");if(!r)throw new l("GitHub redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/login/oauth/access_token`}}import{fetcher as le}from"@stacksjs/api";import{config as O}from"@stacksjs/config";class J extends h{baseUrl="https://accounts.google.com";apiUrl="https://www.googleapis.com";tokenUrl="https://oauth2.googleapis.com/token";tokenInfoUrl="https://oauth2.googleapis.com/tokeninfo";certsUrl="https://www.googleapis.com/oauth2/v3/certs";issuers=["https://accounts.google.com","accounts.google.com"];getConfig(){let e={clientId:this.clientId||(O.services.google?.clientId??""),clientSecret:this.clientSecret||(O.services.google?.clientSecret??""),redirectUrl:this.redirectUrl||(O.services.google?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:O.services.google?.scopes??["openid","email"]};return this.setScopes(e.scopes),e}async getAuthUrl(){let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/o/oauth2/v2/auth?${new URLSearchParams({client_id:t,redirect_uri:r,scope:i.join(" "),state:e,response_type:"code",access_type:"offline",prompt:"consent",...await this.pkceFields(),...this.parameters}).toString()}`}async getAccessToken(e){let{clientId:t,clientSecret:r,redirectUrl:i}=this.getConfig();this.validateConfig();let s=await le.post(this.tokenUrl,{client_id:t,client_secret:r,code:e,redirect_uri:i,grant_type:"authorization_code",...this.pkceTokenFields()});if(s.data.error)throw Error(`Google OAuth error: ${s.data.error_description}`);return s.data.access_token}async getUserByToken(e){await this.assertTokenWasIssuedToUs(e);let t=await le.withHeaders({Authorization:`Bearer ${e}`}).get(`${this.apiUrl}/oauth2/v2/userinfo`);return{id:t.data.id,nickname:t.data.given_name,name:t.data.name,email:t.data.email,emailVerified:typeof t.data.verified_email==="boolean"?t.data.verified_email:null,avatar:t.data.picture,token:e,raw:t.data}}async getUserByIdToken(e,t={}){let{clientId:r}=this.getConfig(),i;try{i=await C(e,{jwksUrl:this.certsUrl,issuer:this.issuers,audience:r,nonce:t.nonce})}catch(n){throw Error(`Google OAuth error: ${n instanceof Error?n.message:String(n)}`)}let s=typeof i.email==="string"?i.email:"";return{id:String(i.sub),nickname:typeof i.given_name==="string"?i.given_name:null,name:typeof i.name==="string"?i.name:"",email:s,emailVerified:typeof i.email_verified==="boolean"?i.email_verified:i.email_verified==="true"?!0:i.email_verified==="false"?!1:null,avatar:typeof i.picture==="string"?i.picture:null,token:e,raw:i}}async assertTokenWasIssuedToUs(e){let{clientId:t}=this.getConfig(),r=await fetch(`${this.tokenInfoUrl}?access_token=${encodeURIComponent(e)}`);if(!r.ok)throw Error(`Google OAuth error: access token was not accepted by tokeninfo (HTTP ${r.status})`);let i=await r.json();if(i.error)throw Error(`Google OAuth error: ${i.error_description??i.error}`);let s=i.aud??i.audience;if(!s)throw Error("Google OAuth error: tokeninfo did not report an audience for the access token");if(s!==t)throw Error("Google OAuth error: access token was issued to a different client")}validateConfig(){let{clientId:e,clientSecret:t,redirectUrl:r}=this.getConfig();if(!e)throw new l("Google client ID not provided");if(!t)throw new l("Google client secret not provided");if(!r)throw new l("Google redirect URL not provided")}getTokenUrl(){return this.tokenUrl}}class v extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="InstagramApiError"}get isAuthError(){return this.status===401||this.status===403||this.status===190}}class G{provider="instagram";characterLimit=2200;graphVersion;authBase;graphBase;constructor(e={}){this.graphVersion=e.graphVersion||A,this.authBase=e.authBase||"https://www.facebook.com",this.graphBase=e.graphBase||"https://graph.facebook.com"}getAuthUrl(e){let t=new URLSearchParams({client_id:e.clientId,redirect_uri:e.redirectUrl,scope:e.scopes.join(","),state:e.state,response_type:"code"});return`${this.authBase}/${this.graphVersion}/dialog/oauth?${t.toString()}`}async exchangeCode(e){let t=await this.graph("/oauth/access_token",{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:e.clientId,client_secret:e.clientSecret,redirect_uri:e.redirectUrl,code:e.code}).toString()});if(!t.access_token)throw new v("Facebook did not return an access token.",400,JSON.stringify(t));return{accessToken:t.access_token,expiresIn:t.expires_in}}async resolveAccount(e){let t=new URLSearchParams({fields:"name,access_token,instagram_business_account{id,username}"}),r=await this.graph(`/me/accounts?${t.toString()}`,{method:"GET"},e),i=(r.data||[]).find((n)=>n.instagram_business_account?.id),s=i?.instagram_business_account;if(!s?.id||!i?.access_token)throw new v("No Instagram Business account is linked to your Facebook Pages.",400,JSON.stringify(r));return{igUserId:s.id,username:s.username,pageAccessToken:i.access_token}}async publish(e,t){if(!e.accessToken)throw Error("Instagram access token is missing for this identity.");let r=e.did;if(!r)throw Error("Instagram account id is required to publish.");let i=t.media?.[0];if(!i?.url)throw Error("Instagram requires an image to post.");if(t.text.length>this.characterLimit)throw Error(`Instagram captions must be ${this.characterLimit} characters or fewer.`);let s=await this.graph(`/${r}/media`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({image_url:i.url,caption:t.text}).toString()},e.accessToken);if(!s.id)throw new v("Instagram did not return a media container id.",400,JSON.stringify(s));let n=await this.graph(`/${r}/media_publish`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({creation_id:s.id}).toString()},e.accessToken),o=await this.graph(`/${n.id}?fields=permalink`,{method:"GET"},e.accessToken).catch(()=>{return});return{provider:this.provider,uri:n.id,url:o?.permalink}}async timeline(e,t={}){return{items:[]}}async graph(e,t,r){let i=new Headers(t.headers);if(r)i.set("authorization",`Bearer ${r}`);let s=await fetch(`${this.graphBase}/${this.graphVersion}${e}`,{...t,headers:i}),n=await s.text(),o={};try{o=n?JSON.parse(n):{}}catch{o={}}if(!s.ok||o?.error){let a=o?.error?.message||n||s.statusText;throw new v(`Instagram API failed (${s.status}): ${a}`,s.status,n)}return o}}class f extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="LinkedInApiError"}get isAuthError(){return this.status===401||this.status===403}}class K{provider="linkedin";characterLimit=3000;apiVersion;authBase;apiBase;constructor(e={}){this.apiVersion=e.apiVersion||"202405",this.authBase=e.authBase||"https://www.linkedin.com",this.apiBase=e.apiBase||"https://api.linkedin.com"}getAuthUrl(e){let t=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUrl,scope:e.scopes.join(" "),state:e.state});return`${this.authBase}/oauth/v2/authorization?${t.toString()}`}async exchangeCode(e){let t=new URLSearchParams({grant_type:"authorization_code",code:e.code,redirect_uri:e.redirectUrl,client_id:e.clientId,client_secret:e.clientSecret}),r=await this.request(`${this.authBase}/oauth/v2/accessToken`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:t.toString()});if(!r.access_token)throw new f("LinkedIn did not return an access token.",400,JSON.stringify(r));return{accessToken:r.access_token,expiresIn:r.expires_in,scope:r.scope}}async getProfile(e){if(!e)throw Error("LinkedIn access token is required.");let t=await this.request(`${this.apiBase}/v2/userinfo`,{headers:{authorization:`Bearer ${e}`}});if(!t.sub)throw new f("LinkedIn profile is missing a subject id.",400,JSON.stringify(t));return{sub:t.sub,name:t.name,picture:t.picture}}async publish(e,t){if(!e.accessToken)throw Error("LinkedIn access token is missing for this identity.");let r=e.did;if(!r)throw Error("LinkedIn member URN is required to publish.");if(t.text.length>this.characterLimit)throw Error(`LinkedIn posts must be ${this.characterLimit} characters or fewer.`);let i={author:r,commentary:Ee(t.text),visibility:"PUBLIC",distribution:{feedDistribution:"MAIN_FEED",targetEntities:[],thirdPartyDistributionChannels:[]},lifecycleState:"PUBLISHED",isReshareDisabledByAuthor:!1};if(t.external)i.content={article:{source:t.external.uri,title:t.external.title,description:t.external.description||""}};let s=await fetch(`${this.apiBase}/rest/posts`,{method:"POST",headers:{authorization:`Bearer ${e.accessToken}`,"content-type":"application/json","linkedin-version":this.apiVersion,"x-restli-protocol-version":"2.0.0"},body:JSON.stringify(i)}),n=await s.text();if(!s.ok)throw new f(`LinkedIn API failed (${s.status}): ${n||s.statusText}`,s.status,n);let o=s.headers.get("x-restli-id")||s.headers.get("x-linkedin-id")||"";return{provider:this.provider,uri:o,url:o?`https://www.linkedin.com/feed/update/${o}`:void 0}}async timeline(e,t={}){return{items:[]}}async listAuthoredPosts(e,t={}){if(!e.accessToken)throw Error("LinkedIn access token is missing for this identity.");let r=e.did;if(!r)throw Error("LinkedIn member URN is required to list posts.");let i=Math.min(Math.max(t.limit||50,1),100),s=Number(t.cursor||0)||0,n=new URL(`${this.apiBase}/rest/posts`);n.searchParams.set("q","author"),n.searchParams.set("author",r),n.searchParams.set("count",String(i)),n.searchParams.set("start",String(s));let o;try{o=await this.request(n.toString(),{headers:{authorization:`Bearer ${e.accessToken}`,"linkedin-version":this.apiVersion,"x-restli-protocol-version":"2.0.0"}})}catch(c){if(c instanceof f&&(c.status===401||c.status===403))throw new f("LinkedIn will not list this account's posts - the Posts author finder needs the r_member_social permission, which this app does not hold.",c.status,c.body);throw c}let a=(o.elements||[]).filter((c)=>c?.id).map((c)=>({uri:String(c.id),text:c.commentary,postedAt:c.createdAt?new Date(c.createdAt).toISOString():void 0,url:`https://www.linkedin.com/feed/update/${c.id}`}));return{cursor:a.length===i?String(s+i):void 0,posts:a}}async deletePost(e,t){if(!e.accessToken)throw Error("LinkedIn access token is missing for this identity.");let r=String(t.uri||"").trim();if(!r)throw Error("A LinkedIn post URN is required to delete a post.");let i=await fetch(`${this.apiBase}/rest/posts/${encodeURIComponent(r)}`,{method:"DELETE",headers:{authorization:`Bearer ${e.accessToken}`,"linkedin-version":this.apiVersion,"x-restli-protocol-version":"2.0.0"}});if(!i.ok&&i.status!==404){let s=await i.text().catch(()=>"");throw new f(`LinkedIn API failed (${i.status}): ${s||i.statusText}`,i.status,s)}}async request(e,t){let r=await fetch(e,t),i=await r.text();if(!r.ok)throw new f(`LinkedIn API failed (${r.status}): ${i||r.statusText}`,r.status,i);return i?JSON.parse(i):{}}}function Ee(e){return e.replace(/[\\|{}@[\]()<>#*_~]/g,"\\$&")}class de extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="MastodonApiError"}get isAuthError(){return this.status===401||this.status===403}}function Be(e){let t=String(e||"").trim().replace(/\/+$/,"");if(!t)throw Error("Mastodon instance URL is required.");let r=/^https?:\/\//i.test(t)?t:`https://${t}`;try{let i=new URL(r);return`${i.protocol}//${i.host}`}catch{throw Error("Mastodon instance URL is invalid.")}}class Q{provider="mastodon";characterLimit=500;instanceOf(e){return Be(e.did||"")}tokenOf(e){if(!e.accessToken)throw Error("Mastodon access token is missing for this identity.");return e.accessToken}async verifyCredentials(e){let t=await this.request(`${this.instanceOf(e)}/api/v1/accounts/verify_credentials`,{headers:{authorization:`Bearer ${this.tokenOf(e)}`}});return{accountId:t.id,username:t.username,displayName:t.display_name||void 0,url:t.url}}async uploadMedia(e,t,r,i){let s=new FormData;if(s.set("file",new Blob([new Uint8Array(t)],{type:r||"image/jpeg"}),"upload"),i)s.set("description",i);return(await this.request(`${this.instanceOf(e)}/api/v2/media`,{method:"POST",headers:{authorization:`Bearer ${this.tokenOf(e)}`},body:s})).id}async publish(e,t){let r=this.instanceOf(e),i=this.tokenOf(e);if(t.text.length>this.characterLimit)throw Error(`Mastodon posts must be ${this.characterLimit} characters or fewer.`);let s=[];for(let a of(t.media||[]).slice(0,4)){let{bytes:c,mimeType:u}=a;if(!c?.length&&a.url){let p=await fetch(a.url);if(!p.ok)continue;c=new Uint8Array(await p.arrayBuffer()),u=u||p.headers.get("content-type")||"image/jpeg"}if(c?.length)s.push(await this.uploadMedia(e,c,u||"image/jpeg",a.altText))}let n={status:t.text,visibility:"public"};if(s.length)n.media_ids=s;if(t.reply?.parent?.uri)n.in_reply_to_id=t.reply.parent.uri;let o=await this.request(`${r}/api/v1/statuses`,{method:"POST",headers:{authorization:`Bearer ${i}`,"content-type":"application/json"},body:JSON.stringify(n)});return{provider:this.provider,uri:o.id,cid:o.id,url:o.url||o.uri}}async timeline(e,t={}){return{items:[]}}async listAuthoredPosts(e,t={}){let r=this.instanceOf(e),{accountId:i}=await this.verifyCredentials(e),s=new URL(`${r}/api/v1/accounts/${encodeURIComponent(i)}/statuses`);if(s.searchParams.set("limit",String(Math.min(Math.max(t.limit||40,1),40))),s.searchParams.set("exclude_reblogs","true"),t.cursor)s.searchParams.set("max_id",t.cursor);let o=(await this.request(s.toString(),{headers:{authorization:`Bearer ${this.tokenOf(e)}`}})||[]).filter((a)=>a?.id).map((a)=>({uri:a.id,cid:a.id,text:a.content,postedAt:a.created_at,url:a.url}));return{cursor:o.length?o[o.length-1]?.uri:void 0,posts:o}}async deletePost(e,t){let r=String(t.cid||De(t.uri)||"").trim();if(!r)throw Error("A status id is required to delete a post.");await this.request(`${this.instanceOf(e)}/api/v1/statuses/${encodeURIComponent(r)}`,{method:"DELETE",headers:{authorization:`Bearer ${this.tokenOf(e)}`}})}async request(e,t){let r=await fetch(e,t),i=await r.text();if(!r.ok)throw new de(`Mastodon API failed (${r.status}): ${i||r.statusText}`,r.status,i);return i?JSON.parse(i):{}}}function De(e){return String(e||"").replace(/\/+$/,"").split("/").pop()||""}class k extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="ThreadsApiError"}get isAuthError(){return this.status===401||this.status===403||this.status===190}}class W{provider="threads";characterLimit=500;graphVersion;authBase;graphBase;constructor(e={}){this.graphVersion=e.graphVersion||"v1.0",this.authBase=e.authBase||"https://threads.net",this.graphBase=e.graphBase||"https://graph.threads.net"}getAuthUrl(e){let t=new URLSearchParams({client_id:e.clientId,redirect_uri:e.redirectUrl,scope:e.scopes.join(","),response_type:"code",state:e.state});return`${this.authBase}/oauth/authorize?${t.toString()}`}async exchangeCode(e){let t=await fetch(`${this.graphBase}/oauth/access_token`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:e.clientId,client_secret:e.clientSecret,grant_type:"authorization_code",redirect_uri:e.redirectUrl,code:e.code}).toString()}),r=await t.text(),i={};try{i=r?JSON.parse(r):{}}catch{i={}}if(!t.ok||i?.error||!i?.access_token){let s=i?.error_message||i?.error?.message||r||t.statusText;throw new k(`Threads token exchange failed (${t.status}): ${s}`,t.status,r)}return{accessToken:i.access_token,userId:i.user_id!=null?String(i.user_id):void 0,expiresIn:i.expires_in}}async resolveAccount(e){let t=new URLSearchParams({fields:"id,username"}),r=await this.graph(`/me?${t.toString()}`,{method:"GET"},e);if(!r.id)throw new k("Could not resolve the Threads account for this token.",400,JSON.stringify(r));return{threadsUserId:r.id,username:r.username,accessToken:e}}async publish(e,t){if(!e.accessToken)throw Error("Threads access token is missing for this identity.");let r=e.did;if(!r)throw Error("Threads account id is required to publish.");if(t.text.length>this.characterLimit)throw Error(`Threads posts must be ${this.characterLimit} characters or fewer.`);let i=t.media?.[0],s=new URLSearchParams({text:t.text});if(i?.url)s.set("media_type","IMAGE"),s.set("image_url",i.url);else s.set("media_type","TEXT");let n=await this.graph(`/${r}/threads`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:s.toString()},e.accessToken);if(!n.id)throw new k("Threads did not return a media container id.",400,JSON.stringify(n));let o=await this.graph(`/${r}/threads_publish`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({creation_id:n.id}).toString()},e.accessToken);if(!o.id)throw new k("Threads did not return a published post id.",400,JSON.stringify(o));let a=await this.graph(`/${o.id}?fields=permalink`,{method:"GET"},e.accessToken).catch(()=>{return});return{provider:this.provider,uri:o.id,url:a?.permalink}}async timeline(e,t={}){return{items:[]}}async graph(e,t,r){let i=new Headers(t.headers);if(r)i.set("authorization",`Bearer ${r}`);let s=await fetch(`${this.graphBase}/${this.graphVersion}${e}`,{...t,headers:i}),n=await s.text(),o={};try{o=n?JSON.parse(n):{}}catch{o={}}if(!s.ok||o?.error){let a=o?.error?.message||n||s.statusText;throw new k(`Threads API failed (${s.status}): ${a}`,s.status,n)}return o}}import{Buffer as Le}from"buffer";import{fetcher as ue}from"@stacksjs/api";import{config as E}from"@stacksjs/config";class X extends h{baseUrl="https://twitter.com";apiUrl="https://api.twitter.com";_usesPKCE=!0;getConfig(){let e={clientId:this.clientId||(E.services.twitter?.clientId??""),clientSecret:this.clientSecret||(E.services.twitter?.clientSecret??""),redirectUrl:this.redirectUrl||(E.services.twitter?.redirectUrl??""),scopes:E.services.twitter?.scopes??["users.read","tweet.read"]};return this.setScopes(e.scopes),e}async getAuthUrl(){let e=this.resolveState(),{clientId:t,redirectUrl:r,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/i/oauth2/authorize?${new URLSearchParams({client_id:t,redirect_uri:r,scope:i.join(" "),response_type:"code",...this.parameters,...await this.pkceFields(),state:e}).toString()}`}async getAccessToken(e){let{clientId:t,clientSecret:r,redirectUrl:i}=this.getConfig();this.validateConfig();let s=this.pkceTokenFields(),n=Le.from(`${t}:${r}`).toString("base64"),o=await ue.asForm().withHeaders({Authorization:`Basic ${n}`}).post(`${this.apiUrl}/2/oauth2/token`,{code:e,grant_type:"authorization_code",redirect_uri:i,...s});if(o.data.error)throw Error(`Twitter OAuth error: ${o.data.error_description}`);return o.data.access_token}async getUserByToken(e){let t=await ue.withHeaders({Authorization:`Bearer ${e}`}).get(`${this.apiUrl}/2/users/me?user.fields=profile_image_url`);return{id:t.data.id,nickname:t.data.username,name:t.data.name,email:t.data.email??null,avatar:t.data.profile_image_url??null,token:e,raw:t.data}}validateConfig(){let{clientId:e,clientSecret:t,redirectUrl:r}=this.getConfig();if(!e)throw new l("Twitter client ID not provided");if(!t)throw new l("Twitter client secret not provided");if(!r)throw new l("Twitter redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/2/oauth2/token`}}class w extends Error{status;body;constructor(e,t,r){super(e);this.status=t;this.body=r;this.name="TwitterApiError"}get isAuthError(){return this.status===401||this.status===403}}function pe(e){let t="";for(let r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}class b{provider="twitter";characterLimit=280;apiBase;authorizeBase;constructor(e={}){this.apiBase=e.apiBase||"https://api.twitter.com",this.authorizeBase=e.authorizeBase||"https://twitter.com"}async createAuthorization(e){let t=pe(crypto.getRandomValues(new Uint8Array(32))),r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)),i=pe(new Uint8Array(r)),s=new URLSearchParams({response_type:"code",client_id:e.clientId,redirect_uri:e.redirectUrl,scope:e.scopes.join(" "),state:e.state,code_challenge:i,code_challenge_method:"S256"});return{url:`${this.authorizeBase}/i/oauth2/authorize?${s.toString()}`,codeVerifier:t}}async exchangeCode(e){return this.tokenRequest(new URLSearchParams({grant_type:"authorization_code",code:e.code,redirect_uri:e.redirectUrl,code_verifier:e.codeVerifier,client_id:e.clientId}),e.clientId,e.clientSecret)}async refreshAccessToken(e){return this.tokenRequest(new URLSearchParams({grant_type:"refresh_token",refresh_token:e.refreshToken,client_id:e.clientId}),e.clientId,e.clientSecret)}async getProfile(e){let t=await this.request(`${this.apiBase}/2/users/me?user.fields=username,name`,{headers:{authorization:`Bearer ${e}`}});if(!t.data?.id||!t.data.username)throw new w("Twitter did not return the authenticated user.",400,JSON.stringify(t));return{id:t.data.id,username:t.data.username,name:t.data.name}}async uploadMedia(e,t,r){let i=new FormData;i.set("media",new Blob([new Uint8Array(t)],{type:r||"image/jpeg"})),i.set("media_category","tweet_image");let s=await this.request(`${this.apiBase}/2/media/upload`,{method:"POST",headers:{authorization:`Bearer ${e}`},body:i}),n=s.data?.id||s.media_id_string||s.id;if(!n)throw new w("Twitter did not return a media id.",400,JSON.stringify(s));return n}async publish(e,t){if(!e.accessToken)throw Error("Twitter access token is missing for this identity.");if(t.text.length>this.characterLimit)throw Error(`Twitter posts must be ${this.characterLimit} characters or fewer.`);let r=[],i=t.media?.[0];if(i){let{bytes:a,mimeType:c}=i;if(!a?.length&&i.url){let u=await fetch(i.url);if(u.ok)a=new Uint8Array(await u.arrayBuffer()),c=c||u.headers.get("content-type")||"image/jpeg"}if(a?.length)r.push(await this.uploadMedia(e.accessToken,a,c||"image/jpeg"))}let s={text:t.text};if(r.length)s.media={media_ids:r};if(t.reply?.parent?.uri)s.reply={in_reply_to_tweet_id:t.reply.parent.uri};let n=await this.request(`${this.apiBase}/2/tweets`,{method:"POST",headers:{authorization:`Bearer ${e.accessToken}`,"content-type":"application/json"},body:JSON.stringify(s)}),o=n.data?.id;if(!o)throw new w("Twitter did not return a tweet id.",400,JSON.stringify(n));return{provider:this.provider,uri:o,cid:o,url:e.handle?`https://x.com/${e.handle}/status/${o}`:`https://x.com/i/web/status/${o}`}}async timeline(e,t={}){return{items:[]}}async listAuthoredPosts(e,t={}){if(!e.accessToken)throw Error("Twitter access token is missing for this identity.");let r=e.did;if(!r)throw Error("Twitter user id is required to list posts.");let i=new URL(`${this.apiBase}/2/users/${encodeURIComponent(r)}/tweets`);if(i.searchParams.set("max_results",String(Math.min(Math.max(t.limit||100,5),100))),i.searchParams.set("tweet.fields","created_at"),t.cursor)i.searchParams.set("pagination_token",t.cursor);let s=await this.request(i.toString(),{headers:{authorization:`Bearer ${e.accessToken}`}});return{cursor:s.meta?.next_token,posts:(s.data||[]).filter((n)=>n?.id).map((n)=>({uri:n.id,cid:n.id,text:n.text,postedAt:n.created_at,url:`https://x.com/i/web/status/${n.id}`}))}}async deletePost(e,t){if(!e.accessToken)throw Error("Twitter access token is missing for this identity.");let r=String(t.uri||"").trim();if(!r)throw Error("A tweet id is required to delete a post.");let i=await this.request(`${this.apiBase}/2/tweets/${encodeURIComponent(r)}`,{method:"DELETE",headers:{authorization:`Bearer ${e.accessToken}`}});if(i.data&&i.data.deleted===!1)throw new w(`X refused to delete tweet ${r}.`,400,JSON.stringify(i))}async tokenRequest(e,t,r){let i={"content-type":"application/x-www-form-urlencoded"};if(r)i.authorization=`Basic ${btoa(`${t}:${r}`)}`;let s=await this.request(`${this.apiBase}/2/oauth2/token`,{method:"POST",headers:i,body:e.toString()});if(!s.access_token)throw new w("Twitter did not return an access token.",400,JSON.stringify(s));return{accessToken:s.access_token,refreshToken:s.refresh_token,expiresIn:s.expires_in,scope:s.scope}}async request(e,t){let r=await fetch(e,t),i=await r.text();if(!r.ok)throw new w(`Twitter API failed (${r.status}): ${i||r.statusText}`,r.status,i);return i?JSON.parse(i):{}}}import{Buffer as Fe}from"buffer";class I extends Error{code;status;constructor(e,t){super(`Stacks issuer refused the request: ${e}`);this.code=e;this.status=t;this.name="StacksIssuerError"}}class Ne extends h{scopeSeparator=" ";settings;constructor(e){super(e);this.settings=e,this._scopes=e.scopes?.length?[...e.scopes]:[],this._usesPKCE=!0}endpoint(e){return(e==="authorize"?this.settings.authorizeUrl:e==="token"?this.settings.tokenUrl:this.settings.revokeUrl)||`${this.settings.issuer.replace(/\/+$/,"")}/${e}`}resources(){let e=this.settings.resource;return e==null?[]:Array.isArray(e)?e:[e]}validateConfig(){if(!this.settings.issuer)throw new l("Stacks issuer not provided");if(!this.clientId)throw new l("Stacks issuer client ID not provided");if(!this.clientSecret)throw new l("Stacks issuer client secret not provided");if(!this.redirectUrl)throw new l("Stacks issuer redirect URL not provided");if(!this.settings.userInfoUrl)throw new l("Stacks issuer userinfo URL not provided")}getTokenUrl(){return this.endpoint("token")}async getAuthUrl(){this.validateConfig();let e=new URLSearchParams({client_id:this.clientId,redirect_uri:this.redirectUrl,response_type:"code",scope:this.formatScopes(this.getScopes(),this.scopeSeparator),state:this.resolveState(),...await this.pkceFields()});for(let t of this.resources())e.append("resource",t);for(let[t,r]of Object.entries(this.parameters))e.set(t,String(r));return`${this.endpoint("authorize")}?${e.toString()}`}async post(e,t){let r=await fetch(e,{method:"POST",headers:{Authorization:`Basic ${Fe.from(`${encodeURIComponent(this.clientId)}:${encodeURIComponent(this.clientSecret)}`).toString("base64")}`,"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:t.toString()}),i=await r.json().catch(()=>({}));if(!r.ok)throw new I(typeof i.error==="string"?i.error:"server_error",r.status);return i}tokens(e){if(typeof e.access_token!=="string"||!e.access_token)throw new I("invalid_token_response",502);return{accessToken:e.access_token,refreshToken:typeof e.refresh_token==="string"?e.refresh_token:null,expiresIn:typeof e.expires_in==="number"?e.expires_in:null,scopes:typeof e.scope==="string"?e.scope.split(" ").filter(Boolean):[]}}async getTokens(e){this.validateConfig();let t=new URLSearchParams({grant_type:"authorization_code",code:e,redirect_uri:this.redirectUrl,...this.pkceTokenFields()});for(let r of this.resources())t.append("resource",r);return this.tokens(await this.post(this.endpoint("token"),t))}async getAccessToken(e){return(await this.getTokens(e)).accessToken}async refreshTokens(e){this.validateConfig();let t=new URLSearchParams({grant_type:"refresh_token",refresh_token:e});for(let r of this.resources())t.append("resource",r);return this.tokens(await this.post(this.endpoint("token"),t))}async revokeToken(e,t="refresh_token"){this.validateConfig(),await this.post(this.endpoint("revoke"),new URLSearchParams({token:e,token_type_hint:t}))}async getClaims(e){this.validateConfig();let t=await fetch(this.settings.userInfoUrl,{headers:{Authorization:`Bearer ${e}`,Accept:"application/json"}}),r=await t.json().catch(()=>({}));if(!t.ok)throw new I(typeof r.error==="string"?r.error:"invalid_token",t.status);if(typeof r.sub!=="string"||!r.sub)throw new I("invalid_userinfo",502);return r}async getUserByToken(e){let t=await this.getClaims(e),r=typeof t.email==="string"?t.email:null;return{id:String(t.sub),nickname:typeof t.preferred_username==="string"?t.preferred_username:null,name:typeof t.name==="string"&&t.name?t.name:r??"",email:r,emailVerified:typeof t.email_verified==="boolean"?t.email_verified:null,avatar:typeof t.picture==="string"?t.picture:null,token:e,raw:t}}}import{config as ze}from"@stacksjs/config";var B=["clientId","clientSecret","redirectUrl"],T=Object.freeze({google:{name:"google",label:"Google",driver:J,required:B,postCallback:!1,pkce:!1,nonce:!1},github:{name:"github",label:"GitHub",driver:j,required:B,postCallback:!1,pkce:!1,nonce:!1},facebook:{name:"facebook",label:"Facebook",driver:V,required:B,postCallback:!1,pkce:!1,nonce:!1},twitter:{name:"twitter",label:"X",driver:X,required:B,postCallback:!1,pkce:!0,nonce:!1},apple:{name:"apple",label:"Apple",driver:H,required:["clientId","teamId","keyId","privateKey","redirectUrl"],postCallback:!0,pkce:!1,nonce:!0}});function he(e){return ze?.services?.[e]}function qe(e){return typeof e==="string"&&e in T}function ge(e){if(!qe(e))return!1;let t=he(e);if(!t)return!1;return T[e].required.every((r)=>Boolean(t[r]))}function dr(){return Object.keys(T).filter(ge).map((e)=>T[e])}function ur(e){if(!ge(e))return null;let t=T[e],r=he(e)??{};return new t.driver({clientSecret:"",...r,clientId:String(r.clientId??""),redirectUrl:String(r.redirectUrl??"")})}class He{accessToken;refreshToken;expiresIn;approvedScopes;constructor(e,t=null,r=null,i=[]){this.accessToken=e;this.refreshToken=t;this.expiresIn=r;this.approvedScopes=i}}function gr(e){return typeof e?.deletePost==="function"}function fr(e){return typeof e?.listAuthoredPosts==="function"}import{buildSessionHandoffCodeUrl as Ve,buildSessionHandoffUrl as Me}from"@stacksjs/composables";function Z(e,t=[]){if(!e)return!1;if(e.startsWith("//"))return!1;if(e.startsWith("/"))return!0;try{let r=new URL(e);if(r.protocol!=="http:"&&r.protocol!=="https:")return!1;return t.includes(r.host)}catch{return!1}}function yr(e,t={}){let r=t.redirectTo??"/";if(!Z(r,t.allowedHosts??[]))throw Error(`[socials] refusing to hand a session to ${r}: relative paths are always allowed; an absolute URL needs its host in allowedHosts.`);return new Response(null,{status:302,headers:{Location:Me(r,e),"Cache-Control":"no-store","Referrer-Policy":"no-referrer"}})}function kr(e,t={}){let r=t.redirectTo??"/";if(!Z(r,t.allowedHosts??[]))throw Error(`[socials] refusing to hand a session to ${r}: relative paths are always allowed; an absolute URL needs its host in allowedHosts.`);return new Response(null,{status:302,headers:{Location:Ve(r,e),"Cache-Control":"no-store","Referrer-Policy":"no-referrer"}})}function br(e,t={}){let r=t.redirectTo??"/login";if(!Z(r,t.allowedHosts??[]))throw Error(`[socials] refusing to redirect to ${r}`);let i=r.includes("?")?"&":"?",s=`${r}${i}social_error=${encodeURIComponent(e)}`;return new Response(null,{status:302,headers:{Location:s,"Cache-Control":"no-store","Referrer-Policy":"no-referrer"}})}import{Buffer as fe}from"buffer";import{createHash as je,randomBytes as Je,timingSafeEqual as Ge}from"crypto";function x(e){return je("sha256").update(e).digest("hex")}var Ke=/^[a-f0-9]{64}$/,Qe=/^[a-f0-9]{64}$/,We=/^[\w.:-]{1,64}$/,Xe=3600000,me=null;function Ir(e){me=e}function we(){return me??et()}function Y(e=32){return Je(e).toString("hex")}function Ze(e,t){if(e.length!==t.length)return!1;return Ge(fe.from(e,"utf8"),fe.from(t,"utf8"))}function ee(e){return`stacks_oauth_${x(e).slice(0,24)}`}async function Ye(){try{let{config:e}=await import("@stacksjs/config");return String(e.app?.url??"").startsWith("https:")}catch{return!1}}function ye(e,t,r,i){return[`${e}=${t}`,`Path=${i.path}`,"HttpOnly",`SameSite=${i.sameSite}`,`Max-Age=${Math.max(0,Math.floor(r))}`,...i.secure?["Secure"]:[]].join("; ")}async function ke(e={}){let t=e.sameSite??"Lax",r=t==="None"?!0:e.secure??await Ye();return{path:e.path??"/",sameSite:t,secure:r}}async function Tr(e){if(!We.test(e.purpose??""))throw TypeError("[socials] issueOAuthFlow needs a purpose of 1-64 word characters, dots, colons or dashes.");if(!Number.isFinite(e.ttlMs)||e.ttlMs<=0)throw TypeError("[socials] issueOAuthFlow needs a positive ttlMs.");if(e.challenge!==void 0&&!Qe.test(e.challenge))throw TypeError("[socials] issueOAuthFlow challenge must be the lowercase hex SHA-256 of the verifier.");if(e.userId!=null&&(!Number.isSafeInteger(e.userId)||e.userId<=0))throw TypeError("[socials] issueOAuthFlow userId must be a positive integer.");let t=Math.min(e.ttlMs,Xe),r=Y(),s=(e.challenge?!1:e.bindBrowser!==!1)?Y():null,n=e.nonce===!0?Y():typeof e.nonce==="string"&&e.nonce?e.nonce:void 0,o=new Date,a=new Date(o.getTime()+t),c=we();try{await c.prune(o)}catch{}await c.insert({stateDigest:x(r),purpose:e.purpose,browserDigest:e.challenge??(s?x(s):null),userId:e.userId??null,nonce:n??null,data:e.data??null,expiresAt:a});let u=s?ye(ee(r),s,t/1000,await ke(e.cookie)):null;return{state:r,cookie:u,secret:s,...n!==void 0&&{nonce:n},expiresAt:a}}async function xr(e,t){if(typeof e!=="string"||!Ke.test(e))return null;let r=we(),i=x(e),s=await r.find(i);if(!s)return null;if(s.expiresAt.getTime()<=Date.now())return await r.take(i).catch(()=>!1),null;if(s.purpose!==t.purpose)return null;if(s.browserDigest!==null){let n=t.browserSecret;if(typeof n!=="string"||n.length===0||n.length>512)return null;if(!Ze(x(n),s.browserDigest))return null}if(!await r.take(i))return null;return{purpose:s.purpose,userId:s.userId,nonce:s.nonce,data:s.data,expiresAt:s.expiresAt}}function _r(e,t){if(!e||!t)return"";let r=ee(t);for(let i of e.split(";")){let s=i.trim();if(s.startsWith(`${r}=`))return s.slice(r.length+1)}return""}async function Cr(e,t={}){return ye(ee(e),"",0,await ke(t))}function Ur(){let e=new Map;return{records:e,async insert(t){e.set(t.stateDigest,{...t})},async find(t){let r=e.get(t);return r?{...r}:void 0},async take(t){return e.delete(t)},async prune(t){for(let[r,i]of e)if(i.expiresAt.getTime()<=t.getTime())e.delete(r)}}}var D="oauth_flows";function et(){let e=()=>import("@stacksjs/database/runtime");return{async insert(t){let{db:r,sqlDateTime:i}=await e();await r.insertInto(D).values({state_digest:t.stateDigest,purpose:t.purpose,browser_digest:t.browserDigest,user_id:t.userId,nonce:t.nonce,data:t.data?JSON.stringify(t.data):null,expires_at:i(t.expiresAt),created_at:i(new Date)}).execute()},async find(t){let{db:r,parseSqlDateTime:i}=await e(),s=await r.primary.selectFrom(D).where("state_digest","=",t).selectAll().executeTakeFirst();if(!s)return;let n=null;if(typeof s.data==="string"&&s.data)try{let o=JSON.parse(s.data);n=o&&typeof o==="object"?o:null}catch{n=null}return{stateDigest:String(s.state_digest),purpose:String(s.purpose),browserDigest:typeof s.browser_digest==="string"&&s.browser_digest?s.browser_digest:null,userId:s.user_id==null?null:Number(s.user_id),nonce:typeof s.nonce==="string"&&s.nonce?s.nonce:null,data:n,expiresAt:i(s.expires_at)??new Date(0)}},async take(t){let{db:r,mutationCount:i}=await e(),s=await r.deleteFrom(D).where("state_digest","=",t).execute();return i(s)===1},async prune(t){let{db:r,sqlDateTime:i}=await e();await r.deleteFrom(D).where("expires_at","<",i(t)).execute()}}}var tt={bluesky:["identifier","password","service"],twitter:["clientId","clientSecret","accessToken","refreshToken"],linkedin:["accessToken","memberUrn","clientId","clientSecret","apiVersion"],mastodon:["accessToken","instanceUrl"],instagram:["accessToken","accountId","clientId","clientSecret"],threads:["accessToken","userId","clientId","clientSecret"]},rt={bluesky:["identifier","password"],twitter:["clientId","clientSecret","accessToken"],linkedin:["accessToken","memberUrn"],mastodon:["accessToken","instanceUrl"],instagram:["accessToken","accountId"],threads:["accessToken","userId"]};function it(e){return e.toUpperCase().replace(/[^A-Z0-9]/g,"")}function L(e,t,r){let i=r.replace(/[A-Z]/g,(s)=>`_${s}`).toUpperCase();return`SOCIALS_${it(e)}_${t.toUpperCase()}_${i}`}class g extends Error{constructor(e){super(e);this.name="SocialIdentityError"}}async function F(){let{config:e,overridesReady:t}=await import("@stacksjs/config");return await t,e.socials??{}}function be(e,t){let r=Object.keys(e.identities??{});if(t){if(!r.includes(t))throw new g(`Unknown social identity '${t}'. config/socials.ts declares: ${r.length?r.join(", "):"none"}.`);return t}if(e.default){if(!r.includes(e.default))throw new g(`config/socials.ts sets default: '${e.default}', which is not one of its identities: ${r.length?r.join(", "):"none"}.`);return e.default}if(r.length===1)return r[0];if(r.length===0)throw new g("config/socials.ts declares no identities, so there is no account to post as.");throw new g(`config/socials.ts declares ${r.length} identities (${r.join(", ")}) and no default, so a call has to name one.`)}function st(e,t,r,i=process.env){let n=(e.identities?.[r]??{}).credentials?.[t]??{},o={};for(let a of tt[t]){let c=(n[a]??i[L(r,t,a)]??"").trim();if(c)o[a]=c}return o}function te(e,t,r,i=process.env){let s=be(e,r),n=st(e,t,s,i),o=rt[t].filter((a)=>!n[a]);if(o.length){let a=o.map((c)=>L(s,t,c)).join(", ");throw new g(`Identity '${s}' is not configured for ${t}: ${o.join(", ")} ${o.length===1?"is":"are"} missing. Set ${a}, or supply them inline in config/socials.ts.`)}return n}async function Se(e){return be(await F(),e)}async function S(e,t,r){return te(await F(),e,t,r)}function Rr(e,t){return S("bluesky",e,t)}function $r(e,t){return S("twitter",e,t)}function Or(e,t){return S("linkedin",e,t)}function Er(e,t){return S("mastodon",e,t)}function Br(e,t){return S("instagram",e,t)}function Dr(e,t){return S("threads",e,t)}import{randomBytes as nt}from"crypto";function qr(e){return e==="twitter"}var ot=["tweet.read","tweet.write","users.read","offline.access"],at="127.0.0.1",Pe="/callback";function Hr(e){if(!Number.isInteger(e)||e<1||e>65535)throw new g(`${e} is not a port a redirect can listen on.`);return`http://${at}:${e}${Pe}`}function Vr(){return nt(16).toString("hex")}function Mr(e,t){let r;try{r=new URL(e)}catch{return{ok:!1,reason:"not-the-callback",message:`Not a URL: ${e}`}}if(r.pathname!==Pe)return{ok:!1,reason:"not-the-callback",message:`Ignoring a request for ${r.pathname}.`};let i=r.searchParams.get("error");if(i){let o=r.searchParams.get("error_description");return{ok:!1,reason:"declined",message:o?`${i}: ${o}`:i}}let s=r.searchParams.get("state")??"";if(!t||s!==t)return{ok:!1,reason:"state-mismatch",message:"The redirect carried the wrong state, so it did not come from this run."};let n=r.searchParams.get("code")??"";if(!n)return{ok:!1,reason:"no-code",message:"The redirect carried no authorization code."};return{ok:!0,code:n}}function jr(e,t,r){let i=[];for(let[s,n]of Object.entries(r)){if(!n)continue;i.push(`${L(e,t,s)}=${n}`)}return i}async function Jr(e){let t=new b,r=await t.exchangeCode(e);if(!r.accessToken)throw new g("X returned no access token for that code.");let i=Boolean(r.refreshToken),s=await t.getProfile(r.accessToken);return{handle:s.username,accountId:s.id,accessToken:r.accessToken,refreshToken:r.refreshToken,expiresIn:r.expiresIn,scope:r.scope,renewable:i}}async function Gr(e){return new b().createAuthorization({clientId:e.clientId,redirectUrl:e.redirectUrl,scopes:[...ot],state:e.state})}function Kr(e,t){let r=(i)=>i.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");return`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>${r(e)}</title><style>body{font:16px/1.5 system-ui,sans-serif;margin:0;display:grid;place-items:center;min-height:100vh;color:#111}main{max-width:28rem;padding:2rem;text-align:center}h1{font-size:1.25rem;margin:0 0 .5rem}p{margin:0;color:#555}@media(prefers-color-scheme:dark){body{background:#111;color:#eee}p{color:#aaa}}</style></head><body><main><h1>${r(e)}</h1><p>${r(t)}</p></main></body></html>`}var ct={bluesky:null,twitter:null,linkedin:"memberUrn",mastodon:"instanceUrl",instagram:"accountId",threads:"userId"};function lt(e,t){if(e==="bluesky")return t.service?{service:t.service}:{};if(e==="linkedin")return t.apiVersion?{apiVersion:t.apiVersion}:{};return{}}function dt(e,t={}){switch(e){case"bluesky":return new U(t);case"linkedin":return new K(t);case"twitter":return new b;case"mastodon":return new Q;case"instagram":return new G;case"threads":return new W;default:throw new g(`No publishing driver for '${String(e)}'.`)}}function ut(e,t,r){let i=ct[e],s={handle:(t??"").trim()};if(r.accessToken)s.accessToken=r.accessToken;if(r.refreshToken)s.refreshToken=r.refreshToken;if(i&&r[i])s.did=r[i];return s}function pt(e,t,r){if(!r)return;if(r.includes(t))return;throw new g(`Identity '${e}' is not set up for ${t}. config/socials.ts lists: ${r.length?r.join(", "):"none"}.`)}async function ht(e,t,r){let i=await F(),s=await Se(t),n=i.identities?.[s]??{};pt(s,e,n.platforms);let o=te(i,e,s,r),a=lt(e,o);if(e==="bluesky"){let c=await new U(a).createSession({identifier:o.identifier,password:o.password});return{name:s,platform:e,identity:{handle:c.handle,did:c.did,accessToken:c.accessJwt,refreshToken:c.refreshJwt},options:a}}return{name:s,platform:e,identity:ut(e,n.handle??"",o),options:a}}async function Zr(e,t,r,i){let s=await ht(e,r,i);return dt(s.platform,s.options).publish(s.identity,t)}export{at as AUTHORIZE_HOST,Pe as AUTHORIZE_PATH,h as AbstractProvider,H as AppleProvider,ae as BlueskyApiError,U as BlueskyPublishingDriver,l as ConfigException,ct as DRIVER_DID_FIELD,V as FacebookProvider,j as GitHubProvider,J as GoogleProvider,v as InstagramApiError,G as InstagramPublishingDriver,Te as InvalidStateException,f as LinkedInApiError,K as LinkedInPublishingDriver,A as META_GRAPH_VERSION,de as MastodonApiError,Q as MastodonPublishingDriver,rt as REQUIRED_SOCIAL_CREDENTIALS,tt as SOCIAL_CREDENTIAL_FIELDS,T as SOCIAL_PROVIDERS,g as SocialIdentityError,I as StacksIssuerError,Ne as StacksIssuerProvider,ot as TWITTER_PUBLISH_SCOPES,k as ThreadsApiError,W as ThreadsPublishingDriver,He as Token,w as TwitterApiError,X as TwitterProvider,b as TwitterPublishingDriver,pt as assertPlatformDeclared,Kr as callbackPage,Hr as callbackUri,Cr as clearOAuthFlowCookie,Jr as completeTwitterAuthorization,dr as configuredSocialProviders,xr as consumeOAuthFlow,et as databaseOAuthFlowStore,Oe as detectFacetCandidates,lt as driverOptions,jr as envLines,L as envName,it as envSegment,Ee as escapeLinkedInText,qr as isAuthorizablePlatform,Z as isSafeHandoffTarget,ge as isSocialProviderConfigured,qe as isSocialProviderName,Tr as issueOAuthFlow,Ur as memoryOAuthFlowStore,Vr as newState,Be as normalizeInstance,ee as oauthFlowCookieName,x as oauthFlowDigest,Re as parseAtUri,te as pickCredentials,be as pickIdentity,Zr as publishAs,dt as publishingDriver,Mr as readCallback,st as readCredentials,_r as readOAuthFlowCookie,Rr as resolveBlueskyCredentials,Se as resolveIdentityName,Br as resolveInstagramCredentials,Or as resolveLinkedInCredentials,Er as resolveMastodonCredentials,S as resolveSocialCredentials,Dr as resolveThreadsCredentials,$r as resolveTwitterCredentials,kr as socialHandoffCodeRedirect,br as socialHandoffFailureRedirect,yr as socialHandoffRedirect,ur as socialProvider,ht as socialPublishTarget,F as socialsConfig,Gr as startTwitterAuthorization,gr as supportsDeletion,fr as supportsEnumeration,ut as toDriverIdentity,Ir as useOAuthFlowStore};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/socials",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.450",
|
|
6
6
|
"description": "A simple and elegant social authentication package for Stacks.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -57,14 +57,14 @@
|
|
|
57
57
|
"prepublishOnly": "bun run build"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@stacksjs/api": "0.75.
|
|
61
|
-
"@stacksjs/composables": "0.75.
|
|
62
|
-
"@stacksjs/config": "0.75.
|
|
63
|
-
"@stacksjs/database": "0.75.
|
|
60
|
+
"@stacksjs/api": "0.75.450",
|
|
61
|
+
"@stacksjs/composables": "0.75.450",
|
|
62
|
+
"@stacksjs/config": "0.75.450",
|
|
63
|
+
"@stacksjs/database": "0.75.450"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@stacksjs/error-handling": "0.75.
|
|
67
|
-
"@stacksjs/router": "0.75.
|
|
66
|
+
"@stacksjs/error-handling": "0.75.450",
|
|
67
|
+
"@stacksjs/router": "0.75.450",
|
|
68
68
|
"better-dx": "^0.2.24"
|
|
69
69
|
}
|
|
70
70
|
}
|