@stacksjs/socials 0.70.86 → 0.70.88

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/socials",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.86",
5
+ "version": "0.70.88",
6
6
  "description": "A simple and elegant social authentication package for Stacks.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "better-dx": "^0.2.16",
56
- "@stacksjs/error-handling": "0.70.86",
57
- "@stacksjs/router": "0.70.86"
56
+ "@stacksjs/error-handling": "0.70.88",
57
+ "@stacksjs/router": "0.70.88"
58
58
  }
59
59
  }
@@ -1,45 +0,0 @@
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 _state: string | null;
18
- protected user: SocialUser | null;
19
- constructor(config: ProviderConfig);
20
- abstract getAuthUrl(): Promise<string>;
21
- protected abstract getTokenUrl(): string;
22
- abstract getAccessToken(code: string): Promise<string>;
23
- abstract getUserByToken(token: string): Promise<SocialUser>;
24
- protected getCodeFields(state?: string | null): Record<string, any>;
25
- protected formatScopes(scopes: string[], scopeSeparator: string): string;
26
- userFromToken(token: string): Promise<SocialUser>;
27
- scopes(scopes: string | string[]): this;
28
- setScopes(scopes: string | string[]): this;
29
- getScopes(): string[];
30
- setRedirectUrl(url: string): this;
31
- withState(state: string): this;
32
- protected resolveState(): string;
33
- protected usesState(): boolean;
34
- validateState(expected: string | null | undefined, actual: string | null | undefined): boolean;
35
- protected isStateless(): boolean;
36
- stateless(): this;
37
- protected getState(): string;
38
- protected usesPKCE(): boolean;
39
- enablePKCE(): this;
40
- protected getCodeVerifier(): string;
41
- protected getCodeChallenge(): Promise<string>;
42
- protected getCodeChallengeMethod(): string;
43
- with(parameters: Record<string, any>): this;
44
- protected buildAuthUrlFromBase(url: string, state: string | null): string;
45
- }
@@ -1,56 +0,0 @@
1
- import { AbstractProvider } from '../abstract';
2
- import type { AppleIdTokenClaims, ProviderInterface, SocialUser } from '../types';
3
- import type { ProviderConfig } from '../abstract';
4
- /**
5
- * Apple replaces the static client secret with a signed JWT, so its
6
- * provider config carries the signing inputs instead of clientSecret
7
- * (which may be left '').
8
- */
9
- export declare interface AppleProviderConfig extends ProviderConfig {
10
- teamId?: string
11
- keyId?: string
12
- privateKey?: string
13
- }
14
- /**
15
- * Sign in with Apple (OAuth2 / OIDC).
16
- *
17
- * Apple deviates from the other providers in three ways, all handled
18
- * here so callers keep the same getAuthUrl/getAccessToken/getUserByToken
19
- * contract:
20
- *
21
- * 1. There is no static client secret. Apple requires a short-lived JWT
22
- * signed with an ES256 private key (.p8) issued in the developer
23
- * portal, scoped by team ID and key ID. `generateClientSecret()`
24
- * builds one per token exchange.
25
- * 2. There is no userinfo endpoint. Identity comes from the `id_token`
26
- * returned by the token endpoint, so `getAccessToken()` returns the
27
- * id_token (not the access token — there is nothing to spend it on),
28
- * and `getUserByToken()` decodes its claims. The id_token arrives
29
- * straight from Apple's token endpoint over TLS, which OIDC Core
30
- * 3.1.3.7 accepts in place of local signature verification; iss, aud
31
- * and exp are still validated.
32
- * 3. When scopes are requested (they are by default: name + email),
33
- * Apple mandates `response_mode=form_post` — the callback arrives as
34
- * a cross-site POST, not a GET. Applications must register a POST
35
- * callback route and use a cookie jar that survives cross-site POSTs
36
- * (SameSite=None) if they carry state in cookies.
37
- *
38
- * Note that Apple only transmits the user's name (and only on the very
39
- * first authorization) as a `user` JSON field in the form_post body —
40
- * it is never part of the id_token. Reading it is the application's
41
- * job; `SocialUser.name` from this driver is therefore always ''.
42
- */
43
- export declare class AppleProvider extends AbstractProvider implements ProviderInterface {
44
- protected baseUrl: string;
45
- protected teamId: string;
46
- protected keyId: string;
47
- protected privateKey: string;
48
- constructor(providerConfig: AppleProviderConfig);
49
- getAuthUrl(): Promise<string>;
50
- getAccessToken(code: string): Promise<string>;
51
- getUserByToken(token: string): Promise<SocialUser>;
52
- protected generateClientSecret(): string;
53
- protected decodeIdToken(idToken: string): AppleIdTokenClaims;
54
- protected validateConfig(): void;
55
- protected getTokenUrl(): string;
56
- }
@@ -1,24 +0,0 @@
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
- }
@@ -1,11 +0,0 @@
1
- import { AbstractProvider } from '../abstract';
2
- import type { ProviderInterface, SocialUser } from '../types';
3
- export declare class FacebookProvider extends AbstractProvider implements ProviderInterface {
4
- protected baseUrl: string;
5
- protected apiUrl: string;
6
- getAuthUrl(): Promise<string>;
7
- getAccessToken(code: string): Promise<string>;
8
- getUserByToken(token: string): Promise<SocialUser>;
9
- protected validateConfig(): void;
10
- protected getTokenUrl(): string;
11
- }
@@ -1,13 +0,0 @@
1
- import { AbstractProvider } from '../abstract';
2
- import type { GitHubEmail, ProviderInterface, SocialUser } from '../types';
3
- export declare class GitHubProvider extends AbstractProvider implements ProviderInterface {
4
- protected baseUrl: string;
5
- protected apiUrl: string;
6
- getAuthUrl(): Promise<string>;
7
- getAccessToken(code: string): Promise<string>;
8
- getUserByToken(token: string): Promise<SocialUser>;
9
- protected pickEmail(emails: GitHubEmail[]): GitHubEmail | null;
10
- protected getEmail(emails: GitHubEmail[]): string | null;
11
- protected validateConfig(): void;
12
- protected getTokenUrl(): string;
13
- }
@@ -1,11 +0,0 @@
1
- import { AbstractProvider } from '../abstract';
2
- import type { ProviderInterface, SocialUser } from '../types';
3
- export declare class GoogleProvider extends AbstractProvider implements ProviderInterface {
4
- protected baseUrl: string;
5
- protected apiUrl: string;
6
- getAuthUrl(): Promise<string>;
7
- getAccessToken(code: string): Promise<string>;
8
- getUserByToken(token: string): Promise<SocialUser>;
9
- protected validateConfig(): void;
10
- protected getTokenUrl(): string;
11
- }
@@ -1,9 +0,0 @@
1
- export * from './apple';
2
- export * from './bluesky';
3
- export * from './facebook';
4
- export * from './github';
5
- export * from './google';
6
- export * from './instagram';
7
- export * from './linkedin';
8
- export * from './threads';
9
- export * from './twitter';
@@ -1,51 +0,0 @@
1
- import type { PublishedPost, PublishPostInput, SocialIdentityCredentials, SocialPublishingDriver, TimelineQuery, TimelineResult } from '../types';
2
- export declare interface InstagramDriverOptions {
3
- graphVersion?: string
4
- authBase?: string
5
- graphBase?: string
6
- }
7
- export declare interface InstagramAuthUrlInput {
8
- clientId: string
9
- redirectUrl: string
10
- scopes: string[]
11
- state: string
12
- }
13
- export declare interface InstagramTokenExchangeInput {
14
- clientId: string
15
- clientSecret: string
16
- redirectUrl: string
17
- code: string
18
- }
19
- export declare interface InstagramAccount {
20
- igUserId: string
21
- username?: string
22
- pageAccessToken: string
23
- }
24
- export declare class InstagramApiError extends Error {
25
- public status: number;
26
- public body: string;
27
- constructor(message: string, status: number, body: string);
28
- get isAuthError(): boolean;
29
- }
30
- /**
31
- * Publishing driver for Instagram Business/Creator accounts via the Facebook
32
- * Graph API. Auth is Facebook Login (OAuth 2.0). Publishing is the documented
33
- * two-step flow: create a media container, then publish it.
34
- *
35
- * Instagram does not allow text-only posts — every post requires an image (or
36
- * video) reachable at a public URL, supplied via `post.media`.
37
- */
38
- export declare class InstagramPublishingDriver implements SocialPublishingDriver {
39
- readonly provider: 'instagram';
40
- characterLimit: number;
41
- protected graphVersion: string;
42
- protected authBase: string;
43
- protected graphBase: string;
44
- constructor(options?: InstagramDriverOptions);
45
- getAuthUrl(input: InstagramAuthUrlInput): string;
46
- exchangeCode(input: InstagramTokenExchangeInput): Promise<{ accessToken: string, expiresIn?: number }>;
47
- resolveAccount(accessToken: string): Promise<InstagramAccount>;
48
- publish(identity: SocialIdentityCredentials, post: PublishPostInput): Promise<PublishedPost>;
49
- timeline(_identity: SocialIdentityCredentials, _query?: TimelineQuery): Promise<TimelineResult>;
50
- protected graph<T>(path: string, init: RequestInit): Promise<T>;
51
- }
@@ -1,62 +0,0 @@
1
- import type { PublishedPost, PublishPostInput, SocialIdentityCredentials, SocialPublishingDriver, TimelineQuery, TimelineResult } from '../types';
2
- /**
3
- * Escape the reserved characters of LinkedIn's "little text" commentary format
4
- * so the literal text renders as typed. Without this, characters like `(` `)`
5
- * `@` `#` cause the share to be rejected with a 400.
6
- */
7
- export declare function escapeLinkedInText(text: string): string;
8
- export declare interface LinkedInDriverOptions {
9
- apiVersion?: string
10
- authBase?: string
11
- apiBase?: string
12
- }
13
- export declare interface LinkedInAuthUrlInput {
14
- clientId: string
15
- redirectUrl: string
16
- scopes: string[]
17
- state: string
18
- }
19
- export declare interface LinkedInTokenExchangeInput {
20
- clientId: string
21
- clientSecret: string
22
- redirectUrl: string
23
- code: string
24
- }
25
- export declare interface LinkedInTokenResponse {
26
- accessToken: string
27
- expiresIn?: number
28
- scope?: string
29
- }
30
- export declare interface LinkedInProfile {
31
- sub: string
32
- name?: string
33
- picture?: string
34
- }
35
- export declare class LinkedInApiError extends Error {
36
- public status: number;
37
- public body: string;
38
- constructor(message: string, status: number, body: string);
39
- get isAuthError(): boolean;
40
- }
41
- /**
42
- * Publishing driver for LinkedIn member shares.
43
- *
44
- * Auth is OAuth 2.0 (authorization code). Posting uses the versioned REST
45
- * `/rest/posts` endpoint with the `w_member_social` scope. Unlike Bluesky,
46
- * LinkedIn has no app-password, so a token is always obtained via OAuth (or
47
- * pasted in from a prior OAuth grant).
48
- */
49
- export declare class LinkedInPublishingDriver implements SocialPublishingDriver {
50
- readonly provider: 'linkedin';
51
- characterLimit: number;
52
- protected apiVersion: string;
53
- protected authBase: string;
54
- protected apiBase: string;
55
- constructor(options?: LinkedInDriverOptions);
56
- getAuthUrl(input: LinkedInAuthUrlInput): string;
57
- exchangeCode(input: LinkedInTokenExchangeInput): Promise<LinkedInTokenResponse>;
58
- getProfile(accessToken: string): Promise<LinkedInProfile>;
59
- publish(identity: SocialIdentityCredentials, post: PublishPostInput): Promise<PublishedPost>;
60
- timeline(_identity: SocialIdentityCredentials, _query?: TimelineQuery): Promise<TimelineResult>;
61
- protected request<T>(url: string, init: RequestInit): Promise<T>;
62
- }
@@ -1,52 +0,0 @@
1
- import type { PublishedPost, PublishPostInput, SocialIdentityCredentials, SocialPublishingDriver, TimelineQuery, TimelineResult } from '../types';
2
- export declare interface ThreadsDriverOptions {
3
- graphVersion?: string
4
- authBase?: string
5
- graphBase?: string
6
- }
7
- export declare interface ThreadsAuthUrlInput {
8
- clientId: string
9
- redirectUrl: string
10
- scopes: string[]
11
- state: string
12
- }
13
- export declare interface ThreadsTokenExchangeInput {
14
- clientId: string
15
- clientSecret: string
16
- redirectUrl: string
17
- code: string
18
- }
19
- export declare interface ThreadsAccount {
20
- threadsUserId: string
21
- username?: string
22
- accessToken: string
23
- }
24
- export declare class ThreadsApiError extends Error {
25
- public status: number;
26
- public body: string;
27
- constructor(message: string, status: number, body: string);
28
- get isAuthError(): boolean;
29
- }
30
- /**
31
- * Publishing driver for Threads (Meta) via the Threads Graph API. Auth is the
32
- * Threads OAuth flow (`threads.net/oauth/authorize`, scopes `threads_basic` +
33
- * `threads_content_publish`). Publishing is the documented two-step flow:
34
- * create a media container, then publish it.
35
- *
36
- * Unlike Instagram, Threads allows text-only posts — `post.media` is optional
37
- * and, when present, the container is created as an `IMAGE` instead of `TEXT`.
38
- */
39
- export declare class ThreadsPublishingDriver implements SocialPublishingDriver {
40
- readonly provider: 'threads';
41
- characterLimit: number;
42
- protected graphVersion: string;
43
- protected authBase: string;
44
- protected graphBase: string;
45
- constructor(options?: ThreadsDriverOptions);
46
- getAuthUrl(input: ThreadsAuthUrlInput): string;
47
- exchangeCode(input: ThreadsTokenExchangeInput): Promise<{ accessToken: string, userId?: string, expiresIn?: number }>;
48
- resolveAccount(accessToken: string): Promise<ThreadsAccount>;
49
- publish(identity: SocialIdentityCredentials, post: PublishPostInput): Promise<PublishedPost>;
50
- timeline(_identity: SocialIdentityCredentials, _query?: TimelineQuery): Promise<TimelineResult>;
51
- protected graph<T>(path: string, init: RequestInit): Promise<T>;
52
- }
@@ -1,11 +0,0 @@
1
- import { AbstractProvider } from '../abstract';
2
- import type { ProviderInterface, SocialUser } from '../types';
3
- export declare class TwitterProvider extends AbstractProvider implements ProviderInterface {
4
- protected baseUrl: string;
5
- protected apiUrl: string;
6
- getAuthUrl(): Promise<string>;
7
- getAccessToken(code: string): Promise<string>;
8
- getUserByToken(token: string): Promise<SocialUser>;
9
- protected validateConfig(): void;
10
- protected getTokenUrl(): string;
11
- }
@@ -1,6 +0,0 @@
1
- export declare class InvalidStateException extends Error {
2
- constructor(message?: string);
3
- }
4
- export declare class ConfigException extends Error {
5
- constructor(message: string);
6
- }
package/dist/index.d.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from './abstract';
2
- export * from './drivers/index';
3
- export * from './exceptions';
4
- export * from './token';
5
- export * from './types';
package/dist/index.js DELETED
@@ -1,3 +0,0 @@
1
- // @bun
2
- var _=import.meta.require;class N{clientId;clientSecret;redirectUrl;parameters={};_scopes=[];scopeSeparator=",";_stateless=!1;_usesPKCE=!1;_state=null;user=null;constructor(w){this.clientId=w.clientId,this.clientSecret=w.clientSecret,this.redirectUrl=w.redirectUrl}getCodeFields(w=null){let h={client_id:this.clientId,redirect_uri:this.redirectUrl,scope:this.formatScopes(this.getScopes(),this.scopeSeparator),response_type:"code"};if(this.usesState())h.state=w;if(this.usesPKCE())h.code_challenge=this.getCodeChallenge(),h.code_challenge_method=this.getCodeChallengeMethod();return{...h,...this.parameters}}formatScopes(w,h){return w.join(h)}async userFromToken(w){return{...await this.getUserByToken(w),token:w}}scopes(w){let h=Array.isArray(w)?w:[w];return this._scopes=[...new Set([...this._scopes,...h])],this}setScopes(w){let h=Array.isArray(w)?w:[w];return this._scopes=[...new Set(h)],this}getScopes(){return this._scopes}setRedirectUrl(w){if(typeof w!=="string"||w.length===0)throw Error("[socials] setRedirectUrl requires a non-empty string");let h;try{h=new URL(w)}catch{throw Error(`[socials] setRedirectUrl: invalid URL: ${w}`)}if(h.protocol!=="https:"&&h.protocol!=="http:")throw Error(`[socials] setRedirectUrl protocol must be http(s)://, got ${h.protocol}`);return this.redirectUrl=w,this}withState(w){if(typeof w!=="string"||w.length===0)throw Error("[socials] withState requires a non-empty string");return this._state=w,this}resolveState(){return this._state??this.getState()}usesState(){return!this._stateless}validateState(w,h){if(typeof w!=="string"||typeof h!=="string")return!1;if(w.length===0||h.length===0)return!1;if(w.length!==h.length)return!1;try{let{timingSafeEqual:P}=_("crypto");return P(Buffer.from(w,"utf8"),Buffer.from(h,"utf8"))}catch{let P=0;for(let S=0;S<w.length;S++)P|=w.charCodeAt(S)^h.charCodeAt(S);return P===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((h)=>h.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((h)=>h.toString(16).padStart(2,"0")).join("")}async getCodeChallenge(){let h=new TextEncoder().encode(this.getCodeVerifier()),P=await crypto.subtle.digest("SHA-256",h),{Buffer:S}=await import("buffer");return S.from(new Uint8Array(P)).toString("base64url")}getCodeChallengeMethod(){return"S256"}with(w){return this.parameters=w,this}buildAuthUrlFromBase(w,h){let P=new URLSearchParams(this.getCodeFields(h));return`${w}?${P.toString()}`}}import{Buffer as X}from"buffer";import{createPrivateKey as V,sign as v}from"crypto";import{config as R}from"@stacksjs/config";class m extends Error{constructor(w="Invalid state"){super(w);this.name="InvalidStateException"}}class B extends Error{constructor(w){super(w);this.name="ConfigException"}}class l extends N{baseUrl="https://appleid.apple.com";teamId="";keyId="";privateKey="";constructor(w){super(w);this.teamId=w.teamId??"",this.keyId=w.keyId??"",this.privateKey=w.privateKey??""}getConfig(){let w={clientId:this.clientId||(R.services.apple?.clientId??""),teamId:this.teamId||(R.services.apple?.teamId??""),keyId:this.keyId||(R.services.apple?.keyId??""),privateKey:(this.privateKey||(R.services.apple?.privateKey??"")).replace(/\\n/g,`
3
- `),redirectUrl:this.redirectUrl||(R.services.apple?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:R.services.apple?.scopes??["name","email"]};return this.setScopes(w.scopes),w}async getAuthUrl(){let w=this.resolveState(),{clientId:h,redirectUrl:P,scopes:S}=this.getConfig();this.validateConfig();let $={client_id:h,redirect_uri:P,scope:S.join(" "),state:w,response_type:"code",...this.parameters};if(S.length>0)$.response_mode="form_post";return`${this.baseUrl}/auth/authorize?${new URLSearchParams($).toString()}`}async getAccessToken(w){let{clientId:h,redirectUrl:P}=this.getConfig();this.validateConfig();let S=await fetch(`${this.baseUrl}/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:w,redirect_uri:P,client_id:h,client_secret:this.generateClientSecret()})}),$=await S.json();if(!S.ok||$.error)throw Error(`Apple OAuth error: ${$.error_description??$.error??`HTTP ${S.status}`}`);if(!$.id_token)throw Error("Apple OAuth error: token response contained no id_token");return $.id_token}async getUserByToken(w){let{clientId:h}=this.getConfig(),P=this.decodeIdToken(w),S=P.iss===this.baseUrl,$=Array.isArray(P.aud)?P.aud.includes(h):P.aud===h,J=typeof P.exp==="number"&&P.exp*1000>Date.now();if(!S||!$||!J)throw Error("Apple OAuth error: id_token claims failed validation (iss/aud/exp)");if(!P.sub)throw Error("Apple OAuth error: id_token has no subject");let D=typeof P.email==="string"?P.email:null,u=null;if(P.email_verified===!0||P.email_verified==="true")u=!0;else if(P.email_verified===!1||P.email_verified==="false")u=!1;return{id:String(P.sub),nickname:null,name:"",email:D,emailVerified:u,avatar:null,token:w,raw:P}}generateClientSecret(){let{clientId:w,teamId:h,keyId:P,privateKey:S}=this.getConfig(),$=Math.floor(Date.now()/1000),J={alg:"ES256",kid:P,typ:"JWT"},D={iss:h,iat:$,exp:$+3600,aud:this.baseUrl,sub:w},u=`${this.base64urlJson(J)}.${this.base64urlJson(D)}`,Z;try{Z=V(S)}catch(H){throw new B(`Apple private key could not be parsed: ${H instanceof Error?H.message:String(H)}`)}let L=v("sha256",X.from(u),{key:Z,dsaEncoding:"ieee-p1363"});return`${u}.${L.toString("base64url")}`}decodeIdToken(w){let h=w.split(".");if(h.length!==3)throw Error("Apple OAuth error: malformed id_token");return JSON.parse(X.from(h[1],"base64url").toString("utf8"))}base64urlJson(w){return X.from(JSON.stringify(w)).toString("base64url")}validateConfig(){let{clientId:w,teamId:h,keyId:P,privateKey:S,redirectUrl:$}=this.getConfig();if(!w)throw new B("Apple client ID (Service ID) not provided");if(!h)throw new B("Apple team ID not provided");if(!P)throw new B("Apple key ID not provided");if(!S)throw new B("Apple private key not provided");if(!$)throw new B("Apple redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/auth/token`}}class T extends Error{status;body;constructor(w,h,P){super(w);this.status=h;this.body=P;this.name="BlueskyApiError"}get isAuthError(){return this.status===400||this.status===401||this.status===403}}class U{provider="bluesky";characterLimit=300;service;constructor(w={}){this.service=w.service||"https://bsky.social"}async createSession(w){let h=w.identifier.trim(),P=w.password.trim();if(!h)throw Error("Bluesky identifier is required.");if(!P)throw Error("Bluesky app password is required.");let S=await this.post("/xrpc/com.atproto.server.createSession",{identifier:h,password:P}),$=await this.getProfile({did:S.did,handle:S.handle,accessToken:S.accessJwt,refreshToken:S.refreshJwt}).catch(()=>{return});return{did:S.did,handle:S.handle,displayName:$?.displayName,accessJwt:S.accessJwt,refreshJwt:S.refreshJwt}}async refreshSession(w){if(!w)throw Error("Bluesky refresh token is required.");let h=await this.post("/xrpc/com.atproto.server.refreshSession",void 0,{authorization:`Bearer ${w}`});return{did:h.did,handle:h.handle,accessJwt:h.accessJwt,refreshJwt:h.refreshJwt}}async publish(w,h){let P=w.did||w.handle;if(!w.accessToken)throw Error("Bluesky access token is missing for this identity.");if(!P)throw Error("Bluesky identity DID or handle is required.");if(h.text.length>this.characterLimit)throw Error(`Bluesky posts must be ${this.characterLimit} characters or fewer.`);let S={$type:"app.bsky.feed.post",text:h.text,createdAt:h.scheduledAt||new Date().toISOString()};if(h.langs?.length)S.langs=h.langs;if(h.external)S.embed={$type:"app.bsky.embed.external",external:{uri:h.external.uri,title:h.external.title,description:h.external.description||""}};let $=await this.post("/xrpc/com.atproto.repo.createRecord",{repo:P,collection:"app.bsky.feed.post",record:S},{authorization:`Bearer ${w.accessToken}`});return{provider:this.provider,uri:$.uri,cid:$.cid,url:this.toPostUrl(w.handle,$.uri)}}async timeline(w,h={}){if(!w.accessToken)throw Error("Bluesky access token is missing for this identity.");let P=new URL(`${this.service}/xrpc/app.bsky.feed.getTimeline`);if(P.searchParams.set("limit",String(Math.min(Math.max(h.limit||30,1),100))),h.cursor)P.searchParams.set("cursor",h.cursor);let S=await this.request(P,{headers:{authorization:`Bearer ${w.accessToken}`}});return{cursor:S.cursor,items:(S.feed||[]).flatMap(($)=>{let J=$.post;if(!J?.uri||!J.author?.handle)return[];return[{uri:J.uri,authorHandle:J.author.handle,authorName:J.author.displayName,authorAvatar:J.author.avatar,postUrl:this.toPostUrl(J.author.handle,J.uri),body:J.record?.text||"",postedAt:J.record?.createdAt||new Date().toISOString(),likeCount:J.likeCount||0,repostCount:J.repostCount||0,replyCount:J.replyCount||0}]})}}async getProfile(w){if(!w.accessToken)throw Error("Bluesky access token is missing for this identity.");let h=w.did||w.handle;if(!h)throw Error("Bluesky identity DID or handle is required.");let P=new URL(`${this.service}/xrpc/app.bsky.actor.getProfile`);return P.searchParams.set("actor",h),await this.request(P,{headers:{authorization:`Bearer ${w.accessToken}`}})}async post(w,h,P={}){return await this.request(new URL(`${this.service}${w}`),{method:"POST",headers:{...h===void 0?{}:{"content-type":"application/json"},...P},...h===void 0?{}:{body:JSON.stringify(h)}})}async request(w,h){let P=await fetch(w,h),S=await P.text();if(!P.ok)throw new T(`Bluesky API failed (${P.status}): ${S||P.statusText}`,P.status,S);return S?JSON.parse(S):{}}toPostUrl(w,h){let P=h.split("/").pop();return`https://bsky.app/profile/${w}/post/${P}`}}import{fetcher as W}from"@stacksjs/api";import{config as Q}from"@stacksjs/config";class C extends N{baseUrl="https://www.facebook.com";apiUrl="https://graph.facebook.com";getConfig(){let w={clientId:Q.services.facebook?.clientId??"",clientSecret:Q.services.facebook?.clientSecret??"",redirectUrl:Q.services.facebook?.redirectUrl??"",scopes:Q.services.facebook?.scopes??["email","public_profile"]};return this.setScopes(w.scopes),w}async getAuthUrl(){let w=this.getState(),{clientId:h,redirectUrl:P,scopes:S}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/v18.0/dialog/oauth?${new URLSearchParams({client_id:h,redirect_uri:P,scope:S.join(","),state:w,response_type:"code"}).toString()}`}async getAccessToken(w){let{clientId:h,clientSecret:P,redirectUrl:S}=this.getConfig();this.validateConfig();let $=await W.get(`${this.apiUrl}/v18.0/oauth/access_token?${new URLSearchParams({client_id:h,client_secret:P,redirect_uri:S,code:w}).toString()}`);if($.data.error)throw Error(`Facebook OAuth error: ${$.data.error.message}`);return $.data.access_token}async getUserByToken(w){let h=await W.get(`${this.apiUrl}/v18.0/me?${new URLSearchParams({access_token:w,fields:"id,name,email,picture"}).toString()}`);return{id:h.data.id,nickname:null,name:h.data.name,email:h.data.email??null,avatar:h.data.picture?.data.url??null,token:w,raw:h.data}}validateConfig(){let{clientId:w,clientSecret:h,redirectUrl:P}=this.getConfig();if(!w)throw new B("Facebook client ID not provided");if(!h)throw new B("Facebook client secret not provided");if(!P)throw new B("Facebook redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/v18.0/oauth/access_token`}}import{fetcher as Y}from"@stacksjs/api";import{config as q}from"@stacksjs/config";class x extends N{baseUrl="https://github.com";apiUrl="https://api.github.com";getConfig(){let w={clientId:this.clientId||(q.services.github?.clientId??""),clientSecret:this.clientSecret||(q.services.github?.clientSecret??""),redirectUrl:this.redirectUrl||(q.services.github?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:q.services.github?.scopes??["read:user","user:email"]};return this.setScopes(w.scopes),w}async getAuthUrl(){let w=this.resolveState(),{clientId:h,redirectUrl:P,scopes:S}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/login/oauth/authorize?${new URLSearchParams({client_id:h,redirect_uri:P,scope:S.join(" "),state:w,response_type:"code",...this.parameters}).toString()}`}async getAccessToken(w){let{clientId:h,clientSecret:P,redirectUrl:S}=this.getConfig();this.validateConfig();let $=await Y.post(`${this.baseUrl}/login/oauth/access_token`,{client_id:h,client_secret:P,code:w,redirect_uri:S});if($.data.error)throw Error(`GitHub OAuth error: ${$.data.error_description}`);return $.data.access_token}async getUserByToken(w){let[h,P]=await Promise.all([Y.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${w}`}).get(`${this.apiUrl}/user`),Y.withHeaders({Accept:"application/vnd.github.v3+json",Authorization:`token ${w}`}).get(`${this.apiUrl}/user/emails`)]),S=this.pickEmail(P.data);return{id:h.data.id.toString(),nickname:h.data.login,name:h.data.name??h.data.login,email:S?.email??h.data.email??null,emailVerified:S?S.verified:null,avatar:h.data.avatar_url,token:w,raw:h.data}}pickEmail(w){if(!Array.isArray(w)||w.length===0)return null;let h=w.find((S)=>S.primary&&S.verified),P=w.find((S)=>S.verified);return h??P??w.find((S)=>S.primary)??w[0]??null}getEmail(w){return this.pickEmail(w)?.email??null}validateConfig(){let{clientId:w,clientSecret:h,redirectUrl:P}=this.getConfig();if(!w)throw new B("GitHub client ID not provided");if(!h)throw new B("GitHub client secret not provided");if(!P)throw new B("GitHub redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/login/oauth/access_token`}}import{fetcher as b}from"@stacksjs/api";import{config as F}from"@stacksjs/config";class k extends N{baseUrl="https://accounts.google.com";apiUrl="https://www.googleapis.com";getConfig(){let w={clientId:this.clientId||(F.services.google?.clientId??""),clientSecret:this.clientSecret||(F.services.google?.clientSecret??""),redirectUrl:this.redirectUrl||(F.services.google?.redirectUrl??""),scopes:this._scopes.length>0?this._scopes:F.services.google?.scopes??["openid","email"]};return this.setScopes(w.scopes),w}async getAuthUrl(){let w=this.resolveState(),{clientId:h,redirectUrl:P,scopes:S}=this.getConfig();return this.validateConfig(),`${this.baseUrl}/o/oauth2/v2/auth?${new URLSearchParams({client_id:h,redirect_uri:P,scope:S.join(" "),state:w,response_type:"code",access_type:"offline",prompt:"consent",...this.parameters}).toString()}`}async getAccessToken(w){let{clientId:h,clientSecret:P,redirectUrl:S}=this.getConfig();this.validateConfig();let $=await b.post(`${this.baseUrl}/oauth2/v4/token`,{client_id:h,client_secret:P,code:w,redirect_uri:S,grant_type:"authorization_code"});if($.data.error)throw Error(`Google OAuth error: ${$.data.error_description}`);return $.data.access_token}async getUserByToken(w){let h=await b.withHeaders({Authorization:`Bearer ${w}`}).get(`${this.apiUrl}/oauth2/v2/userinfo`);return{id:h.data.id,nickname:h.data.given_name,name:h.data.name,email:h.data.email,emailVerified:typeof h.data.verified_email==="boolean"?h.data.verified_email:null,avatar:h.data.picture,token:w,raw:h.data}}validateConfig(){let{clientId:w,clientSecret:h,redirectUrl:P}=this.getConfig();if(!w)throw new B("Google client ID not provided");if(!h)throw new B("Google client secret not provided");if(!P)throw new B("Google redirect URL not provided")}getTokenUrl(){return`${this.baseUrl}/oauth2/v4/token`}}class O extends Error{status;body;constructor(w,h,P){super(w);this.status=h;this.body=P;this.name="InstagramApiError"}get isAuthError(){return this.status===401||this.status===403||this.status===190}}class A{provider="instagram";characterLimit=2200;graphVersion;authBase;graphBase;constructor(w={}){this.graphVersion=w.graphVersion||"v21.0",this.authBase=w.authBase||"https://www.facebook.com",this.graphBase=w.graphBase||"https://graph.facebook.com"}getAuthUrl(w){let h=new URLSearchParams({client_id:w.clientId,redirect_uri:w.redirectUrl,scope:w.scopes.join(","),state:w.state,response_type:"code"});return`${this.authBase}/${this.graphVersion}/dialog/oauth?${h.toString()}`}async exchangeCode(w){let h=new URLSearchParams({client_id:w.clientId,client_secret:w.clientSecret,redirect_uri:w.redirectUrl,code:w.code}),P=await this.graph(`/oauth/access_token?${h.toString()}`,{method:"GET"});if(!P.access_token)throw new O("Facebook did not return an access token.",400,JSON.stringify(P));return{accessToken:P.access_token,expiresIn:P.expires_in}}async resolveAccount(w){let h=new URLSearchParams({fields:"name,access_token,instagram_business_account{id,username}",access_token:w}),P=await this.graph(`/me/accounts?${h.toString()}`,{method:"GET"}),S=(P.data||[]).find((J)=>J.instagram_business_account?.id),$=S?.instagram_business_account;if(!$?.id||!S?.access_token)throw new O("No Instagram Business account is linked to your Facebook Pages.",400,JSON.stringify(P));return{igUserId:$.id,username:$.username,pageAccessToken:S.access_token}}async publish(w,h){if(!w.accessToken)throw Error("Instagram access token is missing for this identity.");let P=w.did;if(!P)throw Error("Instagram account id is required to publish.");let S=h.media?.[0];if(!S?.url)throw Error("Instagram requires an image to post.");if(h.text.length>this.characterLimit)throw Error(`Instagram captions must be ${this.characterLimit} characters or fewer.`);let $=await this.graph(`/${P}/media`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({image_url:S.url,caption:h.text,access_token:w.accessToken}).toString()});if(!$.id)throw new O("Instagram did not return a media container id.",400,JSON.stringify($));let J=await this.graph(`/${P}/media_publish`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({creation_id:$.id,access_token:w.accessToken}).toString()}),D=await this.graph(`/${J.id}?fields=permalink&access_token=${encodeURIComponent(w.accessToken)}`,{method:"GET"}).catch(()=>{return});return{provider:this.provider,uri:J.id,url:D?.permalink}}async timeline(w,h={}){return{items:[]}}async graph(w,h){let P=await fetch(`${this.graphBase}/${this.graphVersion}${w}`,h),S=await P.text(),$={};try{$=S?JSON.parse(S):{}}catch{$={}}if(!P.ok||$?.error){let J=$?.error?.message||S||P.statusText;throw new O(`Instagram API failed (${P.status}): ${J}`,P.status,S)}return $}}class M extends Error{status;body;constructor(w,h,P){super(w);this.status=h;this.body=P;this.name="LinkedInApiError"}get isAuthError(){return this.status===401||this.status===403}}class E{provider="linkedin";characterLimit=3000;apiVersion;authBase;apiBase;constructor(w={}){this.apiVersion=w.apiVersion||"202405",this.authBase=w.authBase||"https://www.linkedin.com",this.apiBase=w.apiBase||"https://api.linkedin.com"}getAuthUrl(w){let h=new URLSearchParams({response_type:"code",client_id:w.clientId,redirect_uri:w.redirectUrl,scope:w.scopes.join(" "),state:w.state});return`${this.authBase}/oauth/v2/authorization?${h.toString()}`}async exchangeCode(w){let h=new URLSearchParams({grant_type:"authorization_code",code:w.code,redirect_uri:w.redirectUrl,client_id:w.clientId,client_secret:w.clientSecret}),P=await this.request(`${this.authBase}/oauth/v2/accessToken`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:h.toString()});if(!P.access_token)throw new M("LinkedIn did not return an access token.",400,JSON.stringify(P));return{accessToken:P.access_token,expiresIn:P.expires_in,scope:P.scope}}async getProfile(w){if(!w)throw Error("LinkedIn access token is required.");let h=await this.request(`${this.apiBase}/v2/userinfo`,{headers:{authorization:`Bearer ${w}`}});if(!h.sub)throw new M("LinkedIn profile is missing a subject id.",400,JSON.stringify(h));return{sub:h.sub,name:h.name,picture:h.picture}}async publish(w,h){if(!w.accessToken)throw Error("LinkedIn access token is missing for this identity.");let P=w.did;if(!P)throw Error("LinkedIn member URN is required to publish.");if(h.text.length>this.characterLimit)throw Error(`LinkedIn posts must be ${this.characterLimit} characters or fewer.`);let S={author:P,commentary:j(h.text),visibility:"PUBLIC",distribution:{feedDistribution:"MAIN_FEED",targetEntities:[],thirdPartyDistributionChannels:[]},lifecycleState:"PUBLISHED",isReshareDisabledByAuthor:!1};if(h.external)S.content={article:{source:h.external.uri,title:h.external.title,description:h.external.description||""}};let $=await fetch(`${this.apiBase}/rest/posts`,{method:"POST",headers:{authorization:`Bearer ${w.accessToken}`,"content-type":"application/json","linkedin-version":this.apiVersion,"x-restli-protocol-version":"2.0.0"},body:JSON.stringify(S)}),J=await $.text();if(!$.ok)throw new M(`LinkedIn API failed (${$.status}): ${J||$.statusText}`,$.status,J);let D=$.headers.get("x-restli-id")||$.headers.get("x-linkedin-id")||"";return{provider:this.provider,uri:D,url:D?`https://www.linkedin.com/feed/update/${D}`:void 0}}async timeline(w,h={}){return{items:[]}}async request(w,h){let P=await fetch(w,h),S=await P.text();if(!P.ok)throw new M(`LinkedIn API failed (${P.status}): ${S||P.statusText}`,P.status,S);return S?JSON.parse(S):{}}}function j(w){return w.replace(/[\\|{}@[\]()<>#*_~]/g,"\\$&")}class z extends Error{status;body;constructor(w,h,P){super(w);this.status=h;this.body=P;this.name="ThreadsApiError"}get isAuthError(){return this.status===401||this.status===403||this.status===190}}class y{provider="threads";characterLimit=500;graphVersion;authBase;graphBase;constructor(w={}){this.graphVersion=w.graphVersion||"v1.0",this.authBase=w.authBase||"https://threads.net",this.graphBase=w.graphBase||"https://graph.threads.net"}getAuthUrl(w){let h=new URLSearchParams({client_id:w.clientId,redirect_uri:w.redirectUrl,scope:w.scopes.join(","),response_type:"code",state:w.state});return`${this.authBase}/oauth/authorize?${h.toString()}`}async exchangeCode(w){let h=await fetch(`${this.graphBase}/oauth/access_token`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:w.clientId,client_secret:w.clientSecret,grant_type:"authorization_code",redirect_uri:w.redirectUrl,code:w.code}).toString()}),P=await h.text(),S={};try{S=P?JSON.parse(P):{}}catch{S={}}if(!h.ok||S?.error||!S?.access_token){let $=S?.error_message||S?.error?.message||P||h.statusText;throw new z(`Threads token exchange failed (${h.status}): ${$}`,h.status,P)}return{accessToken:S.access_token,userId:S.user_id!=null?String(S.user_id):void 0,expiresIn:S.expires_in}}async resolveAccount(w){let h=new URLSearchParams({fields:"id,username",access_token:w}),P=await this.graph(`/me?${h.toString()}`,{method:"GET"});if(!P.id)throw new z("Could not resolve the Threads account for this token.",400,JSON.stringify(P));return{threadsUserId:P.id,username:P.username,accessToken:w}}async publish(w,h){if(!w.accessToken)throw Error("Threads access token is missing for this identity.");let P=w.did;if(!P)throw Error("Threads account id is required to publish.");if(h.text.length>this.characterLimit)throw Error(`Threads posts must be ${this.characterLimit} characters or fewer.`);let S=h.media?.[0],$=new URLSearchParams({text:h.text,access_token:w.accessToken});if(S?.url)$.set("media_type","IMAGE"),$.set("image_url",S.url);else $.set("media_type","TEXT");let J=await this.graph(`/${P}/threads`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:$.toString()});if(!J.id)throw new z("Threads did not return a media container id.",400,JSON.stringify(J));let D=await this.graph(`/${P}/threads_publish`,{method:"POST",headers:{"content-type":"application/x-www-form-urlencoded"},body:new URLSearchParams({creation_id:J.id,access_token:w.accessToken}).toString()});if(!D.id)throw new z("Threads did not return a published post id.",400,JSON.stringify(D));let u=await this.graph(`/${D.id}?fields=permalink&access_token=${encodeURIComponent(w.accessToken)}`,{method:"GET"}).catch(()=>{return});return{provider:this.provider,uri:D.id,url:u?.permalink}}async timeline(w,h={}){return{items:[]}}async graph(w,h){let P=await fetch(`${this.graphBase}/${this.graphVersion}${w}`,h),S=await P.text(),$={};try{$=S?JSON.parse(S):{}}catch{$={}}if(!P.ok||$?.error){let J=$?.error?.message||S||P.statusText;throw new z(`Threads API failed (${P.status}): ${J}`,P.status,S)}return $}}import{Buffer as I}from"buffer";import{createHash as g,randomBytes as r}from"crypto";import{fetcher as K}from"@stacksjs/api";import{config as G}from"@stacksjs/config";class f extends N{baseUrl="https://twitter.com";apiUrl="https://api.twitter.com";codeVerifier=null;getConfig(){let w={clientId:G.services.twitter?.clientId??"",clientSecret:G.services.twitter?.clientSecret??"",redirectUrl:G.services.twitter?.redirectUrl??"",scopes:G.services.twitter?.scopes??["users.read","tweet.read"]};return this.setScopes(w.scopes),w}generateCodeVerifier(){return r(32).toString("base64").replace(/[^a-z0-9]/gi,"").substring(0,128)}generateCodeChallenge(w){return g("sha256").update(w).digest("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async getAuthUrl(){let w=this.getState(),{clientId:h,redirectUrl:P,scopes:S}=this.getConfig();this.validateConfig(),this.codeVerifier=this.generateCodeVerifier();let $=this.generateCodeChallenge(this.codeVerifier);return`${this.baseUrl}/i/oauth2/authorize?${new URLSearchParams({client_id:h,redirect_uri:P,scope:S.join(" "),state:w,response_type:"code",code_challenge:$,code_challenge_method:"S256"}).toString()}`}async getAccessToken(w){let{clientId:h,clientSecret:P,redirectUrl:S}=this.getConfig();if(this.validateConfig(),!this.codeVerifier)throw Error("Code verifier not found. Please ensure getAuthUrl() is called first.");let $=I.from(`${h}:${P}`).toString("base64"),J=await K.withHeaders({Authorization:`Basic ${$}`,"Content-Type":"application/x-www-form-urlencoded"}).post(`${this.apiUrl}/2/oauth2/token`,{code:w,grant_type:"authorization_code",redirect_uri:S,code_verifier:this.codeVerifier});if(J.data.error)throw Error(`Twitter OAuth error: ${J.data.error_description}`);return J.data.access_token}async getUserByToken(w){let h=await K.withHeaders({Authorization:`Bearer ${w}`}).get(`${this.apiUrl}/2/users/me?user.fields=profile_image_url`);return{id:h.data.id,nickname:h.data.username,name:h.data.name,email:h.data.email??null,avatar:h.data.profile_image_url??null,token:w,raw:h.data}}validateConfig(){let{clientId:w,clientSecret:h,redirectUrl:P}=this.getConfig();if(!w)throw new B("Twitter client ID not provided");if(!h)throw new B("Twitter client secret not provided");if(!P)throw new B("Twitter redirect URL not provided")}getTokenUrl(){return`${this.apiUrl}/2/oauth2/token`}}class a{accessToken;refreshToken;expiresIn;approvedScopes;constructor(w,h=null,P=null,S=[]){this.accessToken=w;this.refreshToken=h;this.expiresIn=P;this.approvedScopes=S}}export{j as escapeLinkedInText,f as TwitterProvider,a as Token,y as ThreadsPublishingDriver,z as ThreadsApiError,E as LinkedInPublishingDriver,M as LinkedInApiError,m as InvalidStateException,A as InstagramPublishingDriver,O as InstagramApiError,k as GoogleProvider,x as GitHubProvider,C as FacebookProvider,B as ConfigException,U as BlueskyPublishingDriver,T as BlueskyApiError,l as AppleProvider,N as AbstractProvider};
package/dist/token.d.ts DELETED
@@ -1,7 +0,0 @@
1
- export declare class Token {
2
- public accessToken: string;
3
- public refreshToken?: string | null;
4
- public expiresIn?: number | null;
5
- public approvedScopes?: string[];
6
- constructor(accessToken: string, refreshToken?: string | null, expiresIn?: number | null, approvedScopes?: string[]);
7
- }
package/dist/types.d.ts DELETED
@@ -1,159 +0,0 @@
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
- emailVerified?: boolean | null
11
- avatar: string | null
12
- token: string
13
- raw?: any
14
- }
15
- /**
16
- * GitHub-specific user type from their API response
17
- */
18
- export declare interface GitHubUser {
19
- id: number
20
- login: string
21
- name: string | null
22
- avatar_url: string | null
23
- [key: string]: any
24
- }
25
- /**
26
- * GitHub-specific email type from their API response
27
- */
28
- export declare interface GitHubEmail {
29
- email: string
30
- primary: boolean
31
- verified: boolean
32
- }
33
- /**
34
- * GitHub-specific OAuth token response
35
- */
36
- export declare interface GitHubTokenResponse {
37
- access_token: string
38
- error?: string
39
- error_description?: string
40
- }
41
- export declare interface ProviderInterface {
42
- getAuthUrl: () => Promise<string>
43
- getAccessToken: (code: string) => Promise<string>
44
- getUserByToken: (token: string) => Promise<SocialUser>
45
- }
46
- export declare interface TwitterTokenResponse {
47
- access_token: string
48
- token_type: string
49
- expires_in: number
50
- scope: string
51
- error?: string
52
- error_description?: string
53
- }
54
- export declare interface TwitterUser {
55
- id: string
56
- username: string
57
- name: string
58
- email?: string
59
- profile_image_url?: string
60
- }
61
- /**
62
- * Apple-specific token response from https://appleid.apple.com/auth/token
63
- */
64
- export declare interface AppleTokenResponse {
65
- access_token: string
66
- token_type: string
67
- expires_in: number
68
- refresh_token?: string
69
- id_token: string
70
- error?: string
71
- error_description?: string
72
- }
73
- /**
74
- * Claims Apple places in the id_token. `email_verified` and
75
- * `is_private_email` arrive as booleans or the strings 'true'/'false'
76
- * depending on the API era.
77
- */
78
- export declare interface AppleIdTokenClaims {
79
- iss: string
80
- aud: string | string[]
81
- exp: number
82
- iat: number
83
- sub: string
84
- nonce?: string
85
- email?: string
86
- email_verified?: boolean | 'true' | 'false'
87
- is_private_email?: boolean | 'true' | 'false'
88
- [key: string]: any
89
- }
90
- export declare interface BlueskySessionCredentials {
91
- identifier: string
92
- password: string
93
- }
94
- export declare interface BlueskySession {
95
- did: string
96
- handle: string
97
- displayName?: string
98
- accessJwt: string
99
- refreshJwt: string
100
- }
101
- export declare interface SocialIdentityCredentials {
102
- handle: string
103
- did?: string
104
- accessToken?: string
105
- refreshToken?: string
106
- }
107
- export declare interface PublishPostInput {
108
- text: string
109
- scheduledAt?: string
110
- langs?: string[]
111
- external?: {
112
- uri: string
113
- title: string
114
- description?: string
115
- }
116
- media?: Array<{
117
- url: string
118
- altText?: string
119
- }>
120
- }
121
- export declare interface PublishedPost {
122
- provider: SocialPublishingProvider
123
- uri: string
124
- cid?: string
125
- url?: string
126
- }
127
- export declare interface TimelineQuery {
128
- cursor?: string
129
- limit?: number
130
- }
131
- export declare interface TimelineResult {
132
- cursor?: string
133
- items: Array<{
134
- uri: string
135
- authorHandle: string
136
- authorName?: string
137
- authorAvatar?: string
138
- postUrl?: string
139
- body: string
140
- postedAt: string
141
- likeCount: number
142
- repostCount: number
143
- replyCount: number
144
- }>
145
- }
146
- export declare interface SocialPublishingDriver {
147
- provider: SocialPublishingProvider
148
- characterLimit: number
149
- publish: (identity: SocialIdentityCredentials, post: PublishPostInput) => Promise<PublishedPost>
150
- timeline: (identity: SocialIdentityCredentials, query?: TimelineQuery) => Promise<TimelineResult>
151
- }
152
- export type SocialPublishingProvider = | 'bluesky'
153
- | 'twitter'
154
- | 'mastodon'
155
- | 'facebook'
156
- | 'instagram'
157
- | 'tiktok'
158
- | 'linkedin'
159
- | 'threads';