@superfan-app/spotify-auth 0.1.35 → 0.1.37

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.
@@ -4,7 +4,7 @@
4
4
  export interface SpotifyAuthEvent {
5
5
  success: boolean;
6
6
  token: string | null;
7
- error?: string;
7
+ error?: SpotifyAuthError;
8
8
  }
9
9
  /**
10
10
  * Data returned from the Spotify authorization process
@@ -14,9 +14,38 @@ export interface SpotifyAuthorizationData {
14
14
  success: boolean;
15
15
  /** The access token if authorization was successful, null otherwise */
16
16
  token: string | null;
17
- /** Error message if authorization failed */
18
- error?: string;
17
+ /** Error information if authorization failed */
18
+ error?: SpotifyAuthError;
19
19
  }
20
+ /**
21
+ * Possible error types that can occur during Spotify authentication
22
+ */
23
+ export type SpotifyAuthError = {
24
+ /** The type of error that occurred */
25
+ type: "configuration_error" | "network_error" | "token_error" | "authorization_error" | "server_error" | "unknown_error";
26
+ /** Human-readable error message */
27
+ message: string;
28
+ /** Additional error details */
29
+ details: {
30
+ /** Specific error code for more granular error handling */
31
+ error_code: string;
32
+ /** Whether the error can be recovered from */
33
+ recoverable: boolean;
34
+ /** Retry strategy information if applicable */
35
+ retry?: {
36
+ /** Type of retry strategy */
37
+ type: "fixed" | "exponential";
38
+ /** For fixed retry: number of attempts */
39
+ attempts?: number;
40
+ /** For fixed retry: delay between attempts in seconds */
41
+ delay?: number;
42
+ /** For exponential backoff: maximum number of attempts */
43
+ max_attempts?: number;
44
+ /** For exponential backoff: initial delay in seconds */
45
+ initial_delay?: number;
46
+ };
47
+ };
48
+ };
20
49
  /**
21
50
  * Configuration for the authorization request
22
51
  */
@@ -48,7 +77,7 @@ export interface SpotifyAuthContext {
48
77
  /** Whether authorization is in progress */
49
78
  isAuthenticating: boolean;
50
79
  /** Last error that occurred during authentication */
51
- error: string | null;
80
+ error: SpotifyAuthError | null;
52
81
  }
53
82
  export declare const SpotifyAuthContextInstance: import("react").Context<SpotifyAuthContext>;
54
83
  export interface SpotifyAuthOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"SpotifyAuth.types.d.ts","sourceRoot":"","sources":["../src/SpotifyAuth.types.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;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,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,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,MAAM,GAAG,IAAI,CAAC;CACtB;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":"AAEA;;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;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,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 +1 @@
1
- {"version":3,"file":"SpotifyAuth.types.js","sourceRoot":"","sources":["../src/SpotifyAuth.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AA2DtC,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 * Event data structure for Spotify authorization events\n */\nexport interface SpotifyAuthEvent {\n success: boolean;\n token: string | null;\n error?: string;\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 message if authorization failed */\n error?: string;\n}\n\n/**\n * Configuration for the authorization request\n */\nexport interface AuthorizeConfig {\n /** Spotify Client ID */\n clientId: string;\n /** OAuth redirect URL */\n redirectUrl: string;\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: string | 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;AA+FtC,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 * 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 */\nexport interface AuthorizeConfig {\n /** Spotify Client ID */\n clientId: string;\n /** OAuth redirect URL */\n redirectUrl: string;\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 +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,EACrB,MAAM,qBAAqB,CAAC;AAe7B;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAEvD;AAED,UAAU,wBAAwB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,GACT,EAAE,wBAAwB,GAAG,GAAG,CAAC,OAAO,CAsCxC;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,EAElB,KAAK,eAAe,EAErB,MAAM,qBAAqB,CAAC;AAe7B;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAEvD;AAED,UAAU,wBAAwB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,GACT,EAAE,wBAAwB,GAAG,GAAG,CAAC,OAAO,CAsDxC;AAED,wBAAgB,cAAc,IAAI,kBAAkB,CAMnD"}
package/build/index.js CHANGED
@@ -26,18 +26,34 @@ export function SpotifyAuthProvider({ children, }) {
26
26
  await SpotifyAuthModule.authorize(config);
27
27
  }
28
28
  catch (err) {
29
- setError(err instanceof Error ? err.message : "Authorization failed");
29
+ // Handle structured errors from the native layer
30
+ if (err && typeof err === 'object' && 'type' in err) {
31
+ setError(err);
32
+ }
33
+ else {
34
+ // Create a generic error structure for unknown errors
35
+ setError({
36
+ type: 'unknown_error',
37
+ message: err instanceof Error ? err.message : 'Authorization failed',
38
+ details: {
39
+ error_code: 'unknown',
40
+ recoverable: false
41
+ }
42
+ });
43
+ }
30
44
  throw err;
31
45
  }
32
- finally {
33
- setIsAuthenticating(false);
34
- }
35
46
  }, []);
