@stacksjs/socials 0.70.36 → 0.70.42
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/abstract.d.ts +42 -0
- package/dist/drivers/bluesky.d.ts +24 -0
- package/dist/{src/drivers → drivers}/index.d.ts +1 -0
- package/dist/exceptions.d.ts +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/types.d.ts +122 -0
- package/package.json +3 -3
- package/dist/src/index.d.ts +0 -1
- /package/dist/{src/drivers → drivers}/facebook.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/github.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/google.d.ts +0 -0
- /package/dist/{src/drivers → drivers}/twitter.d.ts +0 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ProviderInterface, SocialUser } from './types';
|
|
2
|
+
export declare interface ProviderConfig {
|
|
3
|
+
clientId: string
|
|
4
|
+
clientSecret: string
|
|
5
|
+
redirectUrl: string
|
|
6
|
+
guzzle?: Record<string, any>
|
|
7
|
+
}
|
|
8
|
+
export declare abstract class AbstractProvider implements ProviderInterface {
|
|
9
|
+
protected clientId: string;
|
|
10
|
+
protected clientSecret: string;
|
|
11
|
+
protected redirectUrl: string;
|
|
12
|
+
protected parameters: Record<string, any>;
|
|
13
|
+
protected _scopes: string[];
|
|
14
|
+
protected scopeSeparator: string;
|
|
15
|
+
protected _stateless: boolean;
|
|
16
|
+
protected _usesPKCE: boolean;
|
|
17
|
+
protected user: SocialUser | null;
|
|
18
|
+
constructor(config: ProviderConfig);
|
|
19
|
+
abstract getAuthUrl(): Promise<string>;
|
|
20
|
+
protected abstract getTokenUrl(): string;
|
|
21
|
+
abstract getAccessToken(code: string): Promise<string>;
|
|
22
|
+
abstract getUserByToken(token: string): Promise<SocialUser>;
|
|
23
|
+
protected getCodeFields(state?: string | null): Record<string, any>;
|
|
24
|
+
protected formatScopes(scopes: string[], scopeSeparator: string): string;
|
|
25
|
+
userFromToken(token: string): Promise<SocialUser>;
|
|
26
|
+
scopes(scopes: string | string[]): this;
|
|
27
|
+
setScopes(scopes: string | string[]): this;
|
|
28
|
+
getScopes(): string[];
|
|
29
|
+
setRedirectUrl(url: string): this;
|
|
30
|
+
protected usesState(): boolean;
|
|
31
|
+
validateState(expected: string | null | undefined, actual: string | null | undefined): boolean;
|
|
32
|
+
protected isStateless(): boolean;
|
|
33
|
+
stateless(): this;
|
|
34
|
+
protected getState(): string;
|
|
35
|
+
protected usesPKCE(): boolean;
|
|
36
|
+
enablePKCE(): this;
|
|
37
|
+
protected getCodeVerifier(): string;
|
|
38
|
+
protected getCodeChallenge(): Promise<string>;
|
|
39
|
+
protected getCodeChallengeMethod(): string;
|
|
40
|
+
with(parameters: Record<string, any>): this;
|
|
41
|
+
protected buildAuthUrlFromBase(url: string, state: string | null): string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { BlueskySession, BlueskySessionCredentials, PublishedPost, PublishPostInput, SocialIdentityCredentials, SocialPublishingDriver, TimelineQuery, TimelineResult } from '../types';
|
|
2
|
+
export declare interface BlueskyDriverOptions {
|
|
3
|
+
service?: string
|
|
4
|
+
}
|
|
5
|
+
export declare class BlueskyApiError extends Error {
|
|
6
|
+
public status: number;
|
|
7
|
+
public body: string;
|
|
8
|
+
constructor(message: string, status: number, body: string);
|
|
9
|
+
get isAuthError(): boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class BlueskyPublishingDriver implements SocialPublishingDriver {
|
|
12
|
+
readonly provider: 'bluesky';
|
|
13
|
+
characterLimit: number;
|
|
14
|
+
protected service: string;
|
|
15
|
+
constructor(options?: BlueskyDriverOptions);
|
|
16
|
+
createSession(credentials: BlueskySessionCredentials): Promise<BlueskySession>;
|
|
17
|
+
refreshSession(refreshToken: string): Promise<BlueskySession>;
|
|
18
|
+
publish(identity: SocialIdentityCredentials, post: PublishPostInput): Promise<PublishedPost>;
|
|
19
|
+
timeline(identity: SocialIdentityCredentials, query?: TimelineQuery): Promise<TimelineResult>;
|
|
20
|
+
getProfile(identity: SocialIdentityCredentials): Promise<{ did: string, handle: string, displayName?: string }>;
|
|
21
|
+
protected post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
22
|
+
protected request<T>(url: URL, init: RequestInit): Promise<T>;
|
|
23
|
+
protected toPostUrl(handle: string, uri: string): string;
|
|
24
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './drivers/index';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var X=import.meta.require;import{fetcher as Y}from"@stacksjs/api";import{config as M}from"@stacksjs/config";class K{clientId;clientSecret;redirectUrl;parameters={};_scopes=[];scopeSeparator=",";_stateless=!1;_usesPKCE=!1;user=null;constructor(w){this.clientId=w.clientId,this.clientSecret=w.clientSecret,this.redirectUrl=w.redirectUrl}getCodeFields(w=null){let z={client_id:this.clientId,redirect_uri:this.redirectUrl,scope:this.formatScopes(this.getScopes(),this.scopeSeparator),response_type:"code"};if(this.usesState())z.state=w;if(this.usesPKCE())z.code_challenge=this.getCodeChallenge(),z.code_challenge_method=this.getCodeChallengeMethod();return{...z,...this.parameters}}formatScopes(w,z){return w.join(z)}async userFromToken(w){return{...await this.getUserByToken(w),token:w}}scopes(w){let z=Array.isArray(w)?w:[w];return this._scopes=[...new Set([...this._scopes,...z])],this}setScopes(w){let z=Array.isArray(w)?w:[w];return this._scopes=[...new Set(z)],this}getScopes(){return this._scopes}setRedirectUrl(w){if(typeof w!=="string"||w.length===0)throw Error("[socials] setRedirectUrl requires a non-empty string");let z;try{z=new URL(w)}catch{throw Error(`[socials] setRedirectUrl: invalid URL: ${w}`)}if(z.protocol!=="https:"&&z.protocol!=="http:")throw Error(`[socials] setRedirectUrl protocol must be http(s)://, got ${z.protocol}`);return this.redirectUrl=w,this}usesState(){return!this._stateless}validateState(w,z){if(typeof w!=="string"||typeof z!=="string")return!1;if(w.length===0||z.length===0)return!1;if(w.length!==z.length)return!1;try{let{timingSafeEqual:D}=X("crypto");return D(Buffer.from(w,"utf8"),Buffer.from(z,"utf8"))}catch{let D=0;for(let F=0;F<w.length;F++)D|=w.charCodeAt(F)^z.charCodeAt(F);return D===0}}isStateless(){return this._stateless}stateless(){return this._stateless=!0,this}getState(){let w=new Uint8Array(32);return crypto.getRandomValues(w),Array.from(w).map((z)=>z.toString(16).padStart(2,"0")).join("")}usesPKCE(){return this._usesPKCE}enablePKCE(){return this._usesPKCE=!0,this}getCodeVerifier(){let w=new Uint8Array(48);return crypto.getRandomValues(w),Array.from(w).map((z)=>z.toString(16).padStart(2,"0")).join("")}async getCodeChallenge(){let z=new TextEncoder().encode(this.getCodeVerifier()),D=await crypto.subtle.digest("SHA-256",z),{Buffer:F}=await import("buffer");return F.from(new Uint8Array(D)).toString("base64url")}getCodeChallengeMethod(){return"S256"}with(w){return this.parameters=w,this}buildAuthUrlFromBase(w,z){let D=new URLSearchParams(this.getCodeFields(z));return`${w}?${D.toString()}`}}class G extends Error{constructor(w){super(w);this.name="ConfigException"}}class $ extends K{baseUrl="https://www.facebook.com";apiUrl="https://graph.facebook.com";getConfig(){let w={clientId:M.services.facebook?.clientId??"",clientSecret:M.services.facebook?.clientSecret??"",redirectUrl:M.services.facebook?.redirectUrl??"",scopes:M.services.facebook?.scopes??["email","public_profile"]};return this.setScopes(w.scopes),w}async getAuthUrl(){let w=this.getState(),{clientId:z,redirectUrl:D,scopes:F}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/v18.0/dialog/oauth?${new URLSearchParams({client_id:z,redirect_uri:D,scope:F.join(","),state:w,response_type:"code"}).toString()}`}async getAccessToken(w){let{clientId:z,clientSecret:D,redirectUrl:F}=this.getConfig();this.validateConfig();let J=await Y.get(`${this.apiUrl}/v18.0/oauth/access_token?${new URLSearchParams({client_id:z,client_secret:D,redirect_uri:F,code:w}).toString()}`);if(J.data.error)throw Error(`Facebook OAuth error: ${J.data.error.message}`);return J.data.access_token}async getUserByToken(w){let z=await Y.get(`${this.apiUrl}/v18.0/me?${new URLSearchParams({access_token:w,fields:"id,name,email,picture"}).toString()}`);return{id:z.data.id,nickname:null,name:z.data.name,email:z.data.email??null,avatar:z.data.picture?.data.url??null,token:w,raw:z.data}}validateConfig(){let{clientId:w,clientSecret:z,redirectUrl:D}=this.getConfig();if(!w)throw new G("Facebook client ID not provided");if(!z)throw new G("Facebook client secret not provided");if(!D)throw new G("Facebook redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/v18.0/oauth/access_token`}}import{fetcher as W}from"@stacksjs/api";import{config as N}from"@stacksjs/config";class H extends K{baseUrl="https://github.com";apiUrl="https://api.github.com";getConfig(){let w={clientId:N.services.github?.clientId??"",clientSecret:N.services.github?.clientSecret??"",redirectUrl:N.services.github?.redirectUrl??"",scopes:N.services.github?.scopes??["read:user","user:email"]};return this.setScopes(w.scopes),w}async getAuthUrl(){let w=this.getState(),{clientId:z,redirectUrl:D,scopes:F}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/login/oauth/authorize?${new URLSearchParams({client_id:z,redirect_uri:D,scope:F.join(" "),state:w,response_type:"code"}).toString()}`}async getAccessToken(w){let{clientId:z,clientSecret:D,redirectUrl:F}=this.getConfig();this.validateConfig();let J=await W.post(`${this.baseUrl}/login/oauth/access_token`,{client_id:z,client_secret:D,code:w,redirect_uri:F});if(J.data.error)throw Error(`GitHub OAuth error: ${J.data.error_description}`);return J.data.access_token}async getUserByToken(w){let[z,D]=await Promise.all([W.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${w}`}).get(`${this.apiUrl}/user`),W.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${w}`}).get(`${this.apiUrl}/user/emails`)]);return{id:z.data.id.toString(),nickname:z.data.login,name:z.data.name,email:this.getEmail(D.data)??z.data.email??null,avatar:z.data.avatar_url,token:w,raw:z.data}}getEmail(w){if(!Array.isArray(w)||w.length===0)return null;let z=w.find((F)=>F.primary&&F.verified),D=w.find((F)=>F.verified);return(z||D||w.find((F)=>F.primary)||w[0])?.email??null}validateConfig(){let{clientId:w,clientSecret:z,redirectUrl:D}=this.getConfig();if(!w)throw new G("GitHub client ID not provided");if(!z)throw new G("GitHub client secret not provided");if(!D)throw new G("GitHub redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/login/oauth/access_token`}}import{fetcher as Z}from"@stacksjs/api";import{config as O}from"@stacksjs/config";class q extends K{baseUrl="https://accounts.google.com";apiUrl="https://www.googleapis.com";getConfig(){let w={clientId:O.services.google?.clientId??"",clientSecret:O.services.google?.clientSecret??"",redirectUrl:O.services.google?.redirectUrl??"",scopes:O.services.google?.scopes??["openid","email"]};return this.setScopes(w.scopes),w}async getAuthUrl(){let w=this.getState(),{clientId:z,redirectUrl:D,scopes:F}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/o/oauth2/v2/auth?${new URLSearchParams({client_id:z,redirect_uri:D,scope:F.join(" "),state:w,response_type:"code",access_type:"offline",prompt:"consent"}).toString()}`}async getAccessToken(w){let{clientId:z,clientSecret:D,redirectUrl:F}=this.getConfig();this.validateConfig();let J=await Z.post(`${this.baseUrl}/oauth2/v4/token`,{client_id:z,client_secret:D,code:w,redirect_uri:F,grant_type:"authorization_code"});if(J.data.error)throw Error(`Google OAuth error: ${J.data.error_description}`);return J.data.access_token}async getUserByToken(w){let z=await Z.withHeaders({Authorization:`Bearer ${w}`}).get(`${this.apiUrl}/oauth2/v2/userinfo`);return{id:z.data.id,nickname:z.data.given_name,name:z.data.name,email:z.data.email,avatar:z.data.picture,token:w,raw:z.data}}validateConfig(){let{clientId:w,clientSecret:z,redirectUrl:D}=this.getConfig();if(!w)throw new G("Google client ID not provided");if(!z)throw new G("Google client secret not provided");if(!D)throw new G("Google redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/oauth2/v4/token`}}import{Buffer as L}from"buffer";import{createHash as B,randomBytes as P}from"crypto";import{fetcher as _}from"@stacksjs/api";import{config as Q}from"@stacksjs/config";class E extends K{baseUrl="https://twitter.com";apiUrl="https://api.twitter.com";codeVerifier=null;getConfig(){let w={clientId:Q.services.twitter?.clientId??"",clientSecret:Q.services.twitter?.clientSecret??"",redirectUrl:Q.services.twitter?.redirectUrl??"",scopes:Q.services.twitter?.scopes??["users.read","tweet.read"]};return this.setScopes(w.scopes),w}generateCodeVerifier(){return P(32).toString("base64").replace(/[^a-z0-9]/gi,"").substring(0,128)}generateCodeChallenge(w){return B("sha256").update(w).digest("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async getAuthUrl(){let w=this.getState(),{clientId:z,redirectUrl:D,scopes:F}=this.getConfig();this.validateConfig(),this.codeVerifier=this.generateCodeVerifier();let J=this.generateCodeChallenge(this.codeVerifier);return`${this.baseUrl}/i/oauth2/authorize?${new URLSearchParams({client_id:z,redirect_uri:D,scope:F.join(" "),state:w,response_type:"code",code_challenge:J,code_challenge_method:"S256"}).toString()}`}async getAccessToken(w){let{clientId:z,clientSecret:D,redirectUrl:F}=this.getConfig();if(this.validateConfig(),!this.codeVerifier)throw Error("Code verifier not found. Please ensure getAuthUrl() is called first.");let J=L.from(`${z}:${D}`).toString("base64"),T=await _.withHeaders({Authorization:`Basic ${J}`,"Content-Type":"application/x-www-form-urlencoded"}).post(`${this.apiUrl}/2/oauth2/token`,{code:w,grant_type:"authorization_code",redirect_uri:F,code_verifier:this.codeVerifier});if(T.data.error)throw Error(`Twitter OAuth error: ${T.data.error_description}`);return T.data.access_token}async getUserByToken(w){let z=await _.withHeaders({Authorization:`Bearer ${w}`}).get(`${this.apiUrl}/2/users/me?user.fields=profile_image_url`);return{id:z.data.id,nickname:z.data.username,name:z.data.name,email:z.data.email??null,avatar:z.data.profile_image_url??null,token:w,raw:z.data}}validateConfig(){let{clientId:w,clientSecret:z,redirectUrl:D}=this.getConfig();if(!w)throw new G("Twitter client ID not provided");if(!z)throw new G("Twitter client secret not provided");if(!D)throw new G("Twitter redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/2/oauth2/token`}}export{E as TwitterProvider,q as GoogleProvider,H as GitHubProvider,$ as FacebookProvider};
|
|
2
|
+
var o=import.meta.require;class d extends Error{status;body;constructor(r,t,s){super(r);this.status=t;this.body=s;this.name="BlueskyApiError"}get isAuthError(){return this.status===400||this.status===401||this.status===403}}class S{provider="bluesky";characterLimit=300;service;constructor(r={}){this.service=r.service||"https://bsky.social"}async createSession(r){let t=r.identifier.trim(),s=r.password.trim();if(!t)throw Error("Bluesky identifier is required.");if(!s)throw Error("Bluesky app password is required.");let i=await this.post("/xrpc/com.atproto.server.createSession",{identifier:t,password:s}),a=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:a?.displayName,accessJwt:i.accessJwt,refreshJwt:i.refreshJwt}}async refreshSession(r){if(!r)throw Error("Bluesky refresh token is required.");let t=await this.post("/xrpc/com.atproto.server.refreshSession",void 0,{authorization:`Bearer ${r}`});return{did:t.did,handle:t.handle,accessJwt:t.accessJwt,refreshJwt:t.refreshJwt}}async publish(r,t){let s=r.did||r.handle;if(!r.accessToken)throw Error("Bluesky access token is missing for this identity.");if(!s)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.external)i.embed={$type:"app.bsky.embed.external",external:{uri:t.external.uri,title:t.external.title,description:t.external.description||""}};let a=await this.post("/xrpc/com.atproto.repo.createRecord",{repo:s,collection:"app.bsky.feed.post",record:i},{authorization:`Bearer ${r.accessToken}`});return{provider:this.provider,uri:a.uri,cid:a.cid,url:this.toPostUrl(r.handle,a.uri)}}async timeline(r,t={}){if(!r.accessToken)throw Error("Bluesky access token is missing for this identity.");let s=new URL(`${this.service}/xrpc/app.bsky.feed.getTimeline`);if(s.searchParams.set("limit",String(Math.min(Math.max(t.limit||30,1),100))),t.cursor)s.searchParams.set("cursor",t.cursor);let i=await this.request(s,{headers:{authorization:`Bearer ${r.accessToken}`}});return{cursor:i.cursor,items:(i.feed||[]).flatMap((a)=>{let h=a.post;if(!h?.uri||!h.author?.handle)return[];return[{uri:h.uri,authorHandle:h.author.handle,authorName:h.author.displayName,body:h.record?.text||"",postedAt:h.record?.createdAt||new Date().toISOString(),likeCount:h.likeCount||0,repostCount:h.repostCount||0,replyCount:h.replyCount||0}]})}}async getProfile(r){if(!r.accessToken)throw Error("Bluesky access token is missing for this identity.");let t=r.did||r.handle;if(!t)throw Error("Bluesky identity DID or handle is required.");let s=new URL(`${this.service}/xrpc/app.bsky.actor.getProfile`);return s.searchParams.set("actor",t),await this.request(s,{headers:{authorization:`Bearer ${r.accessToken}`}})}async post(r,t,s={}){return await this.request(new URL(`${this.service}${r}`),{method:"POST",headers:{...t===void 0?{}:{"content-type":"application/json"},...s},...t===void 0?{}:{body:JSON.stringify(t)}})}async request(r,t){let s=await fetch(r,t),i=await s.text();if(!s.ok)throw new d(`Bluesky API failed (${s.status}): ${i||s.statusText}`,s.status,i);return i?JSON.parse(i):{}}toPostUrl(r,t){let s=t.split("/").pop();return`https://bsky.app/profile/${r}/post/${s}`}}import{fetcher as P}from"@stacksjs/api";import{config as w}from"@stacksjs/config";class n{clientId;clientSecret;redirectUrl;parameters={};_scopes=[];scopeSeparator=",";_stateless=!1;_usesPKCE=!1;user=null;constructor(r){this.clientId=r.clientId,this.clientSecret=r.clientSecret,this.redirectUrl=r.redirectUrl}getCodeFields(r=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=r;if(this.usesPKCE())t.code_challenge=this.getCodeChallenge(),t.code_challenge_method=this.getCodeChallengeMethod();return{...t,...this.parameters}}formatScopes(r,t){return r.join(t)}async userFromToken(r){return{...await this.getUserByToken(r),token:r}}scopes(r){let t=Array.isArray(r)?r:[r];return this._scopes=[...new Set([...this._scopes,...t])],this}setScopes(r){let t=Array.isArray(r)?r:[r];return this._scopes=[...new Set(t)],this}getScopes(){return this._scopes}setRedirectUrl(r){if(typeof r!=="string"||r.length===0)throw Error("[socials] setRedirectUrl requires a non-empty string");let t;try{t=new URL(r)}catch{throw Error(`[socials] setRedirectUrl: invalid URL: ${r}`)}if(t.protocol!=="https:"&&t.protocol!=="http:")throw Error(`[socials] setRedirectUrl protocol must be http(s)://, got ${t.protocol}`);return this.redirectUrl=r,this}usesState(){return!this._stateless}validateState(r,t){if(typeof r!=="string"||typeof t!=="string")return!1;if(r.length===0||t.length===0)return!1;if(r.length!==t.length)return!1;try{let{timingSafeEqual:s}=o("crypto");return s(Buffer.from(r,"utf8"),Buffer.from(t,"utf8"))}catch{let s=0;for(let i=0;i<r.length;i++)s|=r.charCodeAt(i)^t.charCodeAt(i);return s===0}}isStateless(){return this._stateless}stateless(){return this._stateless=!0,this}getState(){let r=new Uint8Array(32);return crypto.getRandomValues(r),Array.from(r).map((t)=>t.toString(16).padStart(2,"0")).join("")}usesPKCE(){return this._usesPKCE}enablePKCE(){return this._usesPKCE=!0,this}getCodeVerifier(){let r=new Uint8Array(48);return crypto.getRandomValues(r),Array.from(r).map((t)=>t.toString(16).padStart(2,"0")).join("")}async getCodeChallenge(){let t=new TextEncoder().encode(this.getCodeVerifier()),s=await crypto.subtle.digest("SHA-256",t),{Buffer:i}=await import("buffer");return i.from(new Uint8Array(s)).toString("base64url")}getCodeChallengeMethod(){return"S256"}with(r){return this.parameters=r,this}buildAuthUrlFromBase(r,t){let s=new URLSearchParams(this.getCodeFields(t));return`${r}?${s.toString()}`}}class u extends Error{constructor(r){super(r);this.name="ConfigException"}}class k extends n{baseUrl="https://www.facebook.com";apiUrl="https://graph.facebook.com";getConfig(){let r={clientId:w.services.facebook?.clientId??"",clientSecret:w.services.facebook?.clientSecret??"",redirectUrl:w.services.facebook?.redirectUrl??"",scopes:w.services.facebook?.scopes??["email","public_profile"]};return this.setScopes(r.scopes),r}async getAuthUrl(){let r=this.getState(),{clientId:t,redirectUrl:s,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/v18.0/dialog/oauth?${new URLSearchParams({client_id:t,redirect_uri:s,scope:i.join(","),state:r,response_type:"code"}).toString()}`}async getAccessToken(r){let{clientId:t,clientSecret:s,redirectUrl:i}=this.getConfig();this.validateConfig();let a=await P.get(`${this.apiUrl}/v18.0/oauth/access_token?${new URLSearchParams({client_id:t,client_secret:s,redirect_uri:i,code:r}).toString()}`);if(a.data.error)throw Error(`Facebook OAuth error: ${a.data.error.message}`);return a.data.access_token}async getUserByToken(r){let t=await P.get(`${this.apiUrl}/v18.0/me?${new URLSearchParams({access_token:r,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:r,raw:t.data}}validateConfig(){let{clientId:r,clientSecret:t,redirectUrl:s}=this.getConfig();if(!r)throw new u("Facebook client ID not provided");if(!t)throw new u("Facebook client secret not provided");if(!s)throw new u("Facebook redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/v18.0/oauth/access_token`}}import{fetcher as y}from"@stacksjs/api";import{config as e}from"@stacksjs/config";class $ extends n{baseUrl="https://github.com";apiUrl="https://api.github.com";getConfig(){let r={clientId:e.services.github?.clientId??"",clientSecret:e.services.github?.clientSecret??"",redirectUrl:e.services.github?.redirectUrl??"",scopes:e.services.github?.scopes??["read:user","user:email"]};return this.setScopes(r.scopes),r}async getAuthUrl(){let r=this.getState(),{clientId:t,redirectUrl:s,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/login/oauth/authorize?${new URLSearchParams({client_id:t,redirect_uri:s,scope:i.join(" "),state:r,response_type:"code"}).toString()}`}async getAccessToken(r){let{clientId:t,clientSecret:s,redirectUrl:i}=this.getConfig();this.validateConfig();let a=await y.post(`${this.baseUrl}/login/oauth/access_token`,{client_id:t,client_secret:s,code:r,redirect_uri:i});if(a.data.error)throw Error(`GitHub OAuth error: ${a.data.error_description}`);return a.data.access_token}async getUserByToken(r){let[t,s]=await Promise.all([y.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${r}`}).get(`${this.apiUrl}/user`),y.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${r}`}).get(`${this.apiUrl}/user/emails`)]);return{id:t.data.id.toString(),nickname:t.data.login,name:t.data.name??t.data.login,email:this.getEmail(s.data)??t.data.email??null,avatar:t.data.avatar_url,token:r,raw:t.data}}getEmail(r){if(!Array.isArray(r)||r.length===0)return null;let t=r.find((i)=>i.primary&&i.verified),s=r.find((i)=>i.verified);return(t||s||r.find((i)=>i.primary)||r[0])?.email??null}validateConfig(){let{clientId:r,clientSecret:t,redirectUrl:s}=this.getConfig();if(!r)throw new u("GitHub client ID not provided");if(!t)throw new u("GitHub client secret not provided");if(!s)throw new u("GitHub redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/login/oauth/access_token`}}import{fetcher as T}from"@stacksjs/api";import{config as l}from"@stacksjs/config";class J extends n{baseUrl="https://accounts.google.com";apiUrl="https://www.googleapis.com";getConfig(){let r={clientId:l.services.google?.clientId??"",clientSecret:l.services.google?.clientSecret??"",redirectUrl:l.services.google?.redirectUrl??"",scopes:l.services.google?.scopes??["openid","email"]};return this.setScopes(r.scopes),r}async getAuthUrl(){let r=this.getState(),{clientId:t,redirectUrl:s,scopes:i}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/o/oauth2/v2/auth?${new URLSearchParams({client_id:t,redirect_uri:s,scope:i.join(" "),state:r,response_type:"code",access_type:"offline",prompt:"consent"}).toString()}`}async getAccessToken(r){let{clientId:t,clientSecret:s,redirectUrl:i}=this.getConfig();this.validateConfig();let a=await T.post(`${this.baseUrl}/oauth2/v4/token`,{client_id:t,client_secret:s,code:r,redirect_uri:i,grant_type:"authorization_code"});if(a.data.error)throw Error(`Google OAuth error: ${a.data.error_description}`);return a.data.access_token}async getUserByToken(r){let t=await T.withHeaders({Authorization:`Bearer ${r}`}).get(`${this.apiUrl}/oauth2/v2/userinfo`);return{id:t.data.id,nickname:t.data.given_name,name:t.data.name,email:t.data.email,avatar:t.data.picture,token:r,raw:t.data}}validateConfig(){let{clientId:r,clientSecret:t,redirectUrl:s}=this.getConfig();if(!r)throw new u("Google client ID not provided");if(!t)throw new u("Google client secret not provided");if(!s)throw new u("Google redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/oauth2/v4/token`}}import{Buffer as D}from"buffer";import{createHash as x,randomBytes as N}from"crypto";import{fetcher as B}from"@stacksjs/api";import{config as m}from"@stacksjs/config";class b extends n{baseUrl="https://twitter.com";apiUrl="https://api.twitter.com";codeVerifier=null;getConfig(){let r={clientId:m.services.twitter?.clientId??"",clientSecret:m.services.twitter?.clientSecret??"",redirectUrl:m.services.twitter?.redirectUrl??"",scopes:m.services.twitter?.scopes??["users.read","tweet.read"]};return this.setScopes(r.scopes),r}generateCodeVerifier(){return N(32).toString("base64").replace(/[^a-z0-9]/gi,"").substring(0,128)}generateCodeChallenge(r){return x("sha256").update(r).digest("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async getAuthUrl(){let r=this.getState(),{clientId:t,redirectUrl:s,scopes:i}=this.getConfig();this.validateConfig(),this.codeVerifier=this.generateCodeVerifier();let a=this.generateCodeChallenge(this.codeVerifier);return`${this.baseUrl}/i/oauth2/authorize?${new URLSearchParams({client_id:t,redirect_uri:s,scope:i.join(" "),state:r,response_type:"code",code_challenge:a,code_challenge_method:"S256"}).toString()}`}async getAccessToken(r){let{clientId:t,clientSecret:s,redirectUrl:i}=this.getConfig();if(this.validateConfig(),!this.codeVerifier)throw Error("Code verifier not found. Please ensure getAuthUrl() is called first.");let a=D.from(`${t}:${s}`).toString("base64"),h=await B.withHeaders({Authorization:`Basic ${a}`,"Content-Type":"application/x-www-form-urlencoded"}).post(`${this.apiUrl}/2/oauth2/token`,{code:r,grant_type:"authorization_code",redirect_uri:i,code_verifier:this.codeVerifier});if(h.data.error)throw Error(`Twitter OAuth error: ${h.data.error_description}`);return h.data.access_token}async getUserByToken(r){let t=await B.withHeaders({Authorization:`Bearer ${r}`}).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:r,raw:t.data}}validateConfig(){let{clientId:r,clientSecret:t,redirectUrl:s}=this.getConfig();if(!r)throw new u("Twitter client ID not provided");if(!t)throw new u("Twitter client secret not provided");if(!s)throw new u("Twitter redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/2/oauth2/token`}}export{b as TwitterProvider,J as GoogleProvider,$ as GitHubProvider,k as FacebookProvider,S as BlueskyPublishingDriver,d as BlueskyApiError};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized user type that all providers will map their responses to.
|
|
3
|
+
* This ensures a consistent user structure regardless of the provider used.
|
|
4
|
+
*/
|
|
5
|
+
export declare interface SocialUser {
|
|
6
|
+
id: string
|
|
7
|
+
nickname: string | null
|
|
8
|
+
name: string
|
|
9
|
+
email: string | null
|
|
10
|
+
avatar: string | null
|
|
11
|
+
token: string
|
|
12
|
+
raw?: any
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* GitHub-specific user type from their API response
|
|
16
|
+
*/
|
|
17
|
+
export declare interface GitHubUser {
|
|
18
|
+
id: number
|
|
19
|
+
login: string
|
|
20
|
+
name: string | null
|
|
21
|
+
avatar_url: string | null
|
|
22
|
+
[key: string]: any
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* GitHub-specific email type from their API response
|
|
26
|
+
*/
|
|
27
|
+
export declare interface GitHubEmail {
|
|
28
|
+
email: string
|
|
29
|
+
primary: boolean
|
|
30
|
+
verified: boolean
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* GitHub-specific OAuth token response
|
|
34
|
+
*/
|
|
35
|
+
export declare interface GitHubTokenResponse {
|
|
36
|
+
access_token: string
|
|
37
|
+
error?: string
|
|
38
|
+
error_description?: string
|
|
39
|
+
}
|
|
40
|
+
export declare interface ProviderInterface {
|
|
41
|
+
getAuthUrl: () => Promise<string>
|
|
42
|
+
getAccessToken: (code: string) => Promise<string>
|
|
43
|
+
getUserByToken: (token: string) => Promise<SocialUser>
|
|
44
|
+
}
|
|
45
|
+
export declare interface TwitterTokenResponse {
|
|
46
|
+
access_token: string
|
|
47
|
+
token_type: string
|
|
48
|
+
expires_in: number
|
|
49
|
+
scope: string
|
|
50
|
+
error?: string
|
|
51
|
+
error_description?: string
|
|
52
|
+
}
|
|
53
|
+
export declare interface TwitterUser {
|
|
54
|
+
id: string
|
|
55
|
+
username: string
|
|
56
|
+
name: string
|
|
57
|
+
email?: string
|
|
58
|
+
profile_image_url?: string
|
|
59
|
+
}
|
|
60
|
+
export declare interface BlueskySessionCredentials {
|
|
61
|
+
identifier: string
|
|
62
|
+
password: string
|
|
63
|
+
}
|
|
64
|
+
export declare interface BlueskySession {
|
|
65
|
+
did: string
|
|
66
|
+
handle: string
|
|
67
|
+
displayName?: string
|
|
68
|
+
accessJwt: string
|
|
69
|
+
refreshJwt: string
|
|
70
|
+
}
|
|
71
|
+
export declare interface SocialIdentityCredentials {
|
|
72
|
+
handle: string
|
|
73
|
+
did?: string
|
|
74
|
+
accessToken?: string
|
|
75
|
+
refreshToken?: string
|
|
76
|
+
}
|
|
77
|
+
export declare interface PublishPostInput {
|
|
78
|
+
text: string
|
|
79
|
+
scheduledAt?: string
|
|
80
|
+
langs?: string[]
|
|
81
|
+
external?: {
|
|
82
|
+
uri: string
|
|
83
|
+
title: string
|
|
84
|
+
description?: string
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export declare interface PublishedPost {
|
|
88
|
+
provider: SocialPublishingProvider
|
|
89
|
+
uri: string
|
|
90
|
+
cid?: string
|
|
91
|
+
url?: string
|
|
92
|
+
}
|
|
93
|
+
export declare interface TimelineQuery {
|
|
94
|
+
cursor?: string
|
|
95
|
+
limit?: number
|
|
96
|
+
}
|
|
97
|
+
export declare interface TimelineResult {
|
|
98
|
+
cursor?: string
|
|
99
|
+
items: Array<{
|
|
100
|
+
uri: string
|
|
101
|
+
authorHandle: string
|
|
102
|
+
authorName?: string
|
|
103
|
+
body: string
|
|
104
|
+
postedAt: string
|
|
105
|
+
likeCount: number
|
|
106
|
+
repostCount: number
|
|
107
|
+
replyCount: number
|
|
108
|
+
}>
|
|
109
|
+
}
|
|
110
|
+
export declare interface SocialPublishingDriver {
|
|
111
|
+
provider: SocialPublishingProvider
|
|
112
|
+
characterLimit: number
|
|
113
|
+
publish: (identity: SocialIdentityCredentials, post: PublishPostInput) => Promise<PublishedPost>
|
|
114
|
+
timeline: (identity: SocialIdentityCredentials, query?: TimelineQuery) => Promise<TimelineResult>
|
|
115
|
+
}
|
|
116
|
+
export type SocialPublishingProvider = | 'bluesky'
|
|
117
|
+
| 'twitter'
|
|
118
|
+
| 'mastodon'
|
|
119
|
+
| 'facebook'
|
|
120
|
+
| 'instagram'
|
|
121
|
+
| 'tiktok'
|
|
122
|
+
| 'linkedin';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/socials",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.42",
|
|
5
5
|
"description": "A simple and elegant social authentication package for Stacks.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"better-dx": "^0.2.12",
|
|
51
|
-
"@stacksjs/error-handling": "0.70.
|
|
52
|
-
"@stacksjs/router": "0.70.
|
|
51
|
+
"@stacksjs/error-handling": "^0.70.42",
|
|
52
|
+
"@stacksjs/router": "^0.70.42"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/dist/src/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './drivers';
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|