@socialproof/mysocial-auth 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # @socialproof/mysocial-auth
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial release: MySocial Auth SDK for "Login with MySocial" via popup or redirect. Supports PKCE,
8
+ request_id flow, session storage (memory/session/custom), signIn, signOut, getSession, refresh,
9
+ handleRedirectCallback, onAuthStateChange.
10
+
11
+ ## 0.0.1
12
+
13
+ ### Minor Changes
14
+
15
+ - Initial release: MySocial Auth SDK for "Login with MySocial" via popup or redirect
package/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # @socialproof/mysocial-auth
2
+
3
+ MySocial Auth SDK for "Login with MySocial" via popup or redirect. Enables third-party platforms
4
+ (e.g. dripdrop.social) to authenticate users against auth.mysocial.network.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pnpm add @socialproof/mysocial-auth
10
+ ```
11
+
12
+ ## Quick Start
13
+
14
+ ```typescript
15
+ import { createMySocialAuth } from '@socialproof/mysocial-auth';
16
+
17
+ const auth = createMySocialAuth({
18
+ apiBaseUrl: 'https://api.mysocial.network',
19
+ authOrigin: 'https://auth.mysocial.network',
20
+ clientId: 'dripdrop-platform-id',
21
+ redirectUri: 'https://dripdrop.social/auth/callback', // must be pre-registered/allowlisted per clientId
22
+ storage: 'memory', // default; use 'session' for persistence across reloads
23
+ });
24
+
25
+ // Popup login
26
+ // IMPORTANT: Call signIn() directly inside the click handler (no await before window.open).
27
+ // Safari blocks popups unless triggered by a direct user gesture.
28
+ document
29
+ .getElementById('login-btn')
30
+ .addEventListener('click', () => auth.signIn({ mode: 'popup', provider: 'google' }));
31
+
32
+ // Redirect login (platform hosts callback at redirectUri)
33
+ // 1. auth.signIn({ mode: 'redirect', provider: 'google' })
34
+ // 2. User redirected to auth.mysocial.network, then back to redirectUri with ?code=...&state=...&nonce=...
35
+ // 3. On callback page: await auth.handleRedirectCallback()
36
+
37
+ // Check session
38
+ const session = await auth.getSession();
39
+ if (session) console.log(session.user);
40
+
41
+ // Listen for changes
42
+ auth.onAuthStateChange((session) => {
43
+ /* ... */
44
+ });
45
+
46
+ // Logout
47
+ await auth.signOut();
48
+ ```
49
+
50
+ ## redirectUri Requirement
51
+
52
+ - Must be a **specific callback path** (e.g. `/auth/callback`), not the root domain.
53
+ - Must be **pre-registered/allowlisted** server-side per `clientId`.
54
+
55
+ ## API
56
+
57
+ ### createMySocialAuth(config)
58
+
59
+ | Option | Type | Default | Description |
60
+ | ------------ | --------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------- |
61
+ | apiBaseUrl | string | - | Backend API base URL (e.g. https://api.mysocial.network) |
62
+ | authOrigin | string | - | Auth popup host (e.g. https://auth.mysocial.network) |
63
+ | clientId | string | - | Platform ID (for backend rate limiting/whitelist) |
64
+ | redirectUri | string | - | Callback URL; must be allowlisted per clientId |
65
+ | storage | 'memory' \| 'session' \| StorageAdapter | 'memory' | Session storage. `memory` is most secure (no XSS exposure). `session` persists across reloads in the same tab. |
66
+ | popupTimeout | number | 120000 | Popup timeout in ms |
67
+ | useRequestId | boolean | false | Use request_id flow (requires backend POST /auth/request) |
68
+
69
+ ### auth.signIn(options?)
70
+
71
+ - `provider?`: 'google' | 'apple' | 'facebook' | 'twitch'
72
+ - `mode?`: 'popup' | 'redirect' (default: 'popup')
73
+ - Returns: `Promise<Session>` (popup) or never (redirect; page navigates)
74
+
75
+ ### auth.signOut()
76
+
77
+ - Clears session and calls backend logout.
78
+
79
+ ### auth.getSession()
80
+
81
+ - Returns current session, or null. Auto-refreshes if expired and refresh_token exists.
82
+
83
+ ### auth.refresh()
84
+
85
+ - Refreshes access token. Returns new session or null.
86
+
87
+ ### auth.handleRedirectCallback(url?)
88
+
89
+ - For redirect mode. Parses URL for code/state/nonce, exchanges for tokens. Omit `url` to use
90
+ `window.location.href`.
91
+
92
+ ### auth.onAuthStateChange(callback)
93
+
94
+ - Subscribe to session changes. Returns unsubscribe function.
95
+
96
+ ## Hosted UI Contract (auth.mysocial.network)
97
+
98
+ The popup page must call:
99
+
100
+ ```javascript
101
+ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT "*"
102
+ ```
103
+
104
+ **Success payload:**
105
+
106
+ ```json
107
+ {
108
+ "type": "MYSOCIAL_AUTH_RESULT",
109
+ "code": "...",
110
+ "state": "...",
111
+ "nonce": "...",
112
+ "clientId": "...",
113
+ "requestId": "..."
114
+ }
115
+ ```
116
+
117
+ **Error payload:**
118
+
119
+ ```json
120
+ {
121
+ "type": "MYSOCIAL_AUTH_ERROR",
122
+ "error": "error_code_or_message",
123
+ "state": "...",
124
+ "nonce": "...",
125
+ "clientId": "...",
126
+ "requestId": "..."
127
+ }
128
+ ```
129
+
130
+ `return_origin` must never be trusted from the query param. The backend must validate it against the
131
+ allowlist for `client_id`.
132
+
133
+ ## Backend Contract
134
+
135
+ - `POST ${apiBaseUrl}/auth/request` (optional): `{ client_id, redirect_uri, return_origin }` →
136
+ `{ request_id }`
137
+ - `POST ${apiBaseUrl}/auth/exchange`:
138
+ `{ code, code_verifier?, redirect_uri, state?, nonce?, request_id? }` →
139
+ `{ access_token, refresh_token?, expires_in, user }`
140
+ - `POST ${apiBaseUrl}/auth/refresh`: `{ refresh_token }` →
141
+ `{ access_token, refresh_token?, expires_in, user }`
142
+ - `POST ${apiBaseUrl}/auth/logout`
143
+
144
+ ## Security Notes
145
+
146
+ - **Tokens in browser storage** (session/localStorage) are XSS-risky. Prefer `storage: 'memory'`
147
+ when possible. If using session storage, recommend short access tokens and refresh rotation.
148
+ - **Safari popup**: Call `signIn()` directly inside the click handler. Open popup immediately with
149
+ `about:blank`, then set location once URL is ready.
@@ -0,0 +1,14 @@
1
+ import { AuthStateChangeCallback, Session, SignInOptions } from "./types.mjs";
2
+
3
+ //#region src/auth.d.ts
4
+ interface MySocialAuth {
5
+ signIn(options?: SignInOptions): Promise<Session>;
6
+ signOut(): Promise<void>;
7
+ getSession(): Promise<Session | null>;
8
+ refresh(): Promise<Session | null>;
9
+ handleRedirectCallback(url?: string): Promise<Session>;
10
+ onAuthStateChange(callback: AuthStateChangeCallback): () => void;
11
+ }
12
+ //#endregion
13
+ export { MySocialAuth };
14
+ //# sourceMappingURL=auth.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.mts","names":[],"sources":["../src/auth.ts"],"mappings":";;;UAsBiB,YAAA;EAChB,MAAA,CAAO,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,OAAA;EACzC,OAAA,IAAW,OAAA;EACX,UAAA,IAAc,OAAA,CAAQ,OAAA;EACtB,OAAA,IAAW,OAAA,CAAQ,OAAA;EACnB,sBAAA,CAAuB,GAAA,YAAe,OAAA,CAAQ,OAAA;EAC9C,iBAAA,CAAkB,QAAA,EAAU,uBAAA;AAAA"}
package/dist/auth.mjs ADDED
@@ -0,0 +1,164 @@
1
+ import { REDIRECT_STATE_PREFIX, SESSION_KEY, createStorage, redirectStorage } from "./storage.mjs";
2
+ import { generateCodeChallenge, generateCodeVerifier, generateNonce, generateState } from "./pkce.mjs";
3
+ import { exchangeCode, fetchRequestId, logout, refreshTokens } from "./exchange.mjs";
4
+ import { openAuthPopup } from "./popup.mjs";
5
+ import { mitt } from "@socialproof/utils";
6
+
7
+ //#region src/auth.ts
8
+ function getReturnOrigin(redirectUri) {
9
+ try {
10
+ const u = new URL(redirectUri);
11
+ return `${u.protocol}//${u.host}`;
12
+ } catch {
13
+ return "";
14
+ }
15
+ }
16
+ function createAuth(config) {
17
+ const storage = createStorage(config.storage);
18
+ const emitter = mitt();
19
+ let cachedSession = null;
20
+ async function loadSession() {
21
+ const raw = storage.get(SESSION_KEY);
22
+ if (!raw) return null;
23
+ try {
24
+ const session = JSON.parse(raw);
25
+ if (session.expires_at && session.expires_at > Date.now()) return session;
26
+ if (session.refresh_token) {
27
+ const refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);
28
+ await saveSession(refreshed);
29
+ emitter.emit("change", refreshed);
30
+ return refreshed;
31
+ }
32
+ storage.remove(SESSION_KEY);
33
+ cachedSession = null;
34
+ emitter.emit("change", null);
35
+ return null;
36
+ } catch {
37
+ storage.remove(SESSION_KEY);
38
+ cachedSession = null;
39
+ return null;
40
+ }
41
+ }
42
+ async function saveSession(session) {
43
+ cachedSession = session;
44
+ storage.set(SESSION_KEY, JSON.stringify(session));
45
+ }
46
+ async function clearSession() {
47
+ cachedSession = null;
48
+ storage.remove(SESSION_KEY);
49
+ }
50
+ return {
51
+ async signIn(options = {}) {
52
+ const mode = options.mode ?? "popup";
53
+ const provider = options.provider;
54
+ if (mode === "popup") {
55
+ const session = await openAuthPopup({
56
+ apiBaseUrl: config.apiBaseUrl,
57
+ authOrigin: config.authOrigin,
58
+ clientId: config.clientId,
59
+ redirectUri: config.redirectUri,
60
+ provider,
61
+ timeout: config.popupTimeout ?? 12e4,
62
+ useRequestId: config.useRequestId ?? false
63
+ });
64
+ await saveSession(session);
65
+ emitter.emit("change", session);
66
+ return session;
67
+ }
68
+ const state = generateState();
69
+ const nonce = generateNonce();
70
+ const codeVerifier = await generateCodeVerifier();
71
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
72
+ const returnOrigin = getReturnOrigin(config.redirectUri);
73
+ let requestId;
74
+ if (config.useRequestId) requestId = await fetchRequestId(config.apiBaseUrl, {
75
+ client_id: config.clientId,
76
+ redirect_uri: config.redirectUri,
77
+ return_origin: returnOrigin
78
+ });
79
+ const redirectState = {
80
+ state,
81
+ nonce,
82
+ codeVerifier,
83
+ requestId
84
+ };
85
+ const key = `${REDIRECT_STATE_PREFIX}${state}`;
86
+ redirectStorage.set(key, JSON.stringify(redirectState));
87
+ const params = new URLSearchParams({
88
+ client_id: config.clientId,
89
+ redirect_uri: config.redirectUri,
90
+ state,
91
+ nonce,
92
+ return_origin: returnOrigin,
93
+ mode: "redirect",
94
+ provider: provider ?? "",
95
+ code_challenge: codeChallenge,
96
+ code_challenge_method: "S256"
97
+ });
98
+ if (requestId) params.set("request_id", requestId);
99
+ const loginUrl = `${config.authOrigin.replace(/\/$/, "")}/login?${params.toString()}`;
100
+ window.location.href = loginUrl;
101
+ return new Promise(() => {});
102
+ },
103
+ async signOut() {
104
+ try {
105
+ await logout(config.apiBaseUrl);
106
+ } catch {}
107
+ await clearSession();
108
+ emitter.emit("change", null);
109
+ },
110
+ async getSession() {
111
+ if (cachedSession && cachedSession.expires_at > Date.now()) return cachedSession;
112
+ return loadSession();
113
+ },
114
+ async refresh() {
115
+ const session = await loadSession();
116
+ if (!session?.refresh_token) return null;
117
+ const refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);
118
+ await saveSession(refreshed);
119
+ emitter.emit("change", refreshed);
120
+ return refreshed;
121
+ },
122
+ async handleRedirectCallback(url) {
123
+ const targetUrl = url ?? (typeof window !== "undefined" ? window.location.href : "");
124
+ const parsed = new URL(targetUrl);
125
+ const code = parsed.searchParams.get("code");
126
+ const state = parsed.searchParams.get("state");
127
+ const nonce = parsed.searchParams.get("nonce");
128
+ const requestId = parsed.searchParams.get("request_id") ?? void 0;
129
+ if (!code || !state || !nonce) throw new Error("Missing code, state, or nonce in callback URL");
130
+ const key = `${REDIRECT_STATE_PREFIX}${state}`;
131
+ const raw = redirectStorage.get(key);
132
+ if (!raw) throw new Error("No matching redirect state found. Session may have expired.");
133
+ const redirectState = JSON.parse(raw);
134
+ if (redirectState.state !== state || redirectState.nonce !== nonce) {
135
+ redirectStorage.remove(key);
136
+ throw new Error("State or nonce mismatch");
137
+ }
138
+ if (requestId && redirectState.requestId !== requestId) {
139
+ redirectStorage.remove(key);
140
+ throw new Error("Request ID mismatch");
141
+ }
142
+ redirectStorage.remove(key);
143
+ const session = await exchangeCode(config.apiBaseUrl, {
144
+ code,
145
+ code_verifier: redirectState.codeVerifier,
146
+ redirect_uri: config.redirectUri,
147
+ state,
148
+ nonce,
149
+ request_id: requestId ?? redirectState.requestId
150
+ });
151
+ await saveSession(session);
152
+ emitter.emit("change", session);
153
+ return session;
154
+ },
155
+ onAuthStateChange(callback) {
156
+ emitter.on("change", callback);
157
+ return () => emitter.off("change", callback);
158
+ }
159
+ };
160
+ }
161
+
162
+ //#endregion
163
+ export { createAuth };
164
+ //# sourceMappingURL=auth.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { mitt, type Emitter } from '@socialproof/utils';\nimport type {\n\tMySocialAuthConfig,\n\tSignInOptions,\n\tSession,\n\tAuthStateChangeCallback,\n} from './types.js';\nimport { createStorage, redirectStorage, SESSION_KEY, REDIRECT_STATE_PREFIX } from './storage.js';\nimport { openAuthPopup } from './popup.js';\nimport { exchangeCode, refreshTokens, logout, fetchRequestId } from './exchange.js';\nimport {\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tgenerateState,\n\tgenerateNonce,\n} from './pkce.js';\n\ntype AuthEvents = { change: Session | null };\n\nexport interface MySocialAuth {\n\tsignIn(options?: SignInOptions): Promise<Session>;\n\tsignOut(): Promise<void>;\n\tgetSession(): Promise<Session | null>;\n\trefresh(): Promise<Session | null>;\n\thandleRedirectCallback(url?: string): Promise<Session>;\n\tonAuthStateChange(callback: AuthStateChangeCallback): () => void;\n}\n\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport function createAuth(config: MySocialAuthConfig): MySocialAuth {\n\tconst storage = createStorage(config.storage);\n\tconst emitter: Emitter<AuthEvents> = mitt<AuthEvents>();\n\n\tlet cachedSession: Session | null = null;\n\n\tasync function loadSession(): Promise<Session | null> {\n\t\tconst raw = storage.get(SESSION_KEY);\n\t\tif (!raw) return null;\n\t\ttry {\n\t\t\tconst session = JSON.parse(raw) as Session;\n\t\t\tif (session.expires_at && session.expires_at > Date.now()) {\n\t\t\t\treturn session;\n\t\t\t}\n\t\t\tif (session.refresh_token) {\n\t\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\t\tawait saveSession(refreshed);\n\t\t\t\temitter.emit('change', refreshed);\n\t\t\t\treturn refreshed;\n\t\t\t}\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\temitter.emit('change', null);\n\t\t\treturn null;\n\t\t} catch {\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync function saveSession(session: Session): Promise<void> {\n\t\tcachedSession = session;\n\t\tstorage.set(SESSION_KEY, JSON.stringify(session));\n\t}\n\n\tasync function clearSession(): Promise<void> {\n\t\tcachedSession = null;\n\t\tstorage.remove(SESSION_KEY);\n\t}\n\n\treturn {\n\t\tasync signIn(options: SignInOptions = {}) {\n\t\t\tconst mode = options.mode ?? 'popup';\n\t\t\tconst provider = options.provider;\n\n\t\t\tif (mode === 'popup') {\n\t\t\t\tconst session = await openAuthPopup({\n\t\t\t\t\tapiBaseUrl: config.apiBaseUrl,\n\t\t\t\t\tauthOrigin: config.authOrigin,\n\t\t\t\t\tclientId: config.clientId,\n\t\t\t\t\tredirectUri: config.redirectUri,\n\t\t\t\t\tprovider,\n\t\t\t\t\ttimeout: config.popupTimeout ?? 120_000,\n\t\t\t\t\tuseRequestId: config.useRequestId ?? false,\n\t\t\t\t});\n\t\t\t\tawait saveSession(session);\n\t\t\t\temitter.emit('change', session);\n\t\t\t\treturn session;\n\t\t\t}\n\n\t\t\t// Redirect mode\n\t\t\tconst state = generateState();\n\t\t\tconst nonce = generateNonce();\n\t\t\tconst codeVerifier = await generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\t\t\tconst returnOrigin = getReturnOrigin(config.redirectUri);\n\n\t\t\tlet requestId: string | undefined;\n\t\t\tif (config.useRequestId) {\n\t\t\t\trequestId = await fetchRequestId(config.apiBaseUrl, {\n\t\t\t\t\tclient_id: config.clientId,\n\t\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst redirectState = {\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\tcodeVerifier,\n\t\t\t\trequestId,\n\t\t\t};\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tredirectStorage.set(key, JSON.stringify(redirectState));\n\n\t\t\tconst params = new URLSearchParams({\n\t\t\t\tclient_id: config.clientId,\n\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\tmode: 'redirect',\n\t\t\t\tprovider: provider ?? '',\n\t\t\t\tcode_challenge: codeChallenge,\n\t\t\t\tcode_challenge_method: 'S256',\n\t\t\t});\n\t\t\tif (requestId) params.set('request_id', requestId);\n\n\t\t\tconst loginUrl = `${config.authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\t\t\twindow.location.href = loginUrl;\n\n\t\t\treturn new Promise<Session>(() => {}); // Never resolves; page navigates away\n\t\t},\n\n\t\tasync signOut() {\n\t\t\ttry {\n\t\t\t\tawait logout(config.apiBaseUrl);\n\t\t\t} catch {\n\t\t\t\t// Best-effort; clear local state regardless\n\t\t\t}\n\t\t\tawait clearSession();\n\t\t\temitter.emit('change', null);\n\t\t},\n\n\t\tasync getSession() {\n\t\t\tif (cachedSession && cachedSession.expires_at > Date.now()) {\n\t\t\t\treturn cachedSession;\n\t\t\t}\n\t\t\treturn loadSession();\n\t\t},\n\n\t\tasync refresh() {\n\t\t\tconst session = await loadSession();\n\t\t\tif (!session?.refresh_token) return null;\n\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\tawait saveSession(refreshed);\n\t\t\temitter.emit('change', refreshed);\n\t\t\treturn refreshed;\n\t\t},\n\n\t\tasync handleRedirectCallback(url?: string) {\n\t\t\tconst targetUrl = url ?? (typeof window !== 'undefined' ? window.location.href : '');\n\t\t\tconst parsed = new URL(targetUrl);\n\t\t\tconst code = parsed.searchParams.get('code');\n\t\t\tconst state = parsed.searchParams.get('state');\n\t\t\tconst nonce = parsed.searchParams.get('nonce');\n\t\t\tconst requestId = parsed.searchParams.get('request_id') ?? undefined;\n\n\t\t\tif (!code || !state || !nonce) {\n\t\t\t\tthrow new Error('Missing code, state, or nonce in callback URL');\n\t\t\t}\n\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tconst raw = redirectStorage.get(key);\n\t\t\tif (!raw) {\n\t\t\t\tthrow new Error('No matching redirect state found. Session may have expired.');\n\t\t\t}\n\n\t\t\tconst redirectState = JSON.parse(raw) as {\n\t\t\t\tstate: string;\n\t\t\t\tnonce: string;\n\t\t\t\tcodeVerifier: string;\n\t\t\t\trequestId?: string;\n\t\t\t};\n\n\t\t\tif (redirectState.state !== state || redirectState.nonce !== nonce) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('State or nonce mismatch');\n\t\t\t}\n\t\t\tif (requestId && redirectState.requestId !== requestId) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('Request ID mismatch');\n\t\t\t}\n\n\t\t\tredirectStorage.remove(key);\n\n\t\t\tconst session = await exchangeCode(config.apiBaseUrl, {\n\t\t\t\tcode,\n\t\t\t\tcode_verifier: redirectState.codeVerifier,\n\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\trequest_id: requestId ?? redirectState.requestId,\n\t\t\t});\n\n\t\t\tawait saveSession(session);\n\t\t\temitter.emit('change', session);\n\t\t\treturn session;\n\t\t},\n\n\t\tonAuthStateChange(callback: AuthStateChangeCallback) {\n\t\t\temitter.on('change', callback);\n\t\t\treturn () => emitter.off('change', callback);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { MySocialAuthConfig } from "./types.mjs";
2
+ import { MySocialAuth } from "./auth.mjs";
3
+
4
+ //#region src/createMySocialAuth.d.ts
5
+ /**
6
+ * Create a MySocial Auth instance for "Login with MySocial".
7
+ *
8
+ * @param config - SDK configuration (apiBaseUrl, authOrigin, clientId, redirectUri, etc.)
9
+ * @returns MySocialAuth instance with signIn, signOut, getSession, refresh, handleRedirectCallback, onAuthStateChange
10
+ */
11
+ declare function createMySocialAuth(config: MySocialAuthConfig): MySocialAuth;
12
+ //#endregion
13
+ export { createMySocialAuth };
14
+ //# sourceMappingURL=createMySocialAuth.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createMySocialAuth.d.mts","names":[],"sources":["../src/createMySocialAuth.ts"],"mappings":";;;;;;AAaA;;;;iBAAgB,kBAAA,CAAmB,MAAA,EAAQ,kBAAA,GAAqB,YAAA"}
@@ -0,0 +1,16 @@
1
+ import { createAuth } from "./auth.mjs";
2
+
3
+ //#region src/createMySocialAuth.ts
4
+ /**
5
+ * Create a MySocial Auth instance for "Login with MySocial".
6
+ *
7
+ * @param config - SDK configuration (apiBaseUrl, authOrigin, clientId, redirectUri, etc.)
8
+ * @returns MySocialAuth instance with signIn, signOut, getSession, refresh, handleRedirectCallback, onAuthStateChange
9
+ */
10
+ function createMySocialAuth(config) {
11
+ return createAuth(config);
12
+ }
13
+
14
+ //#endregion
15
+ export { createMySocialAuth };
16
+ //# sourceMappingURL=createMySocialAuth.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createMySocialAuth.mjs","names":[],"sources":["../src/createMySocialAuth.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { MySocialAuthConfig } from './types.js';\nimport type { MySocialAuth } from './auth.js';\nimport { createAuth } from './auth.js';\n\n/**\n * Create a MySocial Auth instance for \"Login with MySocial\".\n *\n * @param config - SDK configuration (apiBaseUrl, authOrigin, clientId, redirectUri, etc.)\n * @returns MySocialAuth instance with signIn, signOut, getSession, refresh, handleRedirectCallback, onAuthStateChange\n */\nexport function createMySocialAuth(config: MySocialAuthConfig): MySocialAuth {\n\treturn createAuth(config);\n}\n"],"mappings":";;;;;;;;;AAaA,SAAgB,mBAAmB,QAA0C;AAC5E,QAAO,WAAW,OAAO"}
@@ -0,0 +1,32 @@
1
+ //#region src/errors.d.ts
2
+ /** Base error for MySocial Auth SDK */
3
+ declare class MySocialAuthError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ /** Popup was blocked by the browser */
7
+ declare class PopupBlockedError extends MySocialAuthError {
8
+ constructor();
9
+ }
10
+ /** User closed the popup before completion */
11
+ declare class PopupClosedError extends MySocialAuthError {
12
+ constructor();
13
+ }
14
+ /** No postMessage received within timeout */
15
+ declare class AuthTimeoutError extends MySocialAuthError {
16
+ constructor();
17
+ }
18
+ /** postMessage origin does not match expected authOrigin */
19
+ declare class InvalidOriginError extends MySocialAuthError {
20
+ constructor();
21
+ }
22
+ /** postMessage source is not the popup window */
23
+ declare class InvalidSourceError extends MySocialAuthError {
24
+ constructor();
25
+ }
26
+ /** State or nonce mismatch (replay protection) */
27
+ declare class InvalidStateError extends MySocialAuthError {
28
+ constructor();
29
+ }
30
+ //#endregion
31
+ export { AuthTimeoutError, InvalidOriginError, InvalidSourceError, InvalidStateError, MySocialAuthError, PopupBlockedError, PopupClosedError };
32
+ //# sourceMappingURL=errors.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;cAIa,iBAAA,SAA0B,KAAA;cAC1B,OAAA;AAAA;;cAQA,iBAAA,SAA0B,iBAAA;EAAA,WAAA,CAAA;AAAA;;cAW1B,gBAAA,SAAyB,iBAAA;EAAA,WAAA,CAAA;AAAA;;cASzB,gBAAA,SAAyB,iBAAA;EAAA,WAAA,CAAA;AAAA;;cASzB,kBAAA,SAA2B,iBAAA;EAAA,WAAA,CAAA;AAAA;AATxC;AAAA,cAkBa,kBAAA,SAA2B,iBAAA;EAAA,WAAA,CAAA;AAAA;;cAS3B,iBAAA,SAA0B,iBAAA;EAAA,WAAA,CAAA;AAAA"}
@@ -0,0 +1,61 @@
1
+ //#region src/errors.ts
2
+ /** Base error for MySocial Auth SDK */
3
+ var MySocialAuthError = class MySocialAuthError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "MySocialAuthError";
7
+ Object.setPrototypeOf(this, MySocialAuthError.prototype);
8
+ }
9
+ };
10
+ /** Popup was blocked by the browser */
11
+ var PopupBlockedError = class PopupBlockedError extends MySocialAuthError {
12
+ constructor() {
13
+ super("Popup was blocked by the browser. Call signIn() directly inside a user gesture (e.g. click handler).");
14
+ this.name = "PopupBlockedError";
15
+ Object.setPrototypeOf(this, PopupBlockedError.prototype);
16
+ }
17
+ };
18
+ /** User closed the popup before completion */
19
+ var PopupClosedError = class PopupClosedError extends MySocialAuthError {
20
+ constructor() {
21
+ super("User closed the popup before completing authentication.");
22
+ this.name = "PopupClosedError";
23
+ Object.setPrototypeOf(this, PopupClosedError.prototype);
24
+ }
25
+ };
26
+ /** No postMessage received within timeout */
27
+ var AuthTimeoutError = class AuthTimeoutError extends MySocialAuthError {
28
+ constructor() {
29
+ super("Authentication timed out. No response received from the popup.");
30
+ this.name = "AuthTimeoutError";
31
+ Object.setPrototypeOf(this, AuthTimeoutError.prototype);
32
+ }
33
+ };
34
+ /** postMessage origin does not match expected authOrigin */
35
+ var InvalidOriginError = class InvalidOriginError extends MySocialAuthError {
36
+ constructor() {
37
+ super("Invalid message origin. Expected message from auth.mysocial.network.");
38
+ this.name = "InvalidOriginError";
39
+ Object.setPrototypeOf(this, InvalidOriginError.prototype);
40
+ }
41
+ };
42
+ /** postMessage source is not the popup window */
43
+ var InvalidSourceError = class InvalidSourceError extends MySocialAuthError {
44
+ constructor() {
45
+ super("Invalid message source. Message must come from the auth popup.");
46
+ this.name = "InvalidSourceError";
47
+ Object.setPrototypeOf(this, InvalidSourceError.prototype);
48
+ }
49
+ };
50
+ /** State or nonce mismatch (replay protection) */
51
+ var InvalidStateError = class InvalidStateError extends MySocialAuthError {
52
+ constructor() {
53
+ super("Invalid state or nonce. Possible replay attack.");
54
+ this.name = "InvalidStateError";
55
+ Object.setPrototypeOf(this, InvalidStateError.prototype);
56
+ }
57
+ };
58
+
59
+ //#endregion
60
+ export { AuthTimeoutError, InvalidOriginError, InvalidSourceError, InvalidStateError, MySocialAuthError, PopupBlockedError, PopupClosedError };
61
+ //# sourceMappingURL=errors.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\n/** Base error for MySocial Auth SDK */\nexport class MySocialAuthError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'MySocialAuthError';\n\t\tObject.setPrototypeOf(this, MySocialAuthError.prototype);\n\t}\n}\n\n/** Popup was blocked by the browser */\nexport class PopupBlockedError extends MySocialAuthError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'Popup was blocked by the browser. Call signIn() directly inside a user gesture (e.g. click handler).',\n\t\t);\n\t\tthis.name = 'PopupBlockedError';\n\t\tObject.setPrototypeOf(this, PopupBlockedError.prototype);\n\t}\n}\n\n/** User closed the popup before completion */\nexport class PopupClosedError extends MySocialAuthError {\n\tconstructor() {\n\t\tsuper('User closed the popup before completing authentication.');\n\t\tthis.name = 'PopupClosedError';\n\t\tObject.setPrototypeOf(this, PopupClosedError.prototype);\n\t}\n}\n\n/** No postMessage received within timeout */\nexport class AuthTimeoutError extends MySocialAuthError {\n\tconstructor() {\n\t\tsuper('Authentication timed out. No response received from the popup.');\n\t\tthis.name = 'AuthTimeoutError';\n\t\tObject.setPrototypeOf(this, AuthTimeoutError.prototype);\n\t}\n}\n\n/** postMessage origin does not match expected authOrigin */\nexport class InvalidOriginError extends MySocialAuthError {\n\tconstructor() {\n\t\tsuper('Invalid message origin. Expected message from auth.mysocial.network.');\n\t\tthis.name = 'InvalidOriginError';\n\t\tObject.setPrototypeOf(this, InvalidOriginError.prototype);\n\t}\n}\n\n/** postMessage source is not the popup window */\nexport class InvalidSourceError extends MySocialAuthError {\n\tconstructor() {\n\t\tsuper('Invalid message source. Message must come from the auth popup.');\n\t\tthis.name = 'InvalidSourceError';\n\t\tObject.setPrototypeOf(this, InvalidSourceError.prototype);\n\t}\n}\n\n/** State or nonce mismatch (replay protection) */\nexport class InvalidStateError extends MySocialAuthError {\n\tconstructor() {\n\t\tsuper('Invalid state or nonce. Possible replay attack.');\n\t\tthis.name = 'InvalidStateError';\n\t\tObject.setPrototypeOf(this, InvalidStateError.prototype);\n\t}\n}\n"],"mappings":";;AAIA,IAAa,oBAAb,MAAa,0BAA0B,MAAM;CAC5C,YAAY,SAAiB;AAC5B,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,SAAO,eAAe,MAAM,kBAAkB,UAAU;;;;AAK1D,IAAa,oBAAb,MAAa,0BAA0B,kBAAkB;CACxD,cAAc;AACb,QACC,uGACA;AACD,OAAK,OAAO;AACZ,SAAO,eAAe,MAAM,kBAAkB,UAAU;;;;AAK1D,IAAa,mBAAb,MAAa,yBAAyB,kBAAkB;CACvD,cAAc;AACb,QAAM,0DAA0D;AAChE,OAAK,OAAO;AACZ,SAAO,eAAe,MAAM,iBAAiB,UAAU;;;;AAKzD,IAAa,mBAAb,MAAa,yBAAyB,kBAAkB;CACvD,cAAc;AACb,QAAM,iEAAiE;AACvE,OAAK,OAAO;AACZ,SAAO,eAAe,MAAM,iBAAiB,UAAU;;;;AAKzD,IAAa,qBAAb,MAAa,2BAA2B,kBAAkB;CACzD,cAAc;AACb,QAAM,uEAAuE;AAC7E,OAAK,OAAO;AACZ,SAAO,eAAe,MAAM,mBAAmB,UAAU;;;;AAK3D,IAAa,qBAAb,MAAa,2BAA2B,kBAAkB;CACzD,cAAc;AACb,QAAM,iEAAiE;AACvE,OAAK,OAAO;AACZ,SAAO,eAAe,MAAM,mBAAmB,UAAU;;;;AAK3D,IAAa,oBAAb,MAAa,0BAA0B,kBAAkB;CACxD,cAAc;AACb,QAAM,kDAAkD;AACxD,OAAK,OAAO;AACZ,SAAO,eAAe,MAAM,kBAAkB,UAAU"}
@@ -0,0 +1,73 @@
1
+ //#region src/exchange.ts
2
+ /** Fetch request_id from backend (optional flow). Backend validates return_origin against allowlist. */
3
+ async function fetchRequestId(apiBaseUrl, body) {
4
+ const url = `${apiBaseUrl.replace(/\/$/, "")}/auth/request`;
5
+ const res = await fetch(url, {
6
+ method: "POST",
7
+ headers: { "Content-Type": "application/json" },
8
+ body: JSON.stringify(body)
9
+ });
10
+ if (!res.ok) {
11
+ const text = await res.text();
12
+ throw new Error(`Auth request failed: ${res.status} ${text}`);
13
+ }
14
+ return (await res.json()).request_id;
15
+ }
16
+ /** Exchange auth code for tokens */
17
+ async function exchangeCode(apiBaseUrl, body) {
18
+ const url = `${apiBaseUrl.replace(/\/$/, "")}/auth/exchange`;
19
+ const res = await fetch(url, {
20
+ method: "POST",
21
+ headers: { "Content-Type": "application/json" },
22
+ body: JSON.stringify(body)
23
+ });
24
+ if (!res.ok) {
25
+ const text = await res.text();
26
+ throw new Error(`Token exchange failed: ${res.status} ${text}`);
27
+ }
28
+ const data = await res.json();
29
+ const expires_at = Date.now() + data.expires_in * 1e3;
30
+ return {
31
+ access_token: data.access_token,
32
+ refresh_token: data.refresh_token,
33
+ expires_at,
34
+ user: data.user
35
+ };
36
+ }
37
+ /** Refresh access token using refresh_token */
38
+ async function refreshTokens(apiBaseUrl, refreshToken) {
39
+ const url = `${apiBaseUrl.replace(/\/$/, "")}/auth/refresh`;
40
+ const res = await fetch(url, {
41
+ method: "POST",
42
+ headers: { "Content-Type": "application/json" },
43
+ body: JSON.stringify({ refresh_token: refreshToken })
44
+ });
45
+ if (!res.ok) {
46
+ const text = await res.text();
47
+ throw new Error(`Token refresh failed: ${res.status} ${text}`);
48
+ }
49
+ const data = await res.json();
50
+ const expires_at = Date.now() + data.expires_in * 1e3;
51
+ return {
52
+ access_token: data.access_token,
53
+ refresh_token: data.refresh_token ?? refreshToken,
54
+ expires_at,
55
+ user: data.user
56
+ };
57
+ }
58
+ /** Logout / invalidate session on backend */
59
+ async function logout(apiBaseUrl) {
60
+ const url = `${apiBaseUrl.replace(/\/$/, "")}/auth/logout`;
61
+ const res = await fetch(url, {
62
+ method: "POST",
63
+ headers: { "Content-Type": "application/json" }
64
+ });
65
+ if (!res.ok) {
66
+ const text = await res.text();
67
+ throw new Error(`Logout failed: ${res.status} ${text}`);
68
+ }
69
+ }
70
+
71
+ //#endregion
72
+ export { exchangeCode, fetchRequestId, logout, refreshTokens };
73
+ //# sourceMappingURL=exchange.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exchange.mjs","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {\n\tAuthRequestRequest,\n\tAuthRequestResponse,\n\tExchangeRequest,\n\tExchangeResponse,\n\tSession,\n} from './types.js';\n\n/** Fetch request_id from backend (optional flow). Backend validates return_origin against allowlist. */\nexport async function fetchRequestId(\n\tapiBaseUrl: string,\n\tbody: AuthRequestRequest,\n): Promise<string> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/request`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Auth request failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as AuthRequestResponse;\n\treturn data.request_id;\n}\n\n/** Exchange auth code for tokens */\nexport async function exchangeCode(apiBaseUrl: string, body: ExchangeRequest): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/exchange`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token exchange failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Refresh access token using refresh_token */\nexport async function refreshTokens(apiBaseUrl: string, refreshToken: string): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/refresh`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({ refresh_token: refreshToken }),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token refresh failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token ?? refreshToken,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Logout / invalidate session on backend */\nexport async function logout(apiBaseUrl: string): Promise<void> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/logout`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Logout failed: ${res.status} ${text}`);\n\t}\n}\n"],"mappings":";;AAYA,eAAsB,eACrB,YACA,MACkB;CAClB,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,KAAK;EAC1B,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,wBAAwB,IAAI,OAAO,GAAG,OAAO;;AAG9D,SADc,MAAM,IAAI,MAAM,EAClB;;;AAIb,eAAsB,aAAa,YAAoB,MAAyC;CAC/F,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,KAAK;EAC1B,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,0BAA0B,IAAI,OAAO,GAAG,OAAO;;CAEhE,MAAM,OAAQ,MAAM,IAAI,MAAM;CAC9B,MAAM,aAAa,KAAK,KAAK,GAAG,KAAK,aAAa;AAClD,QAAO;EACN,cAAc,KAAK;EACnB,eAAe,KAAK;EACpB;EACA,MAAM,KAAK;EACX;;;AAIF,eAAsB,cAAc,YAAoB,cAAwC;CAC/F,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,EAAE,eAAe,cAAc,CAAC;EACrD,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,yBAAyB,IAAI,OAAO,GAAG,OAAO;;CAE/D,MAAM,OAAQ,MAAM,IAAI,MAAM;CAC9B,MAAM,aAAa,KAAK,KAAK,GAAG,KAAK,aAAa;AAClD,QAAO;EACN,cAAc,KAAK;EACnB,eAAe,KAAK,iBAAiB;EACrC;EACA,MAAM,KAAK;EACX;;;AAIF,eAAsB,OAAO,YAAmC;CAC/D,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,kBAAkB,IAAI,OAAO,GAAG,OAAO"}
@@ -0,0 +1,6 @@
1
+ import { AuthMode, AuthProvider, AuthUser, MySocialAuthConfig, Session, SignInOptions, StorageAdapter, StorageOption } from "./types.mjs";
2
+ import { MySocialAuth } from "./auth.mjs";
3
+ import { createMySocialAuth } from "./createMySocialAuth.mjs";
4
+ import { AuthTimeoutError, InvalidOriginError, InvalidSourceError, InvalidStateError, MySocialAuthError, PopupBlockedError, PopupClosedError } from "./errors.mjs";
5
+ import { getPopupFeatures } from "./popup-utils.mjs";
6
+ export { type AuthMode, type AuthProvider, AuthTimeoutError, type AuthUser, InvalidOriginError, InvalidSourceError, InvalidStateError, type MySocialAuth, type MySocialAuthConfig, MySocialAuthError, PopupBlockedError, PopupClosedError, type Session, type SignInOptions, type StorageAdapter, type StorageOption, createMySocialAuth, getPopupFeatures };
package/dist/index.mjs ADDED
@@ -0,0 +1,5 @@
1
+ import { AuthTimeoutError, InvalidOriginError, InvalidSourceError, InvalidStateError, MySocialAuthError, PopupBlockedError, PopupClosedError } from "./errors.mjs";
2
+ import { getPopupFeatures } from "./popup-utils.mjs";
3
+ import { createMySocialAuth } from "./createMySocialAuth.mjs";
4
+
5
+ export { AuthTimeoutError, InvalidOriginError, InvalidSourceError, InvalidStateError, MySocialAuthError, PopupBlockedError, PopupClosedError, createMySocialAuth, getPopupFeatures };
package/dist/pkce.mjs ADDED
@@ -0,0 +1,41 @@
1
+ //#region src/pkce.ts
2
+ /**
3
+ * Generate a cryptographically random code verifier (32 bytes = 256 bits, base64url).
4
+ * Used for PKCE in OAuth flows.
5
+ */
6
+ async function generateCodeVerifier() {
7
+ const array = new Uint8Array(32);
8
+ crypto.getRandomValues(array);
9
+ return base64UrlEncode(array);
10
+ }
11
+ /**
12
+ * Generate code challenge from verifier using SHA-256 (S256 method).
13
+ */
14
+ async function generateCodeChallenge(verifier) {
15
+ const data = new TextEncoder().encode(verifier);
16
+ const hash = await crypto.subtle.digest("SHA-256", data);
17
+ return base64UrlEncode(new Uint8Array(hash));
18
+ }
19
+ /**
20
+ * Generate a random state (128 bits+ entropy) for CSRF protection.
21
+ */
22
+ function generateState() {
23
+ const array = new Uint8Array(16);
24
+ crypto.getRandomValues(array);
25
+ return base64UrlEncode(array);
26
+ }
27
+ /**
28
+ * Generate a random nonce (128 bits+ entropy) for replay protection.
29
+ */
30
+ function generateNonce() {
31
+ const array = new Uint8Array(16);
32
+ crypto.getRandomValues(array);
33
+ return base64UrlEncode(array);
34
+ }
35
+ function base64UrlEncode(array) {
36
+ return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
37
+ }
38
+
39
+ //#endregion
40
+ export { generateCodeChallenge, generateCodeVerifier, generateNonce, generateState };
41
+ //# sourceMappingURL=pkce.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.mjs","names":[],"sources":["../src/pkce.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generate a cryptographically random code verifier (32 bytes = 256 bits, base64url).\n * Used for PKCE in OAuth flows.\n */\nexport async function generateCodeVerifier(): Promise<string> {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate code challenge from verifier using SHA-256 (S256 method).\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(verifier);\n\tconst hash = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(new Uint8Array(hash));\n}\n\n/**\n * Generate a random state (128 bits+ entropy) for CSRF protection.\n */\nexport function generateState(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate a random nonce (128 bits+ entropy) for replay protection.\n */\nexport function generateNonce(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\nfunction base64UrlEncode(array: Uint8Array): string {\n\tconst base64 = btoa(String.fromCharCode(...array));\n\treturn base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n"],"mappings":";;;;;AAOA,eAAsB,uBAAwC;CAC7D,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;;;;AAM9B,eAAsB,sBAAsB,UAAmC;CAE9E,MAAM,OADU,IAAI,aAAa,CACZ,OAAO,SAAS;CACrC,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK;AACxD,QAAO,gBAAgB,IAAI,WAAW,KAAK,CAAC;;;;;AAM7C,SAAgB,gBAAwB;CACvC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;;;;AAM9B,SAAgB,gBAAwB;CACvC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;AAG9B,SAAS,gBAAgB,OAA2B;AAEnD,QADe,KAAK,OAAO,aAAa,GAAG,MAAM,CAAC,CACpC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,GAAG"}
@@ -0,0 +1,9 @@
1
+ //#region src/popup-utils.d.ts
2
+ /**
3
+ * Get window.open() features string for a centered popup.
4
+ * Width 420, height 720 by default (works on laptop screens).
5
+ */
6
+ declare function getPopupFeatures(width?: number, height?: number): string;
7
+ //#endregion
8
+ export { getPopupFeatures };
9
+ //# sourceMappingURL=popup-utils.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popup-utils.d.mts","names":[],"sources":["../src/popup-utils.ts"],"mappings":";;AAOA;;;iBAAgB,gBAAA,CAAiB,KAAA,WAAa,MAAA"}
@@ -0,0 +1,12 @@
1
+ //#region src/popup-utils.ts
2
+ /**
3
+ * Get window.open() features string for a centered popup.
4
+ * Width 420, height 720 by default (works on laptop screens).
5
+ */
6
+ function getPopupFeatures(width = 420, height = 720) {
7
+ return `width=${width},height=${height},left=${Math.round((window.screen.width - width) / 2)},top=${Math.round((window.screen.height - height) / 2)},popup=yes`;
8
+ }
9
+
10
+ //#endregion
11
+ export { getPopupFeatures };
12
+ //# sourceMappingURL=popup-utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popup-utils.mjs","names":[],"sources":["../src/popup-utils.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Get window.open() features string for a centered popup.\n * Width 420, height 720 by default (works on laptop screens).\n */\nexport function getPopupFeatures(width = 420, height = 720): string {\n\tconst left = Math.round((window.screen.width - width) / 2);\n\tconst top = Math.round((window.screen.height - height) / 2);\n\treturn `width=${width},height=${height},left=${left},top=${top},popup=yes`;\n}\n"],"mappings":";;;;;AAOA,SAAgB,iBAAiB,QAAQ,KAAK,SAAS,KAAa;AAGnE,QAAO,SAAS,MAAM,UAAU,OAAO,QAF1B,KAAK,OAAO,OAAO,OAAO,QAAQ,SAAS,EAAE,CAEN,OADxC,KAAK,OAAO,OAAO,OAAO,SAAS,UAAU,EAAE,CACI"}
package/dist/popup.mjs ADDED
@@ -0,0 +1,119 @@
1
+ import { AuthTimeoutError, InvalidStateError, PopupBlockedError, PopupClosedError } from "./errors.mjs";
2
+ import { generateCodeChallenge, generateCodeVerifier, generateNonce, generateState } from "./pkce.mjs";
3
+ import { getPopupFeatures } from "./popup-utils.mjs";
4
+ import { exchangeCode, fetchRequestId } from "./exchange.mjs";
5
+
6
+ //#region src/popup.ts
7
+ /** Get return_origin from redirectUri (e.g. https://dripdrop.social/auth/callback -> https://dripdrop.social) */
8
+ function getReturnOrigin(redirectUri) {
9
+ try {
10
+ const u = new URL(redirectUri);
11
+ return `${u.protocol}//${u.host}`;
12
+ } catch {
13
+ return "";
14
+ }
15
+ }
16
+ /**
17
+ * Open auth popup and wait for postMessage. Safari: open with about:blank first, then set location.
18
+ * Returns session on success. Rejects on error, timeout, or user close.
19
+ */
20
+ async function openAuthPopup(options) {
21
+ const { apiBaseUrl, authOrigin, clientId, redirectUri, provider, timeout = 12e4, useRequestId = false } = options;
22
+ const state = generateState();
23
+ const nonce = generateNonce();
24
+ const returnOrigin = getReturnOrigin(redirectUri);
25
+ let requestId;
26
+ if (useRequestId) requestId = await fetchRequestId(apiBaseUrl, {
27
+ client_id: clientId,
28
+ redirect_uri: redirectUri,
29
+ return_origin: returnOrigin
30
+ });
31
+ const { codeVerifier, codeChallenge } = await (async () => {
32
+ const verifier = await generateCodeVerifier();
33
+ return {
34
+ codeVerifier: verifier,
35
+ codeChallenge: await generateCodeChallenge(verifier)
36
+ };
37
+ })();
38
+ const features = getPopupFeatures(420, 720);
39
+ const popup = window.open("about:blank", "_blank", features);
40
+ if (!popup || popup.closed) throw new PopupBlockedError();
41
+ const params = new URLSearchParams({
42
+ client_id: clientId,
43
+ redirect_uri: redirectUri,
44
+ state,
45
+ nonce,
46
+ return_origin: returnOrigin,
47
+ mode: "popup",
48
+ provider: provider ?? "",
49
+ code_challenge: codeChallenge,
50
+ code_challenge_method: "S256"
51
+ });
52
+ if (requestId) params.set("request_id", requestId);
53
+ const loginUrl = `${authOrigin.replace(/\/$/, "")}/login?${params.toString()}`;
54
+ popup.location.href = loginUrl;
55
+ return new Promise((resolve, reject) => {
56
+ let timeoutId = null;
57
+ let closedCheckId = null;
58
+ const cleanup = () => {
59
+ if (timeoutId) clearTimeout(timeoutId);
60
+ if (closedCheckId) clearInterval(closedCheckId);
61
+ window.removeEventListener("message", handler);
62
+ };
63
+ const handler = (event) => {
64
+ if (event.origin !== authOrigin) return;
65
+ if (event.source !== popup) return;
66
+ const data = event.data;
67
+ if (!data || typeof data !== "object" || !data.type) return;
68
+ if (data.type === "MYSOCIAL_AUTH_RESULT") {
69
+ const msg = data;
70
+ if (msg.state !== state || msg.nonce !== nonce) {
71
+ cleanup();
72
+ reject(new InvalidStateError());
73
+ return;
74
+ }
75
+ if (msg.clientId !== clientId) {
76
+ cleanup();
77
+ reject(new InvalidStateError());
78
+ return;
79
+ }
80
+ if (requestId && msg.requestId !== requestId) {
81
+ cleanup();
82
+ reject(new InvalidStateError());
83
+ return;
84
+ }
85
+ cleanup();
86
+ popup.close();
87
+ exchangeCode(apiBaseUrl, {
88
+ code: msg.code,
89
+ code_verifier: codeVerifier,
90
+ redirect_uri: redirectUri,
91
+ state,
92
+ nonce,
93
+ request_id: requestId
94
+ }).then(resolve).catch(reject);
95
+ } else if (data.type === "MYSOCIAL_AUTH_ERROR") {
96
+ const msg = data;
97
+ cleanup();
98
+ popup.close();
99
+ reject(new Error(msg.error ?? "Authentication failed"));
100
+ }
101
+ };
102
+ window.addEventListener("message", handler);
103
+ timeoutId = setTimeout(() => {
104
+ cleanup();
105
+ popup.close();
106
+ reject(new AuthTimeoutError());
107
+ }, timeout);
108
+ closedCheckId = setInterval(() => {
109
+ if (popup.closed) {
110
+ cleanup();
111
+ reject(new PopupClosedError());
112
+ }
113
+ }, 200);
114
+ });
115
+ }
116
+
117
+ //#endregion
118
+ export { openAuthPopup };
119
+ //# sourceMappingURL=popup.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popup.mjs","names":[],"sources":["../src/popup.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AuthProvider, AuthResultMessage, AuthErrorMessage } from './types.js';\nimport {\n\tAuthTimeoutError,\n\tInvalidStateError,\n\tPopupBlockedError,\n\tPopupClosedError,\n} from './errors.js';\nimport {\n\tgenerateCodeChallenge,\n\tgenerateCodeVerifier,\n\tgenerateNonce,\n\tgenerateState,\n} from './pkce.js';\nimport { getPopupFeatures } from './popup-utils.js';\nimport { exchangeCode, fetchRequestId } from './exchange.js';\nimport type { Session } from './types.js';\n\n/** Get return_origin from redirectUri (e.g. https://dripdrop.social/auth/callback -> https://dripdrop.social) */\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport interface OpenPopupOptions {\n\tapiBaseUrl: string;\n\tauthOrigin: string;\n\tclientId: string;\n\tredirectUri: string;\n\tprovider?: AuthProvider;\n\ttimeout?: number;\n\tuseRequestId?: boolean;\n}\n\n/**\n * Open auth popup and wait for postMessage. Safari: open with about:blank first, then set location.\n * Returns session on success. Rejects on error, timeout, or user close.\n */\nexport async function openAuthPopup(options: OpenPopupOptions): Promise<Session> {\n\tconst {\n\t\tapiBaseUrl,\n\t\tauthOrigin,\n\t\tclientId,\n\t\tredirectUri,\n\t\tprovider,\n\t\ttimeout = 120_000,\n\t\tuseRequestId = false,\n\t} = options;\n\n\tconst state = generateState();\n\tconst nonce = generateNonce();\n\tconst returnOrigin = getReturnOrigin(redirectUri);\n\n\tlet requestId: string | undefined;\n\tif (useRequestId) {\n\t\trequestId = await fetchRequestId(apiBaseUrl, {\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t\treturn_origin: returnOrigin,\n\t\t});\n\t}\n\n\tconst { codeVerifier, codeChallenge } = await (async () => {\n\t\tconst verifier = await generateCodeVerifier();\n\t\tconst challenge = await generateCodeChallenge(verifier);\n\t\treturn { codeVerifier: verifier, codeChallenge: challenge };\n\t})();\n\n\t// Open popup immediately (Safari requires user gesture). Use about:blank, then set location.\n\tconst features = getPopupFeatures(420, 720);\n\tconst popup = window.open('about:blank', '_blank', features);\n\n\tif (!popup || popup.closed) {\n\t\tthrow new PopupBlockedError();\n\t}\n\n\tconst params = new URLSearchParams({\n\t\tclient_id: clientId,\n\t\tredirect_uri: redirectUri,\n\t\tstate,\n\t\tnonce,\n\t\treturn_origin: returnOrigin,\n\t\tmode: 'popup',\n\t\tprovider: provider ?? '',\n\t\tcode_challenge: codeChallenge,\n\t\tcode_challenge_method: 'S256',\n\t});\n\tif (requestId) params.set('request_id', requestId);\n\n\tconst loginUrl = `${authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\n\t// Set location after popup is open (Safari-safe)\n\tpopup.location.href = loginUrl;\n\n\treturn new Promise<Session>((resolve, reject) => {\n\t\tlet timeoutId: ReturnType<typeof setTimeout> | null = null;\n\t\tlet closedCheckId: ReturnType<typeof setInterval> | null = null;\n\n\t\tconst cleanup = () => {\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (closedCheckId) clearInterval(closedCheckId);\n\t\t\twindow.removeEventListener('message', handler);\n\t\t};\n\n\t\tconst handler = (event: MessageEvent) => {\n\t\t\tif (event.origin !== authOrigin) return;\n\t\t\tif (event.source !== popup) return;\n\n\t\t\tconst data = event.data;\n\t\t\tif (!data || typeof data !== 'object' || !data.type) return;\n\n\t\t\tif (data.type === 'MYSOCIAL_AUTH_RESULT') {\n\t\t\t\tconst msg = data as AuthResultMessage;\n\t\t\t\tif (msg.state !== state || msg.nonce !== nonce) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (msg.clientId !== clientId) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (requestId && msg.requestId !== requestId) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcleanup();\n\t\t\t\tpopup.close();\n\n\t\t\t\texchangeCode(apiBaseUrl, {\n\t\t\t\t\tcode: msg.code,\n\t\t\t\t\tcode_verifier: codeVerifier,\n\t\t\t\t\tredirect_uri: redirectUri,\n\t\t\t\t\tstate,\n\t\t\t\t\tnonce,\n\t\t\t\t\trequest_id: requestId,\n\t\t\t\t})\n\t\t\t\t\t.then(resolve)\n\t\t\t\t\t.catch(reject);\n\t\t\t} else if (data.type === 'MYSOCIAL_AUTH_ERROR') {\n\t\t\t\tconst msg = data as AuthErrorMessage;\n\t\t\t\tcleanup();\n\t\t\t\tpopup.close();\n\t\t\t\treject(new Error(msg.error ?? 'Authentication failed'));\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener('message', handler);\n\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tcleanup();\n\t\t\tpopup.close();\n\t\t\treject(new AuthTimeoutError());\n\t\t}, timeout);\n\n\t\tclosedCheckId = setInterval(() => {\n\t\t\tif (popup.closed) {\n\t\t\t\tcleanup();\n\t\t\t\treject(new PopupClosedError());\n\t\t\t}\n\t\t}, 200);\n\t});\n}\n"],"mappings":";;;;;;;AAqBA,SAAS,gBAAgB,aAA6B;AACrD,KAAI;EACH,MAAM,IAAI,IAAI,IAAI,YAAY;AAC9B,SAAO,GAAG,EAAE,SAAS,IAAI,EAAE;SACpB;AACP,SAAO;;;;;;;AAkBT,eAAsB,cAAc,SAA6C;CAChF,MAAM,EACL,YACA,YACA,UACA,aACA,UACA,UAAU,MACV,eAAe,UACZ;CAEJ,MAAM,QAAQ,eAAe;CAC7B,MAAM,QAAQ,eAAe;CAC7B,MAAM,eAAe,gBAAgB,YAAY;CAEjD,IAAI;AACJ,KAAI,aACH,aAAY,MAAM,eAAe,YAAY;EAC5C,WAAW;EACX,cAAc;EACd,eAAe;EACf,CAAC;CAGH,MAAM,EAAE,cAAc,kBAAkB,OAAO,YAAY;EAC1D,MAAM,WAAW,MAAM,sBAAsB;AAE7C,SAAO;GAAE,cAAc;GAAU,eADf,MAAM,sBAAsB,SAAS;GACI;KACxD;CAGJ,MAAM,WAAW,iBAAiB,KAAK,IAAI;CAC3C,MAAM,QAAQ,OAAO,KAAK,eAAe,UAAU,SAAS;AAE5D,KAAI,CAAC,SAAS,MAAM,OACnB,OAAM,IAAI,mBAAmB;CAG9B,MAAM,SAAS,IAAI,gBAAgB;EAClC,WAAW;EACX,cAAc;EACd;EACA;EACA,eAAe;EACf,MAAM;EACN,UAAU,YAAY;EACtB,gBAAgB;EAChB,uBAAuB;EACvB,CAAC;AACF,KAAI,UAAW,QAAO,IAAI,cAAc,UAAU;CAElD,MAAM,WAAW,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC,SAAS,OAAO,UAAU;AAG5E,OAAM,SAAS,OAAO;AAEtB,QAAO,IAAI,SAAkB,SAAS,WAAW;EAChD,IAAI,YAAkD;EACtD,IAAI,gBAAuD;EAE3D,MAAM,gBAAgB;AACrB,OAAI,UAAW,cAAa,UAAU;AACtC,OAAI,cAAe,eAAc,cAAc;AAC/C,UAAO,oBAAoB,WAAW,QAAQ;;EAG/C,MAAM,WAAW,UAAwB;AACxC,OAAI,MAAM,WAAW,WAAY;AACjC,OAAI,MAAM,WAAW,MAAO;GAE5B,MAAM,OAAO,MAAM;AACnB,OAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,KAAM;AAErD,OAAI,KAAK,SAAS,wBAAwB;IACzC,MAAM,MAAM;AACZ,QAAI,IAAI,UAAU,SAAS,IAAI,UAAU,OAAO;AAC/C,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAED,QAAI,IAAI,aAAa,UAAU;AAC9B,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAED,QAAI,aAAa,IAAI,cAAc,WAAW;AAC7C,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAGD,aAAS;AACT,UAAM,OAAO;AAEb,iBAAa,YAAY;KACxB,MAAM,IAAI;KACV,eAAe;KACf,cAAc;KACd;KACA;KACA,YAAY;KACZ,CAAC,CACA,KAAK,QAAQ,CACb,MAAM,OAAO;cACL,KAAK,SAAS,uBAAuB;IAC/C,MAAM,MAAM;AACZ,aAAS;AACT,UAAM,OAAO;AACb,WAAO,IAAI,MAAM,IAAI,SAAS,wBAAwB,CAAC;;;AAIzD,SAAO,iBAAiB,WAAW,QAAQ;AAE3C,cAAY,iBAAiB;AAC5B,YAAS;AACT,SAAM,OAAO;AACb,UAAO,IAAI,kBAAkB,CAAC;KAC5B,QAAQ;AAEX,kBAAgB,kBAAkB;AACjC,OAAI,MAAM,QAAQ;AACjB,aAAS;AACT,WAAO,IAAI,kBAAkB,CAAC;;KAE7B,IAAI;GACN"}
@@ -0,0 +1,31 @@
1
+ //#region src/storage.ts
2
+ const SESSION_KEY = "mysocial_auth_session";
3
+ const REDIRECT_STATE_PREFIX = "mysocial_auth_redirect_";
4
+ /** In-memory storage (default, most secure) */
5
+ function createMemoryStorage() {
6
+ const store = /* @__PURE__ */ new Map();
7
+ return {
8
+ get: (k) => store.get(k) ?? null,
9
+ set: (k, v) => store.set(k, v),
10
+ remove: (k) => store.delete(k)
11
+ };
12
+ }
13
+ /** sessionStorage adapter (persists across reloads in same tab) */
14
+ function createSessionStorage() {
15
+ return {
16
+ get: (k) => typeof sessionStorage !== "undefined" ? sessionStorage.getItem(k) : null,
17
+ set: (k, v) => sessionStorage?.setItem(k, v),
18
+ remove: (k) => sessionStorage?.removeItem(k)
19
+ };
20
+ }
21
+ /** sessionStorage for redirect state (always used for redirect flow; memory not viable across navigation) */
22
+ const redirectStorage = typeof sessionStorage !== "undefined" ? createSessionStorage() : createMemoryStorage();
23
+ function createStorage(option) {
24
+ if (option === "session") return createSessionStorage();
25
+ if (option && typeof option === "object" && typeof option.get === "function" && typeof option.set === "function" && typeof option.remove === "function") return option;
26
+ return createMemoryStorage();
27
+ }
28
+
29
+ //#endregion
30
+ export { REDIRECT_STATE_PREFIX, SESSION_KEY, createStorage, redirectStorage };
31
+ //# sourceMappingURL=storage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.mjs","names":[],"sources":["../src/storage.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { StorageAdapter, StorageOption } from './types.js';\n\nconst SESSION_KEY = 'mysocial_auth_session';\nconst REDIRECT_STATE_PREFIX = 'mysocial_auth_redirect_';\n\n/** In-memory storage (default, most secure) */\nfunction createMemoryStorage(): StorageAdapter {\n\tconst store = new Map<string, string>();\n\treturn {\n\t\tget: (k) => store.get(k) ?? null,\n\t\tset: (k, v) => store.set(k, v),\n\t\tremove: (k) => store.delete(k),\n\t};\n}\n\n/** sessionStorage adapter (persists across reloads in same tab) */\nfunction createSessionStorage(): StorageAdapter {\n\treturn {\n\t\tget: (k) => (typeof sessionStorage !== 'undefined' ? sessionStorage.getItem(k) : null),\n\t\tset: (k, v) => sessionStorage?.setItem(k, v),\n\t\tremove: (k) => sessionStorage?.removeItem(k),\n\t};\n}\n\n/** sessionStorage for redirect state (always used for redirect flow; memory not viable across navigation) */\nexport const redirectStorage: StorageAdapter =\n\ttypeof sessionStorage !== 'undefined' ? createSessionStorage() : createMemoryStorage();\n\nexport function createStorage(option?: StorageOption): StorageAdapter {\n\tif (option === 'session') return createSessionStorage();\n\tif (\n\t\toption &&\n\t\ttypeof option === 'object' &&\n\t\ttypeof (option as StorageAdapter).get === 'function' &&\n\t\ttypeof (option as StorageAdapter).set === 'function' &&\n\t\ttypeof (option as StorageAdapter).remove === 'function'\n\t) {\n\t\treturn option as StorageAdapter;\n\t}\n\treturn createMemoryStorage();\n}\n\nexport { SESSION_KEY, REDIRECT_STATE_PREFIX };\n"],"mappings":";AAKA,MAAM,cAAc;AACpB,MAAM,wBAAwB;;AAG9B,SAAS,sBAAsC;CAC9C,MAAM,wBAAQ,IAAI,KAAqB;AACvC,QAAO;EACN,MAAM,MAAM,MAAM,IAAI,EAAE,IAAI;EAC5B,MAAM,GAAG,MAAM,MAAM,IAAI,GAAG,EAAE;EAC9B,SAAS,MAAM,MAAM,OAAO,EAAE;EAC9B;;;AAIF,SAAS,uBAAuC;AAC/C,QAAO;EACN,MAAM,MAAO,OAAO,mBAAmB,cAAc,eAAe,QAAQ,EAAE,GAAG;EACjF,MAAM,GAAG,MAAM,gBAAgB,QAAQ,GAAG,EAAE;EAC5C,SAAS,MAAM,gBAAgB,WAAW,EAAE;EAC5C;;;AAIF,MAAa,kBACZ,OAAO,mBAAmB,cAAc,sBAAsB,GAAG,qBAAqB;AAEvF,SAAgB,cAAc,QAAwC;AACrE,KAAI,WAAW,UAAW,QAAO,sBAAsB;AACvD,KACC,UACA,OAAO,WAAW,YAClB,OAAQ,OAA0B,QAAQ,cAC1C,OAAQ,OAA0B,QAAQ,cAC1C,OAAQ,OAA0B,WAAW,WAE7C,QAAO;AAER,QAAO,qBAAqB"}
@@ -0,0 +1,49 @@
1
+ //#region src/types.d.ts
2
+ /** OAuth provider options for Login with MySocial */
3
+ type AuthProvider = 'google' | 'apple' | 'facebook' | 'twitch';
4
+ /** Auth flow mode: popup (opens window) or redirect (navigates away) */
5
+ type AuthMode = 'popup' | 'redirect';
6
+ /** User object returned from exchange/refresh */
7
+ interface AuthUser {
8
+ id?: string;
9
+ email?: string;
10
+ name?: string;
11
+ [key: string]: unknown;
12
+ }
13
+ /** Session object after successful auth */
14
+ interface Session {
15
+ access_token: string;
16
+ refresh_token?: string;
17
+ expires_at: number;
18
+ user: AuthUser;
19
+ }
20
+ /** Storage adapter for persisting session (memory, sessionStorage, or custom) */
21
+ interface StorageAdapter {
22
+ get(key: string): string | null;
23
+ set(key: string, value: string): void;
24
+ remove(key: string): void;
25
+ }
26
+ /** Storage option: 'memory' (default), 'session', or custom adapter */
27
+ type StorageOption = 'memory' | 'session' | StorageAdapter;
28
+ /** MySocial Auth SDK configuration */
29
+ interface MySocialAuthConfig {
30
+ apiBaseUrl: string;
31
+ authOrigin: string;
32
+ clientId: string;
33
+ redirectUri: string;
34
+ storage?: StorageOption;
35
+ /** Popup timeout in ms (default 120000 = 2 min) */
36
+ popupTimeout?: number;
37
+ /** Use request_id flow: call /auth/request before opening popup (requires backend support) */
38
+ useRequestId?: boolean;
39
+ }
40
+ /** Sign-in options */
41
+ interface SignInOptions {
42
+ provider?: AuthProvider;
43
+ mode?: AuthMode;
44
+ }
45
+ /** Auth state change callback */
46
+ type AuthStateChangeCallback = (session: Session | null) => void;
47
+ //#endregion
48
+ export { AuthMode, AuthProvider, AuthStateChangeCallback, AuthUser, MySocialAuthConfig, Session, SignInOptions, StorageAdapter, StorageOption };
49
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;KAIY,YAAA;;KAGA,QAAA;;UAGK,QAAA;EAChB,EAAA;EACA,KAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;AAJF;AAAA,UAQiB,OAAA;EAChB,YAAA;EACA,aAAA;EACA,UAAA;EACA,IAAA,EAAM,QAAA;AAAA;;UAIU,cAAA;EAChB,GAAA,CAAI,GAAA;EACJ,GAAA,CAAI,GAAA,UAAa,KAAA;EACjB,MAAA,CAAO,GAAA;AAAA;;KAII,aAAA,0BAAuC,cAAA;;UAGlC,kBAAA;EAChB,UAAA;EACA,UAAA;EACA,QAAA;EACA,WAAA;EACA,OAAA,GAAU,aAAA;EAfoB;EAiB9B,YAAA;EAjB8B;EAmB9B,YAAA;AAAA;;UAIgB,aAAA;EAChB,QAAA,GAAW,YAAA;EACX,IAAA,GAAO,QAAA;AAAA;;KAII,uBAAA,IAA2B,OAAA,EAAS,OAAA"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@socialproof/mysocial-auth",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "MySocial Auth SDK for Login with MySocial via popup or redirect",
6
+ "license": "Apache-2.0",
7
+ "author": "Mysten Labs <build@socialprooflabs.com>",
8
+ "type": "module",
9
+ "main": "./dist/index.mjs",
10
+ "types": "./dist/index.d.mts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.mts",
14
+ "import": "./dist/index.mjs",
15
+ "default": "./dist/index.mjs"
16
+ }
17
+ },
18
+ "sideEffects": false,
19
+ "files": [
20
+ "CHANGELOG.md",
21
+ "dist"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/MystenLabs/ts-sdks.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/mystenlabs/ts-sdks/issues"
29
+ },
30
+ "homepage": "https://github.com/MystenLabs/ts-sdks/tree/main/packages/mysocial-auth#readme",
31
+ "devDependencies": {
32
+ "@types/node": "^25.0.8",
33
+ "happy-dom": "^20.1.0",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.0.17"
36
+ },
37
+ "dependencies": {
38
+ "@socialproof/utils": "^0.0.2"
39
+ },
40
+ "scripts": {
41
+ "clean": "rm -rf tsconfig.tsbuildinfo ./dist",
42
+ "build": "rm -rf dist && tsc --noEmit && tsdown",
43
+ "vitest": "vitest",
44
+ "test": "pnpm test:typecheck && pnpm test:unit",
45
+ "test:typecheck": "tsc -p ./test",
46
+ "test:unit": "vitest run",
47
+ "prettier:check": "prettier -c --ignore-unknown .",
48
+ "prettier:fix": "prettier -w --ignore-unknown .",
49
+ "oxlint:check": "oxlint .",
50
+ "oxlint:fix": "oxlint --fix .",
51
+ "lint": "pnpm run oxlint:check && pnpm run prettier:check",
52
+ "lint:fix": "pnpm run oxlint:fix && pnpm run prettier:fix"
53
+ }
54
+ }