36
47
  useEffect(() => {
37
48
  const subscription = addAuthListener((data) => {
38
49
  setToken(data.token);
50
+ setIsAuthenticating(false);
39
51
  if (data.error) {
40
- console.error(`Spotify auth error: ${data.error}`);
52
+ console.error('Spotify auth error:', data.error);
53
+ setError(data.error);
54
+ }
55
+ else {
56
+ setError(null);
41
57
  }
42
58
  });
43
59
  return () => subscription.remove();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAE5E,OAAO,EAGL,0BAA0B,GAE3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AAKpD,kCAAkC;AAClC,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,iBAAiB,CAAC,CAAC;AAEpD,SAAS,eAAe,CAAC,QAAkD;IACzE,+CAA+C;IAC/C,MAAM,SAAS,GAAG,iBAAiB,CAAC,aAAqC,CAAC;IAC1E,OAAO,OAAO,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,MAAuB;IAC/C,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAMD,MAAM,UAAU,mBAAmB,CAAC,EAClC,QAAQ,GACiB;IACzB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAExD,MAAM,SAAS,GAAG,WAAW,CAC3B,KAAK,EAAE,MAAuB,EAAiB,EAAE;QAC/C,IAAI,CAAC;YACH,mBAAmB,CAAC,IAAI,CAAC,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,CAAC;YACf,MAAM,iBAAiB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC;YACtE,MAAM,GAAG,CAAC;QACZ,CAAC;gBAAS,CAAC;YACT,mBAAmB,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC,EACD,EAAE,CACH,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,YAAY,GAAG,eAAe,CAAC,CAAC,IAAI,EAAE,EAAE;YAC5C,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,uBAAuB,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;YACrD,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;IACrC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,CAAC,0BAA0B,CAAC,QAAQ,CAClC,KAAK,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,KAAK,EAAE,CAAC,CAElE;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,0BAA0B,CAAC,QAAQ,CAAC,CACvC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,OAAO,GAAG,UAAU,CAAC,0BAA0B,CAAC,CAAC;IACvD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","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} 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 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<string | null>(null);\n\n const authorize = useCallback(\n async (config: AuthorizeConfig): Promise<void> => {\n try {\n setIsAuthenticating(true);\n setError(null);\n await SpotifyAuthModule.authorize(config);\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Authorization failed\");\n throw err;\n } finally {\n setIsAuthenticating(false);\n }\n },\n [],\n );\n\n useEffect(() => {\n const subscription = addAuthListener((data) => {\n setToken(data.token);\n if (data.error) {\n console.error(`Spotify auth error: ${data.error}`);\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 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 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 setIsAuthenticating(true);\n setError(null);\n await SpotifyAuthModule.authorize(config);\n } catch (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 const subscription = addAuthListener((data) => {\n setToken(data.token);\n setIsAuthenticating(false);\n\n if (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"]}
@@ -89,7 +89,7 @@ enum SpotifyAuthError: Error {
89
89
  }
90
90
 
91
91
  final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthViewDelegate {
92
- /// A weak reference to our modules JS interface.
92
+ /// A weak reference to our module's JS interface.
93
93
  weak var module: SpotifyAuthModule?
94
94
 
95
95
  /// For web‑auth we present our own OAuth view.
@@ -489,8 +489,8 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
489
489
  private func exchangeCodeForToken(_ code: String) {
490
490
  guard let swapURLString = try? self.tokenSwapURL,
491
491
  let url = URL(string: swapURLString) else {
492
- handleError(SpotifyAuthError.invalidConfiguration("Invalid token swap URL"), context: "token_exchange")
493
- return
492
+ handleError(SpotifyAuthError.invalidConfiguration("Invalid token swap URL"), context: "token_exchange")
493
+ return
494
494
  }
495
495
 
496
496
  var request = URLRequest(url: url)
@@ -499,40 +499,67 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
499
499
 
500
500
  let params: [String: String]
501
501
  do {
502
- params = [
503
- "grant_type": "authorization_code",
504
- "code": code,
505
- "redirect_uri": try self.redirectURL.absoluteString,
506
- "client_id": try self.clientID
507
- ]
502
+ params = [
503
+ "grant_type": "authorization_code",
504
+ "code": code,
505
+ "redirect_uri": try self.redirectURL.absoluteString,
506
+ "client_id": try self.clientID
507
+ ]
508
508
  } catch {
509
- handleError(error, context: "token_exchange")
510
- return
509
+ handleError(error, context: "token_exchange")
510
+ return
511
511
  }
512
512
 
513
513
  let bodyString = params.map { "\($0)=\($1)" }.joined(separator: "&")
514
514
  request.httpBody = bodyString.data(using: .utf8)
515
515
 
516
516
  let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
517
- if let error = error {
518
- self?.handleError(error, context: "token_exchange")
519
- return
520
- }
521
- guard let data = data,
522
- let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
523
- let accessToken = json["access_token"] as? String,
524
- let refreshToken = json["refresh_token"] as? String,
525
- let expiresIn = json["expires_in"] as? TimeInterval else {
526
- self?.handleError(SpotifyAuthError.tokenError("Invalid token response"), context: "token_exchange")
527
- return
528
- }
529
-
530
- let expirationDate = Date(timeIntervalSinceNow: expiresIn)
531
- let sessionData = SpotifySessionData(accessToken: accessToken, refreshToken: refreshToken, expirationDate: expirationDate)
532
- DispatchQueue.main.async {
533
- self?.currentSession = sessionData
534
- self?.module?.onAccessTokenObtained(accessToken)
535
- }
517
+ if let error = error {
518
+ self?.handleError(SpotifyAuthError.networkError(error.localizedDescription), context: "token_exchange")
519
+ return
520
+ }
521
+
522
+ guard let httpResponse = response as? HTTPURLResponse else {
523
+ self?.handleError(SpotifyAuthError.networkError("Invalid response type"), context: "token_exchange")
524
+ return
525
+ }
526
+
527
+ // Check HTTP status code
528
+ guard (200...299).contains(httpResponse.statusCode) else {
529
+ let errorMessage: String
530
+ if let data = data, let errorJson = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
531
+ let errorDescription = errorJson["error_description"] as? String {
532
+ errorMessage = errorDescription
533
+ } else {
534
+ errorMessage = "Server returned status code \(httpResponse.statusCode)"
535
+ }
536
+ self?.handleError(SpotifyAuthError.networkError(errorMessage), context: "token_exchange")
537
+ return
538
+ }
539
+
540
+ guard let data = data else {
541
+ self?.handleError(SpotifyAuthError.tokenError("No data received"), context: "token_exchange")
542
+ return
543
+ }
544
+
545
+ do {
546
+ let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
547
+ guard let json = json,
548
+ let accessToken = json["access_token"] as? String,
549
+ let refreshToken = json["refresh_token"] as? String,
550
+ let expiresIn = json["expires_in"] as? TimeInterval else {
551
+ throw SpotifyAuthError.tokenError("Invalid token response format")
552
+ }
553
+
554
+ let expirationDate = Date(timeIntervalSinceNow: expiresIn)
555
+ let sessionData = SpotifySessionData(accessToken: accessToken, refreshToken: refreshToken, expirationDate: expirationDate)
556
+ DispatchQueue.main.async {
557
+ self?.currentSession = sessionData
558
+ self?.module?.onAccessTokenObtained(accessToken)
559
+ }
560
+ } catch {
561
+ self?.handleError(error, context: "token_exchange")
562
+ }
536
563
  }
537
564
 
538
565
  task.resume()
@@ -588,18 +615,24 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
588
615
  }
589
616
 
590
617
  private func handleError(_ error: Error, context: String) {
591
- let spotifyError: SpotifyAuthError = .authenticationFailed(error.localizedDescription)
618
+ let spotifyError: SpotifyAuthError
619
+
620
+ if let existingSpotifyError = error as? SpotifyAuthError {
621
+ spotifyError = existingSpotifyError
622
+ } else {
623
+ spotifyError = .authenticationFailed(error.localizedDescription)
624
+ }
592
625
 
593
626
  secureLog("Error in \(context): \(spotifyError.localizedDescription)")
594
627
 
595
628
  switch spotifyError.retryStrategy {
596
629
  case .none:
597
- module?.onAuthorizationError(spotifyError.localizedDescription)
598
- cleanupPreviousSession()
630
+ module?.onAuthorizationError(spotifyError.localizedDescription)
631
+ cleanupPreviousSession()
599
632
  case .retry(let attempts, let delay):
600
- handleRetry(error: spotifyError, context: context, remainingAttempts: attempts, delay: delay)
633
+ handleRetry(error: spotifyError, context: context, remainingAttempts: attempts, delay: delay)
601
634
  case .exponentialBackoff(let maxAttempts, let initialDelay):
602
- handleExponentialBackoff(error: spotifyError, context: context, remainingAttempts: maxAttempts, currentDelay: initialDelay)
635
+ handleExponentialBackoff(error: spotifyError, context: context, remainingAttempts: maxAttempts, currentDelay: initialDelay)
603
636
  }
604
637
  }
605
638
 
@@ -146,20 +146,115 @@ public class SpotifyAuthModule: Module {
146
146
  }
147
147
 
148
148
  @objc
149
- public func onAuthorizationError(_ errorDescription: String) {
150
- let sanitizedError = sanitizeErrorMessage(errorDescription)
151
- secureLog("Authorization error: \(sanitizedError)")
149
+ public func onAuthorizationError(_ error: Error) {
150
+ let errorData: [String: Any]
151
+
152
+ if let spotifyError = error as? SpotifyAuthError {
153
+ // Map domain error to a structured format
154
+ errorData = mapSpotifyError(spotifyError)
155
+ } else if let sptError = error as? SPTError {
156
+ // Map Spotify SDK errors
157
+ errorData = mapSPTError(sptError)
158
+ } else {
159
+ // Map unknown errors
160
+ errorData = [
161
+ "type": "unknown_error",
162
+ "message": sanitizeErrorMessage(error.localizedDescription),
163
+ "details": [
164
+ "error_code": "unknown",
165
+ "recoverable": false
166
+ ]
167
+ ]
168
+ }
169
+
170
+ secureLog("Authorization error: \(errorData["message"] as? String ?? "Unknown error")")
171
+
152
172
  let eventData: [String: Any] = [
153
173
  "success": false,
154
- "token": NSNull(), // Use NSNull() instead of nil.
155
- "error": sanitizedError
174
+ "token": NSNull(),
175
+ "error": errorData
156
176
  ]
157
177
  sendEvent(SPOTIFY_AUTHORIZATION_EVENT_NAME, eventData)
158
178
  }
159
179
 
180
+ private func mapSpotifyError(_ error: SpotifyAuthError) -> [String: Any] {
181
+ let message = sanitizeErrorMessage(error.localizedDescription)
182
+ var details: [String: Any] = ["recoverable": error.isRecoverable]
183
+
184
+ let (type, errorCode) = classifySpotifyError(error)
185
+ details["error_code"] = errorCode
186
+
187
+ // Add retry strategy information if available
188
+ switch error.retryStrategy {
189
+ case .retry(let attempts, let delay):
190
+ details["retry"] = [
191
+ "type": "fixed",
192
+ "attempts": attempts,
193
+ "delay": delay
194
+ ]
195
+ case .exponentialBackoff(let maxAttempts, let initialDelay):
196
+ details["retry"] = [
197
+ "type": "exponential",
198
+ "max_attempts": maxAttempts,
199
+ "initial_delay": initialDelay
200
+ ]
201
+ case .none:
202
+ details["retry"] = nil
203
+ }
204
+
205
+ return [
206
+ "type": type,
207
+ "message": message,
208
+ "details": details
209
+ ]
210
+ }
211
+
212
+ private func mapSPTError(_ error: SPTError) -> [String: Any] {
213
+ let message = sanitizeErrorMessage(error.localizedDescription)
214
+ let details: [String: Any] = [
215
+ "error_code": error.code,
216
+ "recoverable": false
217
+ ]
218
+
219
+ let type: String
220
+ switch error.code {
221
+ case .authorizationFailed:
222
+ type = "authorization_error"
223
+ case .renewSessionFailed:
224
+ type = "token_error"
225
+ case .jsonFailed:
226
+ type = "server_error"
227
+ default:
228
+ type = "unknown_error"
229
+ }
230
+
231
+ return [
232
+ "type": type,
233
+ "message": message,
234
+ "details": details
235
+ ]
236
+ }
237
+
238
+ private func classifySpotifyError(_ error: SpotifyAuthError) -> (type: String, code: String) {
239
+ switch error {
240
+ case .missingConfiguration, .invalidConfiguration:
241
+ return ("configuration_error", "config_invalid")
242
+ case .authenticationFailed:
243
+ return ("authorization_error", "auth_failed")
244
+ case .tokenError:
245
+ return ("token_error", "token_invalid")
246
+ case .sessionError:
247
+ return ("authorization_error", "session_error")
248
+ case .networkError:
249
+ return ("network_error", "network_failed")
250
+ case .recoverable:
251
+ return ("authorization_error", "recoverable_error")
252
+ }
253
+ }
254
+
160
255
  func presentWebAuth(_ webAuthView: SpotifyOAuthView) {
161
256
  guard let topViewController = UIApplication.shared.currentKeyWindow?.rootViewController?.topMostViewController() else {
162
- onAuthorizationError("Could not present web authentication")
257
+ onAuthorizationError(SpotifyAuthError.unknownError("Could not present web authentication"))
163
258
  return
164
259
  }
165
260
 
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@superfan-app/spotify-auth",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "Spotify OAuth module for Expo",
5
- "main": "src/index.ts",
5
+ "main": "src/index.tsx",
6
6
  "types": "build/index.d.ts",
7
7
  "sideEffects": false,
8
8
  "scripts": {
@@ -6,7 +6,7 @@ import { createContext } from "react";
6
6
  export interface SpotifyAuthEvent {
7
7
  success: boolean;
8
8
  token: string | null;
9
- error?: string;
9
+ error?: SpotifyAuthError;
10
10
  }
11
11
 
12
12
  /**
@@ -17,8 +17,44 @@ export interface SpotifyAuthorizationData {
17
17
  success: boolean;
18
18
  /** The access token if authorization was successful, null otherwise */
19
19
  token: string | null;
20
- /** Error message if authorization failed */
21
- error?: string;
20
+ /** Error information if authorization failed */
21
+ error?: SpotifyAuthError;
22
+ }
23
+
24
+ /**
25
+ * Possible error types that can occur during Spotify authentication
26
+ */
27
+ export type SpotifyAuthError = {
28
+ /** The type of error that occurred */
29
+ type:
30
+ | "configuration_error" // Missing or invalid configuration
31
+ | "network_error" // Network-related issues
32
+ | "token_error" // Issues with token exchange/refresh
33
+ | "authorization_error" // User-facing authorization issues
34
+ | "server_error" // Backend server issues
35
+ | "unknown_error"; // Unexpected errors
36
+ /** Human-readable error message */
37
+ message: string;
38
+ /** Additional error details */
39
+ details: {
40
+ /** Specific error code for more granular error handling */
41
+ error_code: string;
42
+ /** Whether the error can be recovered from */
43
+ recoverable: boolean;
44
+ /** Retry strategy information if applicable */
45
+ retry?: {
46
+ /** Type of retry strategy */
47
+ type: "fixed" | "exponential";
48
+ /** For fixed retry: number of attempts */
49
+ attempts?: number;
50
+ /** For fixed retry: delay between attempts in seconds */
51
+ delay?: number;
52
+ /** For exponential backoff: maximum number of attempts */
53
+ max_attempts?: number;
54
+ /** For exponential backoff: initial delay in seconds */
55
+ initial_delay?: number;
56
+ };
57
+ };
22
58
  }
23
59
 
24
60
  /**
@@ -54,7 +90,7 @@ export interface SpotifyAuthContext {
54
90
  /** Whether authorization is in progress */
55
91
  isAuthenticating: boolean;
56
92
  /** Last error that occurred during authentication */
57
- error: string | null;
93
+ error: SpotifyAuthError | null;
58
94
  }
59
95
 
60
96
  export const SpotifyAuthContextInstance = createContext<SpotifyAuthContext>({
package/src/index.tsx CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  SpotifyAuthContext,
7
7
  SpotifyAuthContextInstance,
8
8
  type AuthorizeConfig,
9
+ type SpotifyAuthError,
9
10
  } from "./SpotifyAuth.types";
10
11
  import SpotifyAuthModule from "./SpotifyAuthModule";
11
12
 
@@ -37,7 +38,7 @@ export function SpotifyAuthProvider({
37
38
  }: SpotifyAuthProviderProps): JSX.Element {
38
39
  const [token, setToken] = useState<string | null>(null);
39
40
  const [isAuthenticating, setIsAuthenticating] = useState(false);
40
- const [error, setError] = useState<string | null>(null);
41
+ const [error, setError] = useState<SpotifyAuthError | null>(null);
41
42
 
42
43
  const authorize = useCallback(
43
44
  async (config: AuthorizeConfig): Promise<void> => {
@@ -46,10 +47,21 @@ export function SpotifyAuthProvider({
46
47
  setError(null);
47
48
  await SpotifyAuthModule.authorize(config);
48
49
  } catch (err) {
49
- setError(err instanceof Error ? err.message : "Authorization failed");
50
+ // Handle structured errors from the native layer
51
+ if (err && typeof err === 'object' && 'type' in err) {
52
+ setError(err as SpotifyAuthError);
53
+ } else {
54
+ // Create a generic error structure for unknown errors
55
+ setError({
56
+ type: 'unknown_error',
57
+ message: err instanceof Error ? err.message : 'Authorization failed',
58
+ details: {
59
+ error_code: 'unknown',
60
+ recoverable: false
61
+ }
62
+ });
63
+ }
50
64
  throw err;
51
- } finally {
52
- setIsAuthenticating(false);
53
65
  }
54
66
  },
55
67
  [],
@@ -58,8 +70,13 @@ export function SpotifyAuthProvider({
58
70
  useEffect(() => {
59
71
  const subscription = addAuthListener((data) => {
60
72
  setToken(data.token);
73
+ setIsAuthenticating(false);
74
+
61
75
  if (data.error) {
62
- console.error(`Spotify auth error: ${data.error}`);
76
+ console.error('Spotify auth error:', data.error);
77
+ setError(data.error);
78
+ } else {
79
+ setError(null);
63
80
  }
64
81
  });
65
82
  return () => subscription.remove();