@shadowob/oauth
Advanced tools
+131
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| // src/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| ShadowOAuth: () => ShadowOAuth | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/client.ts | ||
| var DEFAULT_BASE_URL = "https://shadowob.com"; | ||
| var ShadowOAuth = class { | ||
| baseUrl; | ||
| clientId; | ||
| clientSecret; | ||
| redirectUri; | ||
| constructor(config) { | ||
| this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ""); | ||
| this.clientId = config.clientId; | ||
| this.clientSecret = config.clientSecret; | ||
| this.redirectUri = config.redirectUri; | ||
| } | ||
| /** | ||
| * Generate the authorization URL to redirect users to Shadow for login. | ||
| */ | ||
| getAuthorizeUrl(options) { | ||
| const scope = options?.scope?.join(" ") ?? "user:read"; | ||
| const params = new URLSearchParams({ | ||
| response_type: "code", | ||
| client_id: this.clientId, | ||
| redirect_uri: this.redirectUri, | ||
| scope | ||
| }); | ||
| if (options?.state) { | ||
| params.set("state", options.state); | ||
| } | ||
| return `${this.baseUrl}/oauth/authorize?${params.toString()}`; | ||
| } | ||
| /** | ||
| * Exchange an authorization code for access and refresh tokens. | ||
| */ | ||
| async getToken(code) { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/token`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| grant_type: "authorization_code", | ||
| code, | ||
| client_id: this.clientId, | ||
| client_secret: this.clientSecret, | ||
| redirect_uri: this.redirectUri | ||
| }) | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => ""); | ||
| throw new Error(`Shadow OAuth token exchange failed (${res.status}): ${body}`); | ||
| } | ||
| const data = await res.json(); | ||
| return { | ||
| accessToken: data.access_token, | ||
| refreshToken: data.refresh_token, | ||
| expiresIn: data.expires_in, | ||
| tokenType: data.token_type, | ||
| scope: data.scope | ||
| }; | ||
| } | ||
| /** | ||
| * Refresh an access token using a refresh token. | ||
| */ | ||
| async refreshToken(refreshToken) { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/token`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| grant_type: "refresh_token", | ||
| refresh_token: refreshToken, | ||
| client_id: this.clientId, | ||
| client_secret: this.clientSecret | ||
| }) | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => ""); | ||
| throw new Error(`Shadow OAuth token refresh failed (${res.status}): ${body}`); | ||
| } | ||
| const data = await res.json(); | ||
| return { | ||
| accessToken: data.access_token, | ||
| refreshToken: data.refresh_token, | ||
| expiresIn: data.expires_in, | ||
| tokenType: data.token_type, | ||
| scope: data.scope | ||
| }; | ||
| } | ||
| /** | ||
| * Get the authenticated user's information using an access token. | ||
| */ | ||
| async getUser(accessToken) { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/userinfo`, { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| Accept: "application/json" | ||
| } | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => ""); | ||
| throw new Error(`Shadow OAuth userinfo failed (${res.status}): ${body}`); | ||
| } | ||
| return res.json(); | ||
| } | ||
| }; | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| ShadowOAuth | ||
| }); |
| interface ShadowOAuthConfig { | ||
| /** Your app's client_id from Shadow Developer Portal */ | ||
| clientId: string; | ||
| /** Your app's client_secret (keep server-side only) */ | ||
| clientSecret: string; | ||
| /** The redirect URI registered with your app */ | ||
| redirectUri: string; | ||
| /** Shadow API base URL (default: https://shadowob.com) */ | ||
| baseUrl?: string; | ||
| } | ||
| interface ShadowOAuthTokens { | ||
| accessToken: string; | ||
| refreshToken: string; | ||
| expiresIn: number; | ||
| tokenType: string; | ||
| scope: string; | ||
| } | ||
| interface ShadowOAuthUser { | ||
| id: string; | ||
| username: string; | ||
| displayName: string | null; | ||
| avatarUrl: string | null; | ||
| email?: string; | ||
| } | ||
| type ShadowOAuthScope = 'user:read' | 'user:email'; | ||
| /** | ||
| * Shadow OAuth SDK client. | ||
| * | ||
| * Use this in your server-side application to implement | ||
| * "Login with Shadow" via the OAuth 2.0 Authorization Code flow. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const oauth = new ShadowOAuth({ | ||
| * clientId: 'shadow_xxx', | ||
| * clientSecret: 'shsec_xxx', | ||
| * redirectUri: 'https://myapp.com/callback', | ||
| * }) | ||
| * | ||
| * // Step 1: Generate the authorization URL and redirect the user | ||
| * const url = oauth.getAuthorizeUrl({ scope: ['user:read', 'user:email'] }) | ||
| * | ||
| * // Step 2: After callback, exchange the code for tokens | ||
| * const tokens = await oauth.getToken(code) | ||
| * | ||
| * // Step 3: Get user info | ||
| * const user = await oauth.getUser(tokens.accessToken) | ||
| * ``` | ||
| */ | ||
| declare class ShadowOAuth { | ||
| private baseUrl; | ||
| private clientId; | ||
| private clientSecret; | ||
| private redirectUri; | ||
| constructor(config: ShadowOAuthConfig); | ||
| /** | ||
| * Generate the authorization URL to redirect users to Shadow for login. | ||
| */ | ||
| getAuthorizeUrl(options?: { | ||
| scope?: ShadowOAuthScope[]; | ||
| state?: string; | ||
| }): string; | ||
| /** | ||
| * Exchange an authorization code for access and refresh tokens. | ||
| */ | ||
| getToken(code: string): Promise<ShadowOAuthTokens>; | ||
| /** | ||
| * Refresh an access token using a refresh token. | ||
| */ | ||
| refreshToken(refreshToken: string): Promise<ShadowOAuthTokens>; | ||
| /** | ||
| * Get the authenticated user's information using an access token. | ||
| */ | ||
| getUser(accessToken: string): Promise<ShadowOAuthUser>; | ||
| } | ||
| export { ShadowOAuth, type ShadowOAuthConfig, type ShadowOAuthScope, type ShadowOAuthTokens, type ShadowOAuthUser }; |
| interface ShadowOAuthConfig { | ||
| /** Your app's client_id from Shadow Developer Portal */ | ||
| clientId: string; | ||
| /** Your app's client_secret (keep server-side only) */ | ||
| clientSecret: string; | ||
| /** The redirect URI registered with your app */ | ||
| redirectUri: string; | ||
| /** Shadow API base URL (default: https://shadowob.com) */ | ||
| baseUrl?: string; | ||
| } | ||
| interface ShadowOAuthTokens { | ||
| accessToken: string; | ||
| refreshToken: string; | ||
| expiresIn: number; | ||
| tokenType: string; | ||
| scope: string; | ||
| } | ||
| interface ShadowOAuthUser { | ||
| id: string; | ||
| username: string; | ||
| displayName: string | null; | ||
| avatarUrl: string | null; | ||
| email?: string; | ||
| } | ||
| type ShadowOAuthScope = 'user:read' | 'user:email'; | ||
| /** | ||
| * Shadow OAuth SDK client. | ||
| * | ||
| * Use this in your server-side application to implement | ||
| * "Login with Shadow" via the OAuth 2.0 Authorization Code flow. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const oauth = new ShadowOAuth({ | ||
| * clientId: 'shadow_xxx', | ||
| * clientSecret: 'shsec_xxx', | ||
| * redirectUri: 'https://myapp.com/callback', | ||
| * }) | ||
| * | ||
| * // Step 1: Generate the authorization URL and redirect the user | ||
| * const url = oauth.getAuthorizeUrl({ scope: ['user:read', 'user:email'] }) | ||
| * | ||
| * // Step 2: After callback, exchange the code for tokens | ||
| * const tokens = await oauth.getToken(code) | ||
| * | ||
| * // Step 3: Get user info | ||
| * const user = await oauth.getUser(tokens.accessToken) | ||
| * ``` | ||
| */ | ||
| declare class ShadowOAuth { | ||
| private baseUrl; | ||
| private clientId; | ||
| private clientSecret; | ||
| private redirectUri; | ||
| constructor(config: ShadowOAuthConfig); | ||
| /** | ||
| * Generate the authorization URL to redirect users to Shadow for login. | ||
| */ | ||
| getAuthorizeUrl(options?: { | ||
| scope?: ShadowOAuthScope[]; | ||
| state?: string; | ||
| }): string; | ||
| /** | ||
| * Exchange an authorization code for access and refresh tokens. | ||
| */ | ||
| getToken(code: string): Promise<ShadowOAuthTokens>; | ||
| /** | ||
| * Refresh an access token using a refresh token. | ||
| */ | ||
| refreshToken(refreshToken: string): Promise<ShadowOAuthTokens>; | ||
| /** | ||
| * Get the authenticated user's information using an access token. | ||
| */ | ||
| getUser(accessToken: string): Promise<ShadowOAuthUser>; | ||
| } | ||
| export { ShadowOAuth, type ShadowOAuthConfig, type ShadowOAuthScope, type ShadowOAuthTokens, type ShadowOAuthUser }; |
+104
| // src/client.ts | ||
| var DEFAULT_BASE_URL = "https://shadowob.com"; | ||
| var ShadowOAuth = class { | ||
| baseUrl; | ||
| clientId; | ||
| clientSecret; | ||
| redirectUri; | ||
| constructor(config) { | ||
| this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, ""); | ||
| this.clientId = config.clientId; | ||
| this.clientSecret = config.clientSecret; | ||
| this.redirectUri = config.redirectUri; | ||
| } | ||
| /** | ||
| * Generate the authorization URL to redirect users to Shadow for login. | ||
| */ | ||
| getAuthorizeUrl(options) { | ||
| const scope = options?.scope?.join(" ") ?? "user:read"; | ||
| const params = new URLSearchParams({ | ||
| response_type: "code", | ||
| client_id: this.clientId, | ||
| redirect_uri: this.redirectUri, | ||
| scope | ||
| }); | ||
| if (options?.state) { | ||
| params.set("state", options.state); | ||
| } | ||
| return `${this.baseUrl}/oauth/authorize?${params.toString()}`; | ||
| } | ||
| /** | ||
| * Exchange an authorization code for access and refresh tokens. | ||
| */ | ||
| async getToken(code) { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/token`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| grant_type: "authorization_code", | ||
| code, | ||
| client_id: this.clientId, | ||
| client_secret: this.clientSecret, | ||
| redirect_uri: this.redirectUri | ||
| }) | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => ""); | ||
| throw new Error(`Shadow OAuth token exchange failed (${res.status}): ${body}`); | ||
| } | ||
| const data = await res.json(); | ||
| return { | ||
| accessToken: data.access_token, | ||
| refreshToken: data.refresh_token, | ||
| expiresIn: data.expires_in, | ||
| tokenType: data.token_type, | ||
| scope: data.scope | ||
| }; | ||
| } | ||
| /** | ||
| * Refresh an access token using a refresh token. | ||
| */ | ||
| async refreshToken(refreshToken) { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/token`, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| grant_type: "refresh_token", | ||
| refresh_token: refreshToken, | ||
| client_id: this.clientId, | ||
| client_secret: this.clientSecret | ||
| }) | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => ""); | ||
| throw new Error(`Shadow OAuth token refresh failed (${res.status}): ${body}`); | ||
| } | ||
| const data = await res.json(); | ||
| return { | ||
| accessToken: data.access_token, | ||
| refreshToken: data.refresh_token, | ||
| expiresIn: data.expires_in, | ||
| tokenType: data.token_type, | ||
| scope: data.scope | ||
| }; | ||
| } | ||
| /** | ||
| * Get the authenticated user's information using an access token. | ||
| */ | ||
| async getUser(accessToken) { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/userinfo`, { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| Accept: "application/json" | ||
| } | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => ""); | ||
| throw new Error(`Shadow OAuth userinfo failed (${res.status}): ${body}`); | ||
| } | ||
| return res.json(); | ||
| } | ||
| }; | ||
| export { | ||
| ShadowOAuth | ||
| }; |
+24
-4
| { | ||
| "name": "@shadowob/oauth", | ||
| "version": "0.4.0", | ||
| "version": "0.4.1", | ||
| "description": "Shadow OAuth SDK — typed client for integrating Shadow OAuth login into third-party applications", | ||
| "type": "module", | ||
| "main": "./src/index.ts", | ||
| "types": "./src/index.ts", | ||
| "main": "./dist/index.js", | ||
| "module": "./dist/index.js", | ||
| "require": "./dist/index.cjs", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| ".": "./src/index.ts" | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "development": "./src/index.ts", | ||
| "import": "./dist/index.js", | ||
| "require": "./dist/index.cjs", | ||
| "default": "./dist/index.js" | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "dependencies": {}, | ||
| "devDependencies": { | ||
| "tsup": "^8.5.0", | ||
| "typescript": "^5.9.3" | ||
| }, | ||
| "publishConfig": { | ||
| "access": "public" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "test": "vitest run", | ||
@@ -14,0 +34,0 @@ "test:watch": "vitest" |
-157
| import type { | ||
| ShadowOAuthConfig, | ||
| ShadowOAuthScope, | ||
| ShadowOAuthTokens, | ||
| ShadowOAuthUser, | ||
| } from './types' | ||
| const DEFAULT_BASE_URL = 'https://shadowob.com' | ||
| /** | ||
| * Shadow OAuth SDK client. | ||
| * | ||
| * Use this in your server-side application to implement | ||
| * "Login with Shadow" via the OAuth 2.0 Authorization Code flow. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const oauth = new ShadowOAuth({ | ||
| * clientId: 'shadow_xxx', | ||
| * clientSecret: 'shsec_xxx', | ||
| * redirectUri: 'https://myapp.com/callback', | ||
| * }) | ||
| * | ||
| * // Step 1: Generate the authorization URL and redirect the user | ||
| * const url = oauth.getAuthorizeUrl({ scope: ['user:read', 'user:email'] }) | ||
| * | ||
| * // Step 2: After callback, exchange the code for tokens | ||
| * const tokens = await oauth.getToken(code) | ||
| * | ||
| * // Step 3: Get user info | ||
| * const user = await oauth.getUser(tokens.accessToken) | ||
| * ``` | ||
| */ | ||
| export class ShadowOAuth { | ||
| private baseUrl: string | ||
| private clientId: string | ||
| private clientSecret: string | ||
| private redirectUri: string | ||
| constructor(config: ShadowOAuthConfig) { | ||
| this.baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '') | ||
| this.clientId = config.clientId | ||
| this.clientSecret = config.clientSecret | ||
| this.redirectUri = config.redirectUri | ||
| } | ||
| /** | ||
| * Generate the authorization URL to redirect users to Shadow for login. | ||
| */ | ||
| getAuthorizeUrl(options?: { scope?: ShadowOAuthScope[]; state?: string }): string { | ||
| const scope = options?.scope?.join(' ') ?? 'user:read' | ||
| const params = new URLSearchParams({ | ||
| response_type: 'code', | ||
| client_id: this.clientId, | ||
| redirect_uri: this.redirectUri, | ||
| scope, | ||
| }) | ||
| if (options?.state) { | ||
| params.set('state', options.state) | ||
| } | ||
| return `${this.baseUrl}/oauth/authorize?${params.toString()}` | ||
| } | ||
| /** | ||
| * Exchange an authorization code for access and refresh tokens. | ||
| */ | ||
| async getToken(code: string): Promise<ShadowOAuthTokens> { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/token`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ | ||
| grant_type: 'authorization_code', | ||
| code, | ||
| client_id: this.clientId, | ||
| client_secret: this.clientSecret, | ||
| redirect_uri: this.redirectUri, | ||
| }), | ||
| }) | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => '') | ||
| throw new Error(`Shadow OAuth token exchange failed (${res.status}): ${body}`) | ||
| } | ||
| const data = (await res.json()) as { | ||
| access_token: string | ||
| refresh_token: string | ||
| expires_in: number | ||
| token_type: string | ||
| scope: string | ||
| } | ||
| return { | ||
| accessToken: data.access_token, | ||
| refreshToken: data.refresh_token, | ||
| expiresIn: data.expires_in, | ||
| tokenType: data.token_type, | ||
| scope: data.scope, | ||
| } | ||
| } | ||
| /** | ||
| * Refresh an access token using a refresh token. | ||
| */ | ||
| async refreshToken(refreshToken: string): Promise<ShadowOAuthTokens> { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/token`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ | ||
| grant_type: 'refresh_token', | ||
| refresh_token: refreshToken, | ||
| client_id: this.clientId, | ||
| client_secret: this.clientSecret, | ||
| }), | ||
| }) | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => '') | ||
| throw new Error(`Shadow OAuth token refresh failed (${res.status}): ${body}`) | ||
| } | ||
| const data = (await res.json()) as { | ||
| access_token: string | ||
| refresh_token: string | ||
| expires_in: number | ||
| token_type: string | ||
| scope: string | ||
| } | ||
| return { | ||
| accessToken: data.access_token, | ||
| refreshToken: data.refresh_token, | ||
| expiresIn: data.expires_in, | ||
| tokenType: data.token_type, | ||
| scope: data.scope, | ||
| } | ||
| } | ||
| /** | ||
| * Get the authenticated user's information using an access token. | ||
| */ | ||
| async getUser(accessToken: string): Promise<ShadowOAuthUser> { | ||
| const res = await fetch(`${this.baseUrl}/api/oauth/userinfo`, { | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| Accept: 'application/json', | ||
| }, | ||
| }) | ||
| if (!res.ok) { | ||
| const body = await res.text().catch(() => '') | ||
| throw new Error(`Shadow OAuth userinfo failed (${res.status}): ${body}`) | ||
| } | ||
| return res.json() as Promise<ShadowOAuthUser> | ||
| } | ||
| } |
| export { ShadowOAuth } from './client' | ||
| export type { | ||
| ShadowOAuthConfig, | ||
| ShadowOAuthScope, | ||
| ShadowOAuthTokens, | ||
| ShadowOAuthUser, | ||
| } from './types' |
-28
| export interface ShadowOAuthConfig { | ||
| /** Your app's client_id from Shadow Developer Portal */ | ||
| clientId: string | ||
| /** Your app's client_secret (keep server-side only) */ | ||
| clientSecret: string | ||
| /** The redirect URI registered with your app */ | ||
| redirectUri: string | ||
| /** Shadow API base URL (default: https://shadowob.com) */ | ||
| baseUrl?: string | ||
| } | ||
| export interface ShadowOAuthTokens { | ||
| accessToken: string | ||
| refreshToken: string | ||
| expiresIn: number | ||
| tokenType: string | ||
| scope: string | ||
| } | ||
| export interface ShadowOAuthUser { | ||
| id: string | ||
| username: string | ||
| displayName: string | null | ||
| avatarUrl: string | null | ||
| email?: string | ||
| } | ||
| export type ShadowOAuthScope = 'user:read' | 'user:email' |
| { | ||
| "extends": "../../tsconfig.json", | ||
| "compilerOptions": { | ||
| "rootDir": "./src", | ||
| "outDir": "./dist" | ||
| }, | ||
| "include": ["src"] | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
46600
17.27%309
69.78%2
Infinity%7
133.33%