@superfan-app/spotify-auth 0.1.54 → 0.1.55

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.
@@ -9,6 +9,10 @@ export type SpotifyScopes = 'app-remote-control' | 'playlist-modify-private' | '
9
9
  export interface SpotifyAuthEvent {
10
10
  success: boolean;
11
11
  token: string | null;
12
+ refreshToken: string | null;
13
+ expiresIn: number | null;
14
+ tokenType: string | null;
15
+ scope: string | null;
12
16
  error?: SpotifyAuthError;
13
17
  }
14
18
  /**
@@ -19,6 +23,14 @@ export interface SpotifyAuthorizationData {
19
23
  success: boolean;
20
24
  /** The access token if authorization was successful, null otherwise */
21
25
  token: string | null;
26
+ /** The refresh token if authorization was successful, null otherwise */
27
+ refreshToken: string | null;
28
+ /** The token expiration time in seconds if authorization was successful, null otherwise */
29
+ expiresIn: number | null;
30
+ /** The token type (e.g. "Bearer") if authorization was successful, null otherwise */
31
+ tokenType: string | null;
32
+ /** The granted scopes if authorization was successful, null otherwise */
33
+ scope: string | null;
22
34
  /** Error information if authorization failed */
23
35
  error?: SpotifyAuthError;
24
36
  }
@@ -69,11 +81,26 @@ export interface SpotifyAuthViewProps {
69
81
  name: string;
70
82
  }
71
83
  /**
72
- * Context for Spotify authentication state and actions
84
+ * Spotify authentication state containing all token-related information
73
85
  */
74
- export interface SpotifyAuthContext {
86
+ export interface SpotifyAuthState {
75
87
  /** The current Spotify access token, null if not authenticated */
76
88
  accessToken: string | null;
89
+ /** The current refresh token, null if not authenticated */
90
+ refreshToken: string | null;
91
+ /** The token expiration time in seconds, null if not authenticated */
92
+ expiresIn: number | null;
93
+ /** The token type, null if not authenticated */
94
+ tokenType: string | null;
95
+ /** The token scope, null if not authenticated */
96
+ scope: string | null;
97
+ }
98
+ /**
99
+ * Context for Spotify authentication state and actions
100
+ */
101
+ export interface SpotifyAuthContext {
102
+ /** The complete Spotify authentication state */
103
+ authState: SpotifyAuthState;
77
104
  /** Function to initiate Spotify authorization */
78
105
  authorize: (config: AuthorizeConfig) => Promise<void>;
79
106
  /** Whether authorization is in progress */
@@ -1 +1 @@
1
- {"version":3,"file":"SpotifyAuth.types.d.ts","sourceRoot":"","sources":["../src/SpotifyAuth.types.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,MAAM,aAAa,GACrB,oBAAoB,GACpB,yBAAyB,GACzB,wBAAwB,GACxB,6BAA6B,GAC7B,uBAAuB,GACvB,WAAW,GACX,oBAAoB,GACpB,kBAAkB,GAClB,qBAAqB,GACrB,mBAAmB,GACnB,4BAA4B,GAC5B,6BAA6B,GAC7B,iBAAiB,GACjB,6BAA6B,GAC7B,0BAA0B,GAC1B,mBAAmB,GACnB,2BAA2B,GAC3B,eAAe,GACf,QAAQ,CAAC;AAEb;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,uEAAuE;IACvE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gDAAgD;IAChD,KAAK,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,sCAAsC;IACtC,IAAI,EACA,qBAAqB,GACrB,eAAe,GACf,aAAa,GACb,qBAAqB,GACrB,cAAc,GACd,eAAe,CAAC;IACpB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,OAAO,EAAE;QACP,2DAA2D;QAC3D,UAAU,EAAE,MAAM,CAAC;QACnB,8CAA8C;QAC9C,WAAW,EAAE,OAAO,CAAC;QACrB,+CAA+C;QAC/C,KAAK,CAAC,EAAE;YACN,6BAA6B;YAC7B,IAAI,EAAE,OAAO,GAAG,aAAa,CAAC;YAC9B,0CAA0C;YAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,yDAAyD;YACzD,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,0DAA0D;YAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,wDAAwD;YACxD,aAAa,CAAC,EAAE,MAAM,CAAC;SACxB,CAAC;KACH,CAAC;CACH,CAAA;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,sCAAsC;IACtC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iDAAiD;IACjD,SAAS,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,2CAA2C;IAC3C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qDAAqD;IACrD,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAChC;AAED,eAAO,MAAM,0BAA0B,6CAKrC,CAAC;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,oBAAoB,KAAK,IAAI,CAAC;CAC7D;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;CACf"}
1
+ {"version":3,"file":"SpotifyAuth.types.d.ts","sourceRoot":"","sources":["../src/SpotifyAuth.types.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,12 @@
1
1
  import { createContext } from "react";
2
2
  export const SpotifyAuthContextInstance = createContext({
3
- accessToken: null,
3
+ authState: {
4
+ accessToken: null,
5
+ refreshToken: null,
6
+ expiresIn: null,
7
+ tokenType: null,
8
+ scope: null,
9
+ },
4
10
  authorize: async () => { },
5
11
  isAuthenticating: false,
6
12
  error: null,
@@ -1 +1 @@
1
- {"version":3,"file":"SpotifyAuth.types.js","sourceRoot":"","sources":["../src/SpotifyAuth.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAqHtC,MAAM,CAAC,MAAM,0BAA0B,GAAG,aAAa,CAAqB;IAC1E,WAAW,EAAE,IAAI;IACjB,SAAS,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;IACzB,gBAAgB,EAAE,KAAK;IACvB,KAAK,EAAE,IAAI;CACZ,CAAC,CAAC","sourcesContent":["import { createContext } from \"react\";\n\n/**\n * Available Spotify authorization scopes.\n * @see https://developer.spotify.com/documentation/general/guides/authorization/scopes/\n */\nexport type SpotifyScopes =\n | 'app-remote-control'\n | 'playlist-modify-private'\n | 'playlist-modify-public'\n | 'playlist-read-collaborative'\n | 'playlist-read-private'\n | 'streaming'\n | 'user-follow-modify'\n | 'user-follow-read'\n | 'user-library-modify'\n | 'user-library-read'\n | 'user-modify-playback-state'\n | 'user-read-currently-playing'\n | 'user-read-email'\n | 'user-read-playback-position'\n | 'user-read-playback-state'\n | 'user-read-private'\n | 'user-read-recently-played'\n | 'user-top-read'\n | 'openid';\n\n/**\n * Event data structure for Spotify authorization events\n */\nexport interface SpotifyAuthEvent {\n success: boolean;\n token: string | null;\n error?: SpotifyAuthError;\n}\n\n/**\n * Data returned from the Spotify authorization process\n */\nexport interface SpotifyAuthorizationData {\n /** Whether the authorization was successful */\n success: boolean;\n /** The access token if authorization was successful, null otherwise */\n token: string | null;\n /** Error information if authorization failed */\n error?: SpotifyAuthError;\n}\n\n/**\n * Possible error types that can occur during Spotify authentication\n */\nexport type SpotifyAuthError = {\n /** The type of error that occurred */\n type: \n | \"configuration_error\" // Missing or invalid configuration\n | \"network_error\" // Network-related issues\n | \"token_error\" // Issues with token exchange/refresh\n | \"authorization_error\" // User-facing authorization issues\n | \"server_error\" // Backend server issues\n | \"unknown_error\"; // Unexpected errors\n /** Human-readable error message */\n message: string;\n /** Additional error details */\n details: {\n /** Specific error code for more granular error handling */\n error_code: string;\n /** Whether the error can be recovered from */\n recoverable: boolean;\n /** Retry strategy information if applicable */\n retry?: {\n /** Type of retry strategy */\n type: \"fixed\" | \"exponential\";\n /** For fixed retry: number of attempts */\n attempts?: number;\n /** For fixed retry: delay between attempts in seconds */\n delay?: number;\n /** For exponential backoff: maximum number of attempts */\n max_attempts?: number;\n /** For exponential backoff: initial delay in seconds */\n initial_delay?: number;\n };\n };\n}\n\n/**\n * Configuration for the authorization request.\n * These are runtime options that can be changed between auth attempts.\n */\nexport interface AuthorizeConfig {\n /** Whether to show the auth dialog */\n showDialog?: boolean;\n /** Campaign identifier for attribution */\n campaign?: string;\n}\n\n/**\n * Props for the SpotifyAuthView component\n */\nexport interface SpotifyAuthViewProps {\n /** The name identifier for the auth view */\n name: string;\n}\n\n/**\n * Context for Spotify authentication state and actions\n */\nexport interface SpotifyAuthContext {\n /** The current Spotify access token, null if not authenticated */\n accessToken: string | null;\n /** Function to initiate Spotify authorization */\n authorize: (config: AuthorizeConfig) => Promise<void>;\n /** Whether authorization is in progress */\n isAuthenticating: boolean;\n /** Last error that occurred during authentication */\n error: SpotifyAuthError | null;\n}\n\nexport const SpotifyAuthContextInstance = createContext<SpotifyAuthContext>({\n accessToken: null,\n authorize: async () => {},\n isAuthenticating: false,\n error: null,\n});\n\nexport interface SpotifyAuthOptions {\n clientId: string;\n redirectUrl: string;\n showDialog?: boolean;\n tokenRefreshFunction?: (data: SpotifyTokenResponse) => void;\n}\n\n/**\n * Response data from Spotify token endpoint\n */\nexport interface SpotifyTokenResponse {\n access_token: string;\n token_type: string;\n expires_in: number;\n refresh_token?: string;\n scope: string;\n}\n"]}
1
+ {"version":3,"file":"SpotifyAuth.types.js","sourceRoot":"","sources":["../src/SpotifyAuth.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAiJtC,MAAM,CAAC,MAAM,0BAA0B,GAAG,aAAa,CAAqB;IAC1E,SAAS,EAAE;QACT,WAAW,EAAE,IAAI;QACjB,YAAY,EAAE,IAAI;QAClB,SAAS,EAAE,IAAI;QACf,SAAS,EAAE,IAAI;QACf,KAAK,EAAE,IAAI;KACZ;IACD,SAAS,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;IACzB,gBAAgB,EAAE,KAAK;IACvB,KAAK,EAAE,IAAI;CACZ,CAAC,CAAC","sourcesContent":["import { createContext } from \"react\";\n\n/**\n * Available Spotify authorization scopes.\n * @see https://developer.spotify.com/documentation/general/guides/authorization/scopes/\n */\nexport type SpotifyScopes =\n | 'app-remote-control'\n | 'playlist-modify-private'\n | 'playlist-modify-public'\n | 'playlist-read-collaborative'\n | 'playlist-read-private'\n | 'streaming'\n | 'user-follow-modify'\n | 'user-follow-read'\n | 'user-library-modify'\n | 'user-library-read'\n | 'user-modify-playback-state'\n | 'user-read-currently-playing'\n | 'user-read-email'\n | 'user-read-playback-position'\n | 'user-read-playback-state'\n | 'user-read-private'\n | 'user-read-recently-played'\n | 'user-top-read'\n | 'openid';\n\n/**\n * Event data structure for Spotify authorization events\n */\nexport interface SpotifyAuthEvent {\n success: boolean;\n token: string | null;\n refreshToken: string | null;\n expiresIn: number | null;\n tokenType: string | null;\n scope: string | null;\n error?: SpotifyAuthError;\n}\n\n/**\n * Data returned from the Spotify authorization process\n */\nexport interface SpotifyAuthorizationData {\n /** Whether the authorization was successful */\n success: boolean;\n /** The access token if authorization was successful, null otherwise */\n token: string | null;\n /** The refresh token if authorization was successful, null otherwise */\n refreshToken: string | null;\n /** The token expiration time in seconds if authorization was successful, null otherwise */\n expiresIn: number | null;\n /** The token type (e.g. \"Bearer\") if authorization was successful, null otherwise */\n tokenType: string | null;\n /** The granted scopes if authorization was successful, null otherwise */\n scope: string | null;\n /** Error information if authorization failed */\n error?: SpotifyAuthError;\n}\n\n/**\n * Possible error types that can occur during Spotify authentication\n */\nexport type SpotifyAuthError = {\n /** The type of error that occurred */\n type: \n | \"configuration_error\" // Missing or invalid configuration\n | \"network_error\" // Network-related issues\n | \"token_error\" // Issues with token exchange/refresh\n | \"authorization_error\" // User-facing authorization issues\n | \"server_error\" // Backend server issues\n | \"unknown_error\"; // Unexpected errors\n /** Human-readable error message */\n message: string;\n /** Additional error details */\n details: {\n /** Specific error code for more granular error handling */\n error_code: string;\n /** Whether the error can be recovered from */\n recoverable: boolean;\n /** Retry strategy information if applicable */\n retry?: {\n /** Type of retry strategy */\n type: \"fixed\" | \"exponential\";\n /** For fixed retry: number of attempts */\n attempts?: number;\n /** For fixed retry: delay between attempts in seconds */\n delay?: number;\n /** For exponential backoff: maximum number of attempts */\n max_attempts?: number;\n /** For exponential backoff: initial delay in seconds */\n initial_delay?: number;\n };\n };\n}\n\n/**\n * Configuration for the authorization request.\n * These are runtime options that can be changed between auth attempts.\n */\nexport interface AuthorizeConfig {\n /** Whether to show the auth dialog */\n showDialog?: boolean;\n /** Campaign identifier for attribution */\n campaign?: string;\n}\n\n/**\n * Props for the SpotifyAuthView component\n */\nexport interface SpotifyAuthViewProps {\n /** The name identifier for the auth view */\n name: string;\n}\n\n/**\n * Spotify authentication state containing all token-related information\n */\nexport interface SpotifyAuthState {\n /** The current Spotify access token, null if not authenticated */\n accessToken: string | null;\n /** The current refresh token, null if not authenticated */\n refreshToken: string | null;\n /** The token expiration time in seconds, null if not authenticated */\n expiresIn: number | null;\n /** The token type, null if not authenticated */\n tokenType: string | null;\n /** The token scope, null if not authenticated */\n scope: string | null;\n}\n\n/**\n * Context for Spotify authentication state and actions\n */\nexport interface SpotifyAuthContext {\n /** The complete Spotify authentication state */\n authState: SpotifyAuthState;\n /** Function to initiate Spotify authorization */\n authorize: (config: AuthorizeConfig) => Promise<void>;\n /** Whether authorization is in progress */\n isAuthenticating: boolean;\n /** Last error that occurred during authentication */\n error: SpotifyAuthError | null;\n}\n\nexport const SpotifyAuthContextInstance = createContext<SpotifyAuthContext>({\n authState: {\n accessToken: null,\n refreshToken: null,\n expiresIn: null,\n tokenType: null,\n scope: null,\n },\n authorize: async () => {},\n isAuthenticating: false,\n error: null,\n});\n\nexport interface SpotifyAuthOptions {\n clientId: string;\n redirectUrl: string;\n showDialog?: boolean;\n tokenRefreshFunction?: (data: SpotifyTokenResponse) => void;\n}\n\n/**\n * Response data from Spotify token endpoint\n */\nexport interface SpotifyTokenResponse {\n access_token: string;\n token_type: string;\n expires_in: number;\n refresh_token?: string;\n scope: string;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAuD,MAAM,OAAO,CAAC;AAE5E,OAAO,EAEL,kBAAkB,EAElB,KAAK,eAAe,EAErB,MAAM,qBAAqB,CAAC;AAe7B;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAGvD;AAED,UAAU,wBAAwB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,GACT,EAAE,wBAAwB,GAAG,GAAG,CAAC,OAAO,CA4DxC;AAED,wBAAgB,cAAc,IAAI,kBAAkB,CAMnD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAuD,MAAM,OAAO,CAAC;AAE5E,OAAO,EAEL,kBAAkB,EAGlB,KAAK,eAAe,EAErB,MAAM,qBAAqB,CAAC;AAe7B;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAGvD;AAED,UAAU,wBAAwB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,GACT,EAAE,wBAAwB,GAAG,GAAG,CAAC,OAAO,CAwFxC;AAED,wBAAgB,cAAc,IAAI,kBAAkB,CAMnD"}
package/build/index.js CHANGED
@@ -17,7 +17,13 @@ export function authorize(config) {
17
17
  SpotifyAuthModule.authorize(config);
18
18
  }
19
19
  export function SpotifyAuthProvider({ children, }) {
20
- const [token, setToken] = useState(null);
20
+ const [authState, setAuthState] = useState({
21
+ accessToken: null,
22
+ refreshToken: null,
23
+ expiresIn: null,
24
+ tokenType: null,
25
+ scope: null,
26
+ });
21
27
  const [isAuthenticating, setIsAuthenticating] = useState(false);
22
28
  const [error, setError] = useState(null);
23
29
  const authorize = useCallback(async (config) => {
@@ -51,21 +57,41 @@ export function SpotifyAuthProvider({ children, }) {
51
57
  useEffect(() => {
52
58
  console.log('[SpotifyAuth] Setting up auth listener');
53
59
  const subscription = addAuthListener((data) => {
54
- console.log('[SpotifyAuth] Received auth event:', data.token ? 'Token received' : 'No token');
55
- setToken(data.token);
56
- setIsAuthenticating(false);
57
- if (data.error) {
60
+ console.log('[SpotifyAuth] Received auth event:', JSON.stringify(data));
61
+ // Only update state if we receive a token
62
+ if (data.token) {
63
+ setAuthState({
64
+ accessToken: data.token,
65
+ refreshToken: data.refreshToken,
66
+ expiresIn: data.expiresIn,
67
+ tokenType: data.tokenType,
68
+ scope: data.scope,
69
+ });
70
+ setIsAuthenticating(false);
71
+ setError(null);
72
+ }
73
+ // Only set error if we have no token and there's an error
74
+ if (!data.token && data.error) {
58
75
  console.error('[SpotifyAuth] Auth event error:', data.error);
59
- console.error('Spotify auth error:', data.error);
76
+ setAuthState({
77
+ accessToken: null,
78
+ refreshToken: null,
79
+ expiresIn: null,
80
+ tokenType: null,
81
+ scope: null,
82
+ });
60
83
  setError(data.error);
61
- }
62
- else {
63
- setError(null);
84
+ setIsAuthenticating(false);
64
85
  }
65
86
  });
66
87
  return () => subscription.remove();
67
88
  }, []);
68
- return (<SpotifyAuthContextInstance.Provider value={{ accessToken: token, authorize, isAuthenticating, error }}>
89
+ return (<SpotifyAuthContextInstance.Provider value={{
90
+ authState,
91
+ authorize,
92
+ isAuthenticating,
93
+ error
94
+ }}>
69
95
  {children}
70
96
  </SpotifyAuthContextInstance.Provider>);
71
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from \"expo-modules-core\";\nimport React, { useContext, useEffect, useState, useCallback } from \"react\";\n\nimport {\n SpotifyAuthorizationData,\n SpotifyAuthContext,\n SpotifyAuthContextInstance,\n type AuthorizeConfig,\n type SpotifyAuthError,\n} from \"./SpotifyAuth.types\";\nimport SpotifyAuthModule from \"./SpotifyAuthModule\";\n\n// First define the event name as a string literal type\ntype SpotifyAuthEventName = \"onSpotifyAuth\"; // This should match SpotifyAuthModule.AuthEventName\n\n// Create a properly typed emitter\nconst emitter = new EventEmitter(SpotifyAuthModule);\n\nfunction addAuthListener(listener: (data: SpotifyAuthorizationData) => void) {\n // Assert the event name is of the correct type\n const eventName = SpotifyAuthModule.AuthEventName as SpotifyAuthEventName;\n return emitter.addListener(eventName, listener);\n}\n\n/**\n * Prompts the user to log in to Spotify and authorize your application.\n */\nexport function authorize(config: AuthorizeConfig): void {\n console.log('[SpotifyAuth] Initiating authorization request');\n SpotifyAuthModule.authorize(config);\n}\n\ninterface SpotifyAuthProviderProps {\n children: React.ReactNode;\n}\n\nexport function SpotifyAuthProvider({\n children,\n}: SpotifyAuthProviderProps): JSX.Element {\n const [token, setToken] = useState<string | null>(null);\n const [isAuthenticating, setIsAuthenticating] = useState(false);\n const [error, setError] = useState<SpotifyAuthError | null>(null);\n\n const authorize = useCallback(\n async (config: AuthorizeConfig): Promise<void> => {\n try {\n console.log('[SpotifyAuth] Starting authorization process in provider');\n console.log('[SpotifyAuth] Authorization config:', JSON.stringify(config));\n setIsAuthenticating(true);\n setError(null);\n await SpotifyAuthModule.authorize(config);\n } catch (err) {\n console.error('[SpotifyAuth] Authorization error:', err);\n // Handle structured errors from the native layer\n if (err && typeof err === 'object' && 'type' in err) {\n setError(err as SpotifyAuthError);\n } else {\n // Create a generic error structure for unknown errors\n setError({\n type: 'unknown_error',\n message: err instanceof Error ? err.message : 'Authorization failed',\n details: {\n error_code: 'unknown',\n recoverable: false\n }\n });\n }\n throw err;\n }\n },\n [],\n );\n\n useEffect(() => {\n console.log('[SpotifyAuth] Setting up auth listener');\n const subscription = addAuthListener((data) => {\n console.log('[SpotifyAuth] Received auth event:', data.token ? 'Token received' : 'No token');\n setToken(data.token);\n setIsAuthenticating(false);\n\n if (data.error) {\n console.error('[SpotifyAuth] Auth event error:', data.error);\n console.error('Spotify auth error:', data.error);\n setError(data.error);\n } else {\n setError(null);\n }\n });\n return () => subscription.remove();\n }, []);\n\n return (\n <SpotifyAuthContextInstance.Provider\n value={{ accessToken: token, authorize, isAuthenticating, error }}\n >\n {children}\n </SpotifyAuthContextInstance.Provider>\n );\n}\n\nexport function useSpotifyAuth(): SpotifyAuthContext {\n const context = useContext(SpotifyAuthContextInstance);\n if (!context) {\n throw new Error(\"useSpotifyAuth must be used within a SpotifyAuthProvider\");\n }\n return context;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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","sourcesContent":["import { EventEmitter } from \"expo-modules-core\";\nimport React, { useContext, useEffect, useState, useCallback } from \"react\";\n\nimport {\n SpotifyAuthorizationData,\n SpotifyAuthContext,\n SpotifyAuthContextInstance,\n SpotifyAuthState,\n type AuthorizeConfig,\n type SpotifyAuthError,\n} from \"./SpotifyAuth.types\";\nimport SpotifyAuthModule from \"./SpotifyAuthModule\";\n\n// First define the event name as a string literal type\ntype SpotifyAuthEventName = \"onSpotifyAuth\"; // This should match SpotifyAuthModule.AuthEventName\n\n// Create a properly typed emitter\nconst emitter = new EventEmitter(SpotifyAuthModule);\n\nfunction addAuthListener(listener: (data: SpotifyAuthorizationData) => void) {\n // Assert the event name is of the correct type\n const eventName = SpotifyAuthModule.AuthEventName as SpotifyAuthEventName;\n return emitter.addListener(eventName, listener);\n}\n\n/**\n * Prompts the user to log in to Spotify and authorize your application.\n */\nexport function authorize(config: AuthorizeConfig): void {\n console.log('[SpotifyAuth] Initiating authorization request');\n SpotifyAuthModule.authorize(config);\n}\n\ninterface SpotifyAuthProviderProps {\n children: React.ReactNode;\n}\n\nexport function SpotifyAuthProvider({\n children,\n}: SpotifyAuthProviderProps): JSX.Element {\n const [authState, setAuthState] = useState<SpotifyAuthState>({\n accessToken: null,\n refreshToken: null,\n expiresIn: null,\n tokenType: null,\n scope: null,\n });\n const [isAuthenticating, setIsAuthenticating] = useState(false);\n const [error, setError] = useState<SpotifyAuthError | null>(null);\n\n const authorize = useCallback(\n async (config: AuthorizeConfig): Promise<void> => {\n try {\n console.log('[SpotifyAuth] Starting authorization process in provider');\n console.log('[SpotifyAuth] Authorization config:', JSON.stringify(config));\n setIsAuthenticating(true);\n setError(null);\n await SpotifyAuthModule.authorize(config);\n } catch (err) {\n console.error('[SpotifyAuth] Authorization error:', err);\n // Handle structured errors from the native layer\n if (err && typeof err === 'object' && 'type' in err) {\n setError(err as SpotifyAuthError);\n } else {\n // Create a generic error structure for unknown errors\n setError({\n type: 'unknown_error',\n message: err instanceof Error ? err.message : 'Authorization failed',\n details: {\n error_code: 'unknown',\n recoverable: false\n }\n });\n }\n throw err;\n }\n },\n [],\n );\n\n useEffect(() => {\n console.log('[SpotifyAuth] Setting up auth listener');\n const subscription = addAuthListener((data) => {\n console.log('[SpotifyAuth] Received auth event:', JSON.stringify(data));\n\n // Only update state if we receive a token\n if (data.token) {\n setAuthState({\n accessToken: data.token,\n refreshToken: data.refreshToken,\n expiresIn: data.expiresIn,\n tokenType: data.tokenType,\n scope: data.scope,\n });\n setIsAuthenticating(false);\n setError(null);\n }\n\n // Only set error if we have no token and there's an error\n if (!data.token && data.error) {\n console.error('[SpotifyAuth] Auth event error:', data.error);\n setAuthState({\n accessToken: null,\n refreshToken: null,\n expiresIn: null,\n tokenType: null,\n scope: null,\n });\n setError(data.error);\n setIsAuthenticating(false);\n }\n });\n return () => subscription.remove();\n }, []);\n\n return (\n <SpotifyAuthContextInstance.Provider\n value={{\n authState,\n authorize,\n isAuthenticating,\n error\n }}\n >\n {children}\n </SpotifyAuthContextInstance.Provider>\n );\n}\n\nexport function useSpotifyAuth(): SpotifyAuthContext {\n const context = useContext(SpotifyAuthContextInstance);\n if (!context) {\n throw new Error(\"useSpotifyAuth must be used within a SpotifyAuthProvider\");\n }\n return context;\n}\n"]}
@@ -7,15 +7,17 @@ struct SpotifySessionData {
7
7
  let accessToken: String
8
8
  let refreshToken: String
9
9
  let expirationDate: Date
10
+ let scope: String?
10
11
 
11
12
  var isExpired: Bool {
12
13
  return Date() >= expirationDate
13
14
  }
14
15
 
15
- init(accessToken: String, refreshToken: String, expirationDate: Date) {
16
+ init(accessToken: String, refreshToken: String, expirationDate: Date, scope: String?) {
16
17
  self.accessToken = accessToken
17
18
  self.refreshToken = refreshToken
18
19
  self.expirationDate = expirationDate
20
+ self.scope = scope
19
21
  }
20
22
 
21
23
  /// Initialize from an SPTSession (from app‑switch flow)
@@ -24,6 +26,7 @@ struct SpotifySessionData {
24
26
  self.accessToken = session.accessToken
25
27
  self.refreshToken = session.refreshToken
26
28
  self.expirationDate = session.expirationDate
29
+ self.scope = session.scope?.scopesToStringArray().joined(separator: " ")
27
30
  }
28
31
  }
29
32
 
@@ -238,7 +241,8 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
238
241
 
239
242
  private func securelyStoreToken(_ session: SpotifySessionData) {
240
243
  // Pass token back to JS.
241
- module?.onAccessTokenObtained(session.accessToken)
244
+ let expiresIn = session.expirationDate.timeIntervalSinceNow
245
+ module?.onAccessTokenObtained(session.accessToken, refreshToken: session.refreshToken, expiresIn: expiresIn, scope: session.scope, tokenType: "Bearer")
242
246
 
243
247
  let refreshToken = session.refreshToken
244
248
  if !refreshToken.isEmpty {
@@ -298,40 +302,74 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
298
302
  request.httpMethod = "POST"
299
303
  request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
300
304
 
301
- let params: [String: String]
302
- do {
303
- params = [
304
- "grant_type": "refresh_token",
305
- "refresh_token": currentSession.refreshToken,
306
- "client_id": try self.clientID
307
- ]
308
- } catch {
309
- handleError(error, context: "token_refresh")
310
- return
311
- }
312
-
305
+ let params = ["refresh_token": currentSession.refreshToken]
313
306
  let bodyString = params.map { "\($0)=\($1)" }.joined(separator: "&")
314
307
  request.httpBody = bodyString.data(using: .utf8)
315
308
 
316
- let task = URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
309
+ let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
317
310
  if let error = error {
318
- self?.handleError(error, context: "token_refresh")
311
+ self?.handleError(SpotifyAuthError.networkError(error.localizedDescription), context: "token_refresh")
312
+ return
313
+ }
314
+
315
+ guard let httpResponse = response as? HTTPURLResponse else {
316
+ self?.handleError(SpotifyAuthError.networkError("Invalid response type"), context: "token_refresh")
317
+ return
318
+ }
319
+
320
+ // Check HTTP status code
321
+ guard (200...299).contains(httpResponse.statusCode) else {
322
+ let errorMessage: String
323
+ if let data = data, let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
324
+ let errorDescription = errorJson["error_description"] as? String {
325
+ errorMessage = errorDescription
326
+ } else {
327
+ errorMessage = "Server returned status code \(httpResponse.statusCode)"
328
+ }
329
+ self?.handleError(SpotifyAuthError.networkError(errorMessage), context: "token_refresh")
319
330
  return
320
331
  }
321
- guard let data = data,
322
- let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
323
- let accessToken = json["access_token"] as? String,
324
- let expiresIn = json["expires_in"] as? TimeInterval else {
325
- self?.handleError(SpotifyAuthError.tokenError("Invalid token refresh response"), context: "token_refresh")
332
+
333
+ guard let data = data else {
334
+ self?.handleError(SpotifyAuthError.tokenError("No data received"), context: "token_refresh")
326
335
  return
327
336
  }
328
337
 
329
- let newRefreshToken = (json["refresh_token"] as? String) ?? currentSession.refreshToken
330
- let expirationDate = Date(timeIntervalSinceNow: expiresIn)
331
- let newSession = SpotifySessionData(accessToken: accessToken, refreshToken: newRefreshToken, expirationDate: expirationDate)
332
- DispatchQueue.main.async {
333
- self?.currentSession = newSession
334
- self?.module?.onAccessTokenObtained(accessToken)
338
+ do {
339
+ let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
340
+ guard let json = json else {
341
+ throw SpotifyAuthError.tokenError("Invalid JSON response")
342
+ }
343
+
344
+ // Extract and validate required fields
345
+ guard let accessToken = json["access_token"] as? String else {
346
+ throw SpotifyAuthError.tokenError("Missing access_token in response")
347
+ }
348
+
349
+ guard let expiresInString = json["expires_in"] as? String,
350
+ let expiresIn = TimeInterval(expiresInString) else {
351
+ throw SpotifyAuthError.tokenError("Invalid or missing expires_in in response")
352
+ }
353
+
354
+ guard let tokenType = json["token_type"] as? String,
355
+ tokenType.lowercased() == "bearer" else {
356
+ throw SpotifyAuthError.tokenError("Invalid or missing token_type in response")
357
+ }
358
+
359
+ // Optional field
360
+ let scope = json["scope"] as? String
361
+
362
+ // Keep the existing refresh token since server doesn't send a new one
363
+ let refreshToken = currentSession.refreshToken
364
+ let expirationDate = Date(timeIntervalSinceNow: expiresIn)
365
+ let newSession = SpotifySessionData(accessToken: accessToken, refreshToken: refreshToken, expirationDate: expirationDate, scope: scope)
366
+
367
+ DispatchQueue.main.async {
368
+ self?.currentSession = newSession
369
+ self?.module?.onAccessTokenObtained(accessToken, refreshToken: refreshToken, expiresIn: expiresIn, scope: scope, tokenType: tokenType)
370
+ }
371
+ } catch {
372
+ self?.handleError(error, context: "token_refresh")
335
373
  }
336
374
  }
337
375
  task.resume()
@@ -489,10 +527,8 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
489
527
  let params: [String: String]
490
528
  do {
491
529
  params = [
492
- "grant_type": "authorization_code",
493
530
  "code": code,
494
- "redirect_uri": try self.redirectURL.absoluteString,
495
- "client_id": try self.clientID
531
+ "redirect_uri": try self.redirectURL.absoluteString
496
532
  ]
497
533
  } catch {
498
534
  handleError(error, context: "token_exchange")
@@ -533,18 +569,37 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
533
569
 
534
570
  do {
535
571
  let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
536
- guard let json = json,
537
- let accessToken = json["access_token"] as? String,
538
- let refreshToken = json["refresh_token"] as? String,
539
- let expiresIn = json["expires_in"] as? TimeInterval else {
540
- throw SpotifyAuthError.tokenError("Invalid token response format")
572
+ guard let json = json else {
573
+ throw SpotifyAuthError.tokenError("Invalid JSON response")
574
+ }
575
+
576
+ // Extract and validate required fields
577
+ guard let accessToken = json["access_token"] as? String else {
578
+ throw SpotifyAuthError.tokenError("Missing access_token in response")
579
+ }
580
+
581
+ guard let refreshToken = json["refresh_token"] as? String else {
582
+ throw SpotifyAuthError.tokenError("Missing refresh_token in response")
541
583
  }
542
584
 
585
+ guard let expiresInString = json["expires_in"] as? String,
586
+ let expiresIn = TimeInterval(expiresInString) else {
587
+ throw SpotifyAuthError.tokenError("Invalid or missing expires_in in response")
588
+ }
589
+
590
+ guard let tokenType = json["token_type"] as? String,
591
+ tokenType.lowercased() == "bearer" else {
592
+ throw SpotifyAuthError.tokenError("Invalid or missing token_type in response")
593
+ }
594
+
595
+ // Optional field
596
+ let scope = json["scope"] as? String
597
+
543
598
  let expirationDate = Date(timeIntervalSinceNow: expiresIn)
544
- let sessionData = SpotifySessionData(accessToken: accessToken, refreshToken: refreshToken, expirationDate: expirationDate)
599
+ let sessionData = SpotifySessionData(accessToken: accessToken, refreshToken: refreshToken, expirationDate: expirationDate, scope: scope)
545
600
  DispatchQueue.main.async {
546
601
  self?.currentSession = sessionData
547
- self?.module?.onAccessTokenObtained(accessToken)
602
+ self?.module?.onAccessTokenObtained(accessToken, refreshToken: refreshToken, expiresIn: expiresIn, scope: scope, tokenType: tokenType)
548
603
  }
549
604
  } catch {
550
605
  self?.handleError(error, context: "token_exchange")
@@ -26,10 +26,11 @@ struct AuthorizeConfig: Record {
26
26
 
27
27
  // Define a private enum for mapping Spotify SDK error codes.
28
28
  // (The raw values here are examples; adjust them to match your SDK's definitions.)
29
- private enum SPTErrorCode: Int {
30
- case authorizationFailed = 100
31
- case renewSessionFailed = 101
32
- case jsonFailed = 102
29
+ private enum SPTErrorCode: UInt {
30
+ case unknown = 0
31
+ case authorizationFailed = 1
32
+ case renewSessionFailed = 2
33
+ case jsonFailed = 3
33
34
  }
34
35
 
35
36
  public class SpotifyAuthModule: Module {
@@ -78,7 +79,7 @@ public class SpotifyAuthModule: Module {
78
79
  View(SpotifyOAuthView.self) {
79
80
  Events(spotifyAuthorizationEventName)
80
81
 
81
- Prop("name") { (view: SpotifyOAuthView, _: String) in
82
+ Prop("name") { (_: SpotifyOAuthView, _: String) in
82
83
  DispatchQueue.main.async {
83
84
  secureLog("View prop updated")
84
85
  }
@@ -87,14 +88,13 @@ public class SpotifyAuthModule: Module {
87
88
  }
88
89
 
89
90
  private func sanitizeErrorMessage(_ message: String) -> String {
90
- // Remove potential sensitive data from error messages.
91
+ // Only redact actual sensitive values, not general terms
91
92
  let sensitivePatterns = [
92
- "(?i)client[_-]?id",
93
- "(?i)token",
94
- "(?i)secret",
95
- "(?i)key",
96
- "(?i)auth",
97
- "(?i)password"
93
+ "(?i)client[_-]?id=[^&\\s]+",
94
+ "(?i)access_token=[^&\\s]+",
95
+ "(?i)refresh_token=[^&\\s]+",
96
+ "(?i)secret=[^&\\s]+",
97
+ "(?i)api[_-]?key=[^&\\s]+"
98
98
  ]
99
99
 
100
100
  var sanitized = message
@@ -103,7 +103,7 @@ public class SpotifyAuthModule: Module {
103
103
  sanitized = regex.stringByReplacingMatches(
104
104
  in: sanitized,
105
105
  range: NSRange(sanitized.startIndex..., in: sanitized),
106
- withTemplate: "[REDACTED]"
106
+ withTemplate: "$1[REDACTED]"
107
107
  )
108
108
  }
109
109
  }
@@ -111,11 +111,15 @@ public class SpotifyAuthModule: Module {
111
111
  }
112
112
 
113
113
  @objc
114
- public func onAccessTokenObtained(_ token: String) {
114
+ public func onAccessTokenObtained(_ token: String, refreshToken: String, expiresIn: TimeInterval, scope: String?, tokenType: String) {
115
115
  secureLog("Access token obtained", sensitive: true)
116
116
  let eventData: [String: Any] = [
117
117
  "success": true,
118
118
  "token": token,
119
+ "refreshToken": refreshToken,
120
+ "expiresIn": expiresIn,
121
+ "tokenType": tokenType,
122
+ "scope": scope as Any,
119
123
  "error": NSNull() // Use NSNull() instead of nil.
120
124
  ]
121
125
  sendEvent(spotifyAuthorizationEventName, eventData)
@@ -126,30 +130,42 @@ public class SpotifyAuthModule: Module {
126
130
  secureLog("User signed out")
127
131
  let eventData: [String: Any] = [
128
132
  "success": true,
129
- "token": NSNull(), // Use NSNull() instead of nil.
130
- "error": NSNull() // Use NSNull() instead of nil.
133
+ "token": NSNull(),
134
+ "refreshToken": NSNull(),
135
+ "expiresIn": NSNull(),
136
+ "tokenType": NSNull(),
137
+ "scope": NSNull(),
138
+ "error": NSNull()
131
139
  ]
132
140
  sendEvent(spotifyAuthorizationEventName, eventData)
133
141
  }
134
142
 
135
143
  @objc
136
144
  public func onAuthorizationError(_ error: Error) {
145
+ // Skip sending error events for expected state transitions
146
+ if let spotifyError = error as? SpotifyAuthError,
147
+ case .sessionError(let message) = spotifyError,
148
+ message.contains("authentication process") ||
149
+ message.contains("token exchange") {
150
+ // This is likely a state transition, not an error
151
+ secureLog("Auth state transition: \(message)")
152
+ return
153
+ }
154
+
137
155
  let errorData: [String: Any]
138
156
 
139
157
  if let spotifyError = error as? SpotifyAuthError {
140
- // Map domain error to a structured format
141
158
  errorData = mapSpotifyError(spotifyError)
142
159
  } else if let sptError = error as? SPTError {
143
- // Map Spotify SDK errors
144
160
  errorData = mapSPTError(sptError)
145
161
  } else {
146
- // Map unknown errors
147
162
  errorData = [
148
163
  "type": "unknown_error",
149
164
  "message": sanitizeErrorMessage(error.localizedDescription),
150
165
  "details": [
151
166
  "error_code": "unknown",
152
- "recoverable": false
167
+ "recoverable": false,
168
+ "error_type": String(describing: type(of: error))
153
169
  ]
154
170
  ]
155
171
  }
@@ -159,6 +175,10 @@ public class SpotifyAuthModule: Module {
159
175
  let eventData: [String: Any] = [
160
176
  "success": false,
161
177
  "token": NSNull(),
178
+ "refreshToken": NSNull(),
179
+ "expiresIn": NSNull(),
180
+ "tokenType": NSNull(),
181
+ "scope": NSNull(),
162
182
  "error": errorData
163
183
  ]
164
184
  sendEvent(spotifyAuthorizationEventName, eventData)
@@ -198,21 +218,30 @@ public class SpotifyAuthModule: Module {
198
218
 
199
219
  private func mapSPTError(_ error: SPTError) -> [String: Any] {
200
220
  let message = sanitizeErrorMessage(error.localizedDescription)
201
- let details: [String: Any] = [
221
+ var details: [String: Any] = [
202
222
  "error_code": error.code,
203
223
  "recoverable": false
204
224
  ]
205
225
 
226
+ // Add underlying error info if available
227
+ if let underlying = error.userInfo[NSUnderlyingErrorKey] as? Error {
228
+ details["underlying_error"] = underlying.localizedDescription
229
+ }
230
+
206
231
  let type: String
207
- switch error.code {
208
- case SPTErrorCode.authorizationFailed.rawValue:
232
+ switch SPTErrorCode(rawValue: UInt(error.code)) {
233
+ case .authorizationFailed:
209
234
  type = "authorization_error"
210
- case SPTErrorCode.renewSessionFailed.rawValue:
211
- type = "token_error"
212
- case SPTErrorCode.jsonFailed.rawValue:
235
+ details["recoverable"] = true // Auth failures are usually recoverable
236
+ case .renewSessionFailed:
237
+ type = "session_error" // Changed from token_error to be more specific
238
+ details["recoverable"] = true
239
+ case .jsonFailed:
213
240
  type = "server_error"
214
- default:
241
+ details["recoverable"] = false
242
+ case .unknown, .none:
215
243
  type = "unknown_error"
244
+ details["recoverable"] = false
216
245
  }
217
246
 
218
247
  return [
@@ -231,11 +260,16 @@ public class SpotifyAuthModule: Module {
231
260
  case .tokenError:
232
261
  return ("token_error", "token_invalid")
233
262
  case .sessionError:
234
- return ("authorization_error", "session_error")
263
+ return ("session_error", "session_error") // Changed from authorization_error
235
264
  case .networkError:
236
265
  return ("network_error", "network_failed")
237
- case .recoverable:
238
- return ("authorization_error", "recoverable_error")
266
+ case .recoverable(let baseError, _):
267
+ // Use the base error type but mark as recoverable in the details
268
+ if let spotifyError = baseError as? SpotifyAuthError {
269
+ let (type, code) = classifySpotifyError(spotifyError)
270
+ return (type, "recoverable_\(code)")
271
+ }
272
+ return ("recoverable_error", "recoverable_unknown")
239
273
  }
240
274
  }
241
275
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superfan-app/spotify-auth",
3
- "version": "0.1.54",
3
+ "version": "0.1.55",
4
4
  "description": "Spotify OAuth module for Expo",
5
5
  "main": "src/index.tsx",
6
6
  "types": "build/index.d.ts",
@@ -31,6 +31,10 @@ export type SpotifyScopes =
31
31
  export interface SpotifyAuthEvent {
32
32
  success: boolean;
33
33
  token: string | null;
34
+ refreshToken: string | null;
35
+ expiresIn: number | null;
36
+ tokenType: string | null;
37
+ scope: string | null;
34
38
  error?: SpotifyAuthError;
35
39
  }
36
40
 
@@ -42,6 +46,14 @@ export interface SpotifyAuthorizationData {
42
46
  success: boolean;
43
47
  /** The access token if authorization was successful, null otherwise */
44
48
  token: string | null;
49
+ /** The refresh token if authorization was successful, null otherwise */
50
+ refreshToken: string | null;
51
+ /** The token expiration time in seconds if authorization was successful, null otherwise */
52
+ expiresIn: number | null;
53
+ /** The token type (e.g. "Bearer") if authorization was successful, null otherwise */
54
+ tokenType: string | null;
55
+ /** The granted scopes if authorization was successful, null otherwise */
56
+ scope: string | null;
45
57
  /** Error information if authorization failed */
46
58
  error?: SpotifyAuthError;
47
59
  }
@@ -102,11 +114,27 @@ export interface SpotifyAuthViewProps {
102
114
  }
103
115
 
104
116
  /**
105
- * Context for Spotify authentication state and actions
117
+ * Spotify authentication state containing all token-related information
106
118
  */
107
- export interface SpotifyAuthContext {
119
+ export interface SpotifyAuthState {
108
120
  /** The current Spotify access token, null if not authenticated */
109
121
  accessToken: string | null;
122
+ /** The current refresh token, null if not authenticated */
123
+ refreshToken: string | null;
124
+ /** The token expiration time in seconds, null if not authenticated */
125
+ expiresIn: number | null;
126
+ /** The token type, null if not authenticated */
127
+ tokenType: string | null;
128
+ /** The token scope, null if not authenticated */
129
+ scope: string | null;
130
+ }
131
+
132
+ /**
133
+ * Context for Spotify authentication state and actions
134
+ */
135
+ export interface SpotifyAuthContext {
136
+ /** The complete Spotify authentication state */
137
+ authState: SpotifyAuthState;
110
138
  /** Function to initiate Spotify authorization */
111
139
  authorize: (config: AuthorizeConfig) => Promise<void>;
112
140
  /** Whether authorization is in progress */
@@ -116,7 +144,13 @@ export interface SpotifyAuthContext {
116
144
  }
117
145
 
118
146
  export const SpotifyAuthContextInstance = createContext<SpotifyAuthContext>({
119
- accessToken: null,
147
+ authState: {
148
+ accessToken: null,
149
+ refreshToken: null,
150
+ expiresIn: null,
151
+ tokenType: null,
152
+ scope: null,
153
+ },
120
154
  authorize: async () => {},
121
155
  isAuthenticating: false,
122
156
  error: null,
package/src/index.tsx CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  SpotifyAuthorizationData,
6
6
  SpotifyAuthContext,
7
7
  SpotifyAuthContextInstance,
8
+ SpotifyAuthState,
8
9
  type AuthorizeConfig,
9
10
  type SpotifyAuthError,
10
11
  } from "./SpotifyAuth.types";
@@ -37,7 +38,13 @@ interface SpotifyAuthProviderProps {
37
38
  export function SpotifyAuthProvider({
38
39
  children,
39
40
  }: SpotifyAuthProviderProps): JSX.Element {
40
- const [token, setToken] = useState<string | null>(null);
41
+ const [authState, setAuthState] = useState<SpotifyAuthState>({
42
+ accessToken: null,
43
+ refreshToken: null,
44
+ expiresIn: null,
45
+ tokenType: null,
46
+ scope: null,
47
+ });
41
48
  const [isAuthenticating, setIsAuthenticating] = useState(false);
42
49
  const [error, setError] = useState<SpotifyAuthError | null>(null);
43
50
 
@@ -74,16 +81,33 @@ export function SpotifyAuthProvider({
74
81
  useEffect(() => {
75
82
  console.log('[SpotifyAuth] Setting up auth listener');
76
83
  const subscription = addAuthListener((data) => {
77
- console.log('[SpotifyAuth] Received auth event:', data.token ? 'Token received' : 'No token');
78
- setToken(data.token);
79
- setIsAuthenticating(false);
84
+ console.log('[SpotifyAuth] Received auth event:', JSON.stringify(data));
80
85
 
81
- if (data.error) {
86
+ // Only update state if we receive a token
87
+ if (data.token) {
88
+ setAuthState({
89
+ accessToken: data.token,
90
+ refreshToken: data.refreshToken,
91
+ expiresIn: data.expiresIn,
92
+ tokenType: data.tokenType,
93
+ scope: data.scope,
94
+ });
95
+ setIsAuthenticating(false);
96
+ setError(null);
97
+ }
98
+
99
+ // Only set error if we have no token and there's an error
100
+ if (!data.token && data.error) {
82
101
  console.error('[SpotifyAuth] Auth event error:', data.error);
83
- console.error('Spotify auth error:', data.error);
102
+ setAuthState({
103
+ accessToken: null,
104
+ refreshToken: null,
105
+ expiresIn: null,
106
+ tokenType: null,
107
+ scope: null,
108
+ });
84
109
  setError(data.error);
85
- } else {
86
- setError(null);
110
+ setIsAuthenticating(false);
87
111
  }
88
112
  });
89
113
  return () => subscription.remove();
@@ -91,7 +115,12 @@ export function SpotifyAuthProvider({
91
115
 
92
116
  return (
93
117
  <SpotifyAuthContextInstance.Provider
94
- value={{ accessToken: token, authorize, isAuthenticating, error }}
118
+ value={{
119
+ authState,
120
+ authorize,
121
+ isAuthenticating,
122
+ error
123
+ }}
95
124
  >
96
125
  {children}
97
126
  </SpotifyAuthContextInstance.Provider>