@superfan-app/spotify-auth 0.1.39 → 0.1.41

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.
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Available Spotify authorization scopes.
3
+ * @see https://developer.spotify.com/documentation/general/guides/authorization/scopes/
4
+ */
5
+ export type SpotifyScopes = 'app-remote-control' | 'playlist-modify-private' | 'playlist-modify-public' | 'playlist-read-collaborative' | 'playlist-read-private' | 'streaming' | 'user-follow-modify' | 'user-follow-read' | 'user-library-modify' | 'user-library-read' | 'user-modify-playback-state' | 'user-read-currently-playing' | 'user-read-email' | 'user-read-playback-position' | 'user-read-playback-state' | 'user-read-private' | 'user-read-recently-played' | 'user-top-read' | 'openid';
1
6
  /**
2
7
  * Event data structure for Spotify authorization events
3
8
  */
@@ -47,13 +52,10 @@ export type SpotifyAuthError = {
47
52
  };
48
53
  };
49
54
  /**
50
- * Configuration for the authorization request
55
+ * Configuration for the authorization request.
56
+ * These are runtime options that can be changed between auth attempts.
51
57
  */
52
58
  export interface AuthorizeConfig {
53
- /** Spotify Client ID */
54
- clientId: string;
55
- /** OAuth redirect URL */
56
- redirectUrl: string;
57
59
  /** Whether to show the auth dialog */
58
60
  showDialog?: boolean;
59
61
  /** Campaign identifier for attribution */
@@ -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,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
+ {"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 +1 @@
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
+ {"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,4 +1,13 @@
1
- declare const _default: any;
1
+ import { NativeModule } from "expo-modules-core";
2
+ import type { AuthorizeConfig, SpotifyAuthEvent } from "./SpotifyAuth.types";
3
+ type SpotifyAuthEvents = {
4
+ onSpotifyAuth(event: SpotifyAuthEvent): void;
5
+ };
6
+ export declare class SpotifyAuthModule extends NativeModule<SpotifyAuthEvents> {
7
+ readonly AuthEventName: string;
8
+ readonly authorize: (config: AuthorizeConfig) => Promise<void>;
9
+ }
10
+ declare const _default: SpotifyAuthModule;
2
11
  export default _default;
3
12
  export declare const AuthEventName: "onSpotifyAuth";
4
13
  //# sourceMappingURL=SpotifyAuthModule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SpotifyAuthModule.d.ts","sourceRoot":"","sources":["../src/SpotifyAuthModule.ts"],"names":[],"mappings":";AAGA,wBAAkD;AAElD,eAAO,MAAM,aAAa,EAAG,eAAwB,CAAC"}
1
+ {"version":3,"file":"SpotifyAuthModule.d.ts","sourceRoot":"","sources":["../src/SpotifyAuthModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE7E,KAAK,iBAAiB,GAAG;IACvB,aAAa,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC9C,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,YAAY,CAAC,iBAAiB,CAAC;IAC5E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;wBAGoD,iBAAiB;AAAtE,wBAAuE;AAEvE,eAAO,MAAM,aAAa,EAAG,eAAwB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"SpotifyAuthModule.js","sourceRoot":"","sources":["../src/SpotifyAuthModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,yDAAyD;AACzD,eAAe,mBAAmB,CAAC,aAAa,CAAC,CAAC;AAElD,MAAM,CAAC,MAAM,aAAa,GAAG,eAAwB,CAAC","sourcesContent":["import { requireNativeModule } from \"expo-modules-core\";\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule(\"SpotifyAuth\");\n\nexport const AuthEventName = \"onSpotifyAuth\" as const;\n"]}
1
+ {"version":3,"file":"SpotifyAuthModule.js","sourceRoot":"","sources":["../src/SpotifyAuthModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAgB,MAAM,mBAAmB,CAAC;AAYtE,yDAAyD;AACzD,eAAe,mBAAmB,CAAC,aAAa,CAAsB,CAAC;AAEvE,MAAM,CAAC,MAAM,aAAa,GAAG,eAAwB,CAAC","sourcesContent":["import { requireNativeModule, NativeModule } from \"expo-modules-core\";\nimport type { AuthorizeConfig, SpotifyAuthEvent } from \"./SpotifyAuth.types\";\n\ntype SpotifyAuthEvents = {\n onSpotifyAuth(event: SpotifyAuthEvent): void;\n};\n\nexport declare class SpotifyAuthModule extends NativeModule<SpotifyAuthEvents> {\n readonly AuthEventName: string;\n readonly authorize: (config: AuthorizeConfig) => Promise<void>;\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule(\"SpotifyAuth\") as SpotifyAuthModule;\n\nexport const AuthEventName = \"onSpotifyAuth\" as const;\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,CA2DxC;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,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"}
package/build/index.js CHANGED
@@ -23,6 +23,7 @@ export function SpotifyAuthProvider({ children, }) {
23
23
  const authorize = useCallback(async (config) => {
24
24
  try {
25
25
  console.log('[SpotifyAuth] Starting authorization process in provider');
26
+ console.log('[SpotifyAuth] Authorization config:', JSON.stringify(config));
26
27
  setIsAuthenticating(true);
27
28
  setError(null);
28
29
  await SpotifyAuthModule.authorize(config);
@@ -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 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 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"]}
@@ -125,77 +125,44 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
125
125
 
126
126
  // MARK: - Configuration Accessors
127
127
 
128
+ private func getInfoPlistValue<T>(_ key: String) throws -> T {
129
+ guard let value = Bundle.main.object(forInfoDictionaryKey: key) as? T else {
130
+ throw SpotifyAuthError.missingConfiguration("Missing \(key) in Info.plist")
131
+ }
132
+ return value
133
+ }
134
+
128
135
  private var clientID: String {
129
136
  get throws {
130
- guard let config = currentConfig else {
131
- throw SpotifyAuthError.missingConfiguration("No active configuration")
132
- }
133
- guard !config.clientId.isEmpty else {
134
- throw SpotifyAuthError.missingConfiguration("clientId")
135
- }
136
- return config.clientId
137
+ try getInfoPlistValue("SpotifyClientID")
137
138
  }
138
139
  }
139
140
 
140
141
  private var redirectURL: URL {
141
142
  get throws {
142
- guard let config = currentConfig else {
143
- throw SpotifyAuthError.missingConfiguration("No active configuration")
144
- }
145
- guard let url = URL(string: config.redirectUrl),
146
- url.scheme != nil,
147
- url.host != nil else {
143
+ let urlString: String = try getInfoPlistValue("SpotifyRedirectURL")
144
+ guard let url = URL(string: urlString) else {
148
145
  throw SpotifyAuthError.invalidConfiguration("Invalid redirect URL format")
149
146
  }
150
147
  return url
151
148
  }
152
149
  }
153
150
 
154
- private var tokenRefreshURL: String {
151
+ private var tokenSwapURL: String {
155
152
  get throws {
156
- guard let config = currentConfig else {
157
- throw SpotifyAuthError.missingConfiguration("No active configuration")
158
- }
159
- guard !config.tokenRefreshURL.isEmpty else {
160
- throw SpotifyAuthError.missingConfiguration("tokenRefreshURL")
161
- }
162
- return config.tokenRefreshURL
153
+ try getInfoPlistValue("SpotifyTokenSwapURL")
163
154
  }
164
155
  }
165
156
 
166
- private var tokenSwapURL: String {
157
+ private var tokenRefreshURL: String {
167
158
  get throws {
168
- guard let config = currentConfig else {
169
- throw SpotifyAuthError.missingConfiguration("No active configuration")
170
- }
171
- guard !config.tokenSwapURL.isEmpty else {
172
- throw SpotifyAuthError.missingConfiguration("tokenSwapURL")
173
- }
174
- return config.tokenSwapURL
159
+ try getInfoPlistValue("SpotifyTokenRefreshURL")
175
160
  }
176
161
  }
177
162
 
178
- private var requestedScopes: SPTScope {
163
+ private var scopes: [String] {
179
164
  get throws {
180
- guard let config = currentConfig else {
181
- throw SpotifyAuthError.missingConfiguration("No active configuration")
182
- }
183
- guard !config.scopes.isEmpty else {
184
- throw SpotifyAuthError.missingConfiguration("scopes")
185
- }
186
-
187
- var combinedScope: SPTScope = []
188
- for scopeString in config.scopes {
189
- if let scope = stringToScope(scopeString: scopeString) {
190
- combinedScope.insert(scope)
191
- }
192
- }
193
-
194
- if combinedScope.isEmpty {
195
- throw SpotifyAuthError.invalidConfiguration("No valid scopes provided")
196
- }
197
-
198
- return combinedScope
165
+ try getInfoPlistValue("SpotifyScopes")
199
166
  }
200
167
  }
201
168
 
@@ -374,44 +341,53 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
374
341
 
375
342
  public func initAuth(config: AuthorizeConfig) {
376
343
  do {
377
- // Save configuration.
378
- self.currentConfig = config
379
-
380
- guard let sessionManager = self.sessionManager else {
381
- throw SpotifyAuthError.sessionError("Session manager not initialized")
382
- }
383
- let scopes = try self.requestedScopes
384
- isAuthenticating = true
385
-
386
- if sessionManager.isSpotifyAppInstalled {
387
- // Use the native app‑switch flow.
388
- if config.showDialog {
389
- sessionManager.alwaysShowAuthorizationDialog = true
344
+ guard let sessionManager = self.sessionManager else {
345
+ throw SpotifyAuthError.sessionError("Session manager not initialized")
390
346
  }
391
- sessionManager.initiateSession(with: scopes, options: .default, campaign: config.campaign)
392
- } else {
393
- // Fall back to web‑auth.
394
- isUsingWebAuth = true
395
- let clientId = try self.clientID
396
- let redirectUrl = try self.redirectURL
397
347
 
398
- let webAuthView = SpotifyOAuthView(appContext: nil)
399
- webAuthView.delegate = self
400
- self.webAuthView = webAuthView
348
+ // Get scopes from Info.plist and convert to SPTScope
349
+ let scopes = try self.scopes.reduce(into: SPTScope()) { result, scopeString in
350
+ if let scope = stringToScope(scopeString: scopeString) {
351
+ result.insert(scope)
352
+ }
353
+ }
401
354
 
402
- let scopeStrings = scopes.scopesToStringArray()
355
+ if scopes.isEmpty {
356
+ throw SpotifyAuthError.invalidConfiguration("No valid scopes found in configuration")
357
+ }
403
358
 
404
- webAuthView.startOAuthFlow(
405
- clientId: clientId,
406
- redirectUri: redirectUrl.absoluteString,
407
- scopes: scopeStrings
408
- )
359
+ isAuthenticating = true
409
360
 
410
- module?.presentWebAuth(webAuthView)
411
- }
361
+ if sessionManager.isSpotifyAppInstalled {
362
+ // Use the native app‑switch flow
363
+ if config.showDialog {
364
+ sessionManager.alwaysShowAuthorizationDialog = true
365
+ }
366
+ sessionManager.initiateSession(with: scopes, options: .default, campaign: config.campaign)
367
+ } else {
368
+ // Use web auth as fallback
369
+ let webView = SpotifyOAuthView(appContext: nil)
370
+ webView.delegate = self
371
+ self.webAuthView = webView
372
+ isUsingWebAuth = true
373
+
374
+ // Get configuration from Info.plist
375
+ let clientId = try self.clientID
376
+ let redirectUrl = try self.redirectURL
377
+ let scopeStrings = try self.scopes
378
+
379
+ webView.startOAuthFlow(
380
+ clientId: clientId,
381
+ redirectUri: redirectUrl.absoluteString,
382
+ scopes: scopeStrings,
383
+ showDialog: config.showDialog,
384
+ campaign: config.campaign
385
+ )
386
+
387
+ module?.presentWebAuth(webView)
388
+ }
412
389
  } catch {
413
- isAuthenticating = false
414
- handleError(error, context: "authentication")
390
+ module?.onAuthorizationError(error)
415
391
  }
416
392
  }
417
393
 
@@ -422,9 +398,10 @@ final class SpotifyAuthAuth: NSObject, SPTSessionManagerDelegate, SpotifyOAuthVi
422
398
  guard let sessionManager = self.sessionManager else {
423
399
  throw SpotifyAuthError.sessionError("Session manager not initialized")
424
400
  }
425
- let scopes = try self.requestedScopes
401
+ let scopes = try self.scopes
402
+ let sptScopes = SPTScope(rawValue: scopes.joined(separator: " "))
426
403
  isAuthenticating = true
427
- sessionManager.initiateSession(with: scopes, options: .default, campaign: nil)
404
+ sessionManager.initiateSession(with: sptScopes, options: .default, campaign: nil)
428
405
  } catch {
429
406
  isAuthenticating = false
430
407
  handleError(error, context: "authentication_retry")
@@ -20,13 +20,8 @@ func secureLog(_ message: String, sensitive: Bool = false) {
20
20
  #endif
21
21
 
22
22
  struct AuthorizeConfig: Record {
23
- @Field var clientId: String
24
- @Field var redirectUrl: String
25
23
  @Field var showDialog: Bool = false
26
24
  @Field var campaign: String?
27
- @Field var tokenSwapURL: String
28
- @Field var tokenRefreshURL: String
29
- @Field var scopes: [String]
30
25
  }
31
26
 
32
27
  // Define a private enum for mapping Spotify SDK error codes.
@@ -66,19 +61,9 @@ public class SpotifyAuthModule: Module {
66
61
  }
67
62
 
68
63
  AsyncFunction("authorize") { (config: AuthorizeConfig, promise: Promise) in
69
- secureLog("Authorization requested with clientId: \(config.clientId)")
70
-
71
- // Sanitize and validate redirect URL.
72
- guard let url = URL(string: config.redirectUrl),
73
- url.scheme != nil,
74
- url.host != nil else {
75
- secureLog("Invalid redirect URL format: \(config.redirectUrl)")
76
- promise.reject(SpotifyAuthError.invalidConfiguration("Invalid redirect URL format"))
77
- return
78
- }
64
+ secureLog("Authorization requested")
79
65
 
80
66
  do {
81
- secureLog("Initializing auth with scopes: \(config.scopes.joined(separator: ","))")
82
67
  try self.spotifyAuth.initAuth(config: config)
83
68
  promise.resolve()
84
69
  } catch {
@@ -103,7 +103,7 @@ class SpotifyOAuthView: ExpoView {
103
103
  }
104
104
  }
105
105
 
106
- func startOAuthFlow(clientId: String, redirectUri: String, scopes: [String]) {
106
+ func startOAuthFlow(clientId: String, redirectUri: String, scopes: [String], showDialog: Bool = false, campaign: String? = nil) {
107
107
  guard !isAuthenticating else { return }
108
108
  isAuthenticating = true
109
109
 
@@ -123,7 +123,13 @@ class SpotifyOAuthView: ExpoView {
123
123
  ofTypes: [WKWebsiteDataTypeCookies, WKWebsiteDataTypeSessionStorage],
124
124
  modifiedSince: Date(timeIntervalSince1970: 0)
125
125
  ) { [weak self] in
126
- self?.initiateAuthRequest(clientId: clientId, redirectUri: redirectUri, scopes: scopes)
126
+ self?.initiateAuthRequest(
127
+ clientId: clientId,
128
+ redirectUri: redirectUri,
129
+ scopes: scopes,
130
+ showDialog: showDialog,
131
+ campaign: campaign
132
+ )
127
133
  }
128
134
  }
129
135
 
@@ -153,22 +159,28 @@ class SpotifyOAuthView: ExpoView {
153
159
  ) { }
154
160
  }
155
161
 
156
- private func initiateAuthRequest(clientId: String, redirectUri: String, scopes: [String]) {
162
+ private func initiateAuthRequest(clientId: String, redirectUri: String, scopes: [String], showDialog: Bool, campaign: String?) {
157
163
  guard var urlComponents = URLComponents(string: "https://accounts.spotify.com/authorize") else {
158
164
  isAuthenticating = false
159
165
  delegate?.oauthView(self, didFailWithError: SpotifyOAuthError.invalidRedirectURL)
160
166
  return
161
167
  }
162
168
 
163
- urlComponents.queryItems = [
169
+ var queryItems = [
164
170
  URLQueryItem(name: "client_id", value: clientId),
165
171
  URLQueryItem(name: "response_type", value: "code"),
166
172
  URLQueryItem(name: "redirect_uri", value: redirectUri),
167
173
  URLQueryItem(name: "state", value: state),
168
174
  URLQueryItem(name: "scope", value: scopes.joined(separator: " ")),
169
- URLQueryItem(name: "show_dialog", value: "true")
175
+ URLQueryItem(name: "show_dialog", value: showDialog ? "true" : "false")
170
176
  ]
171
177
 
178
+ if let campaign = campaign {
179
+ queryItems.append(URLQueryItem(name: "campaign", value: campaign))
180
+ }
181
+
182
+ urlComponents.queryItems = queryItems
183
+
172
184
  guard let url = urlComponents.url else {
173
185
  isAuthenticating = false
174
186
  delegate?.oauthView(self, didFailWithError: SpotifyOAuthError.invalidRedirectURL)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superfan-app/spotify-auth",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "Spotify OAuth module for Expo",
5
5
  "main": "src/index.tsx",
6
6
  "types": "build/index.d.ts",
@@ -16,6 +16,14 @@ function validateSpotifyConfig(config) {
16
16
  if (!Array.isArray(config.scopes) || config.scopes.length === 0) {
17
17
  throw new Error("At least one scope is required");
18
18
  }
19
+ // Validate URL scheme format
20
+ if (!/^[a-z][a-z0-9+.-]*$/i.test(config.scheme)) {
21
+ throw new Error("Invalid URL scheme format. Scheme should start with a letter and contain only letters, numbers, plus, period, or hyphen.");
22
+ }
23
+ // Validate callback path
24
+ if (!/^[a-z0-9\-_\/]+$/i.test(config.callback)) {
25
+ throw new Error("Invalid callback path format. Path should contain only letters, numbers, hyphens, underscores, and forward slashes.");
26
+ }
19
27
  }
20
28
  function validateScheme(scheme) {
21
29
  if (!scheme) {
@@ -48,13 +56,14 @@ const withSpotifyURLSchemes = (config, props) => {
48
56
  };
49
57
  const withSpotifyConfiguration = (config, props) => {
50
58
  return (0, config_plugins_1.withInfoPlist)(config, (config) => {
59
+ // Construct the redirect URL from scheme and callback
60
+ const redirectUrl = `${props.scheme}://${props.callback}`;
51
61
  // Add Spotify configuration
52
62
  config.modResults.SpotifyClientID = props.clientID;
53
- config.modResults.SpotifyScheme = props.scheme;
54
- config.modResults.SpotifyCallback = props.callback;
63
+ config.modResults.SpotifyRedirectURL = redirectUrl;
55
64
  config.modResults.SpotifyScopes = props.scopes;
56
- config.modResults.tokenRefreshURL = props.tokenRefreshURL;
57
- config.modResults.tokenSwapURL = props.tokenSwapURL;
65
+ config.modResults.SpotifyTokenSwapURL = props.tokenSwapURL;
66
+ config.modResults.SpotifyTokenRefreshURL = props.tokenRefreshURL;
58
67
  return config;
59
68
  });
60
69
  };
@@ -2,7 +2,7 @@
2
2
  * Available Spotify authorization scopes.
3
3
  * @see https://developer.spotify.com/documentation/general/guides/authorization/scopes/
4
4
  */
5
- export type SpotifyScopes = 'app-remote-control' | 'playlist-modify-private' | 'playlist-modify-public' | 'playlist-read-collaborative' | 'playlist-read-private' | 'streaming' | 'user-follow-modify' | 'user-follow-read' | 'user-library-modify' | 'user-library-read' | 'user-modify-playback-state' | 'user-read-currently-playing' | 'user-read-email' | 'user-read-playback-position' | 'user-read-playback-state' | 'user-read-private' | 'user-read-recently-played' | 'user-top-read';
5
+ export type SpotifyScopes = 'app-remote-control' | 'playlist-modify-private' | 'playlist-modify-public' | 'playlist-read-collaborative' | 'playlist-read-private' | 'streaming' | 'user-follow-modify' | 'user-follow-read' | 'user-library-modify' | 'user-library-read' | 'user-modify-playback-state' | 'user-read-currently-playing' | 'user-read-email' | 'user-read-playback-position' | 'user-read-playback-state' | 'user-read-private' | 'user-read-recently-played' | 'user-top-read' | 'openid';
6
6
  /**
7
7
  * Configuration options for the Spotify OAuth module.
8
8
  * This should be provided in your app.config.js or app.json under the "plugins" section.
@@ -12,6 +12,16 @@ function validateSpotifyConfig(config: SpotifyConfig) {
12
12
  if (!Array.isArray(config.scopes) || config.scopes.length === 0) {
13
13
  throw new Error("At least one scope is required")
14
14
  }
15
+
16
+ // Validate URL scheme format
17
+ if (!/^[a-z][a-z0-9+.-]*$/i.test(config.scheme)) {
18
+ throw new Error("Invalid URL scheme format. Scheme should start with a letter and contain only letters, numbers, plus, period, or hyphen.")
19
+ }
20
+
21
+ // Validate callback path
22
+ if (!/^[a-z0-9\-_\/]+$/i.test(config.callback)) {
23
+ throw new Error("Invalid callback path format. Path should contain only letters, numbers, hyphens, underscores, and forward slashes.")
24
+ }
15
25
  }
16
26
 
17
27
  function validateScheme(scheme: string) {
@@ -50,13 +60,15 @@ const withSpotifyURLSchemes: ConfigPlugin<SpotifyConfig> = (config, props) => {
50
60
 
51
61
  const withSpotifyConfiguration: ConfigPlugin<SpotifyConfig> = (config, props) => {
52
62
  return withInfoPlist(config, (config) => {
63
+ // Construct the redirect URL from scheme and callback
64
+ const redirectUrl = `${props.scheme}://${props.callback}`
65
+
53
66
  // Add Spotify configuration
54
67
  config.modResults.SpotifyClientID = props.clientID;
55
- config.modResults.SpotifyScheme = props.scheme;
56
- config.modResults.SpotifyCallback = props.callback;
68
+ config.modResults.SpotifyRedirectURL = redirectUrl;
57
69
  config.modResults.SpotifyScopes = props.scopes;
58
- config.modResults.tokenRefreshURL = props.tokenRefreshURL;
59
- config.modResults.tokenSwapURL = props.tokenSwapURL;
70
+ config.modResults.SpotifyTokenSwapURL = props.tokenSwapURL;
71
+ config.modResults.SpotifyTokenRefreshURL = props.tokenRefreshURL;
60
72
 
61
73
  return config;
62
74
  });
@@ -21,6 +21,7 @@ export type SpotifyScopes =
21
21
  | 'user-read-private'
22
22
  | 'user-read-recently-played'
23
23
  | 'user-top-read'
24
+ | 'openid'
24
25
 
25
26
  /**
26
27
  * Configuration options for the Spotify OAuth module.
@@ -53,14 +54,14 @@ export interface SpotifyConfig {
53
54
  * Obtain this from your Spotify Developer Dashboard.
54
55
  * @see https://developer.spotify.com/dashboard/
55
56
  */
56
- clientID: string
57
+ clientID: string;
57
58
 
58
59
  /**
59
60
  * The URL scheme to use for OAuth callbacks.
60
61
  * This should be unique to your app and match your Spotify app settings.
61
62
  * @example "my-spotify-app"
62
63
  */
63
- scheme: string
64
+ scheme: string;
64
65
 
65
66
  /**
66
67
  * The callback path for OAuth redirects.
@@ -68,7 +69,7 @@ export interface SpotifyConfig {
68
69
  * Full URI will be: `{scheme}://{callback}`
69
70
  * @example "callback"
70
71
  */
71
- callback: string
72
+ callback: string;
72
73
 
73
74
  /**
74
75
  * URL for token swap endpoint.
@@ -76,7 +77,7 @@ export interface SpotifyConfig {
76
77
  * the code-for-token exchange with Spotify.
77
78
  * @example "https://your-backend.com/spotify/swap"
78
79
  */
79
- tokenSwapURL: string
80
+ tokenSwapURL: string;
80
81
 
81
82
  /**
82
83
  * URL for token refresh endpoint.
@@ -84,7 +85,7 @@ export interface SpotifyConfig {
84
85
  * refreshing expired access tokens.
85
86
  * @example "https://your-backend.com/spotify/refresh"
86
87
  */
87
- tokenRefreshURL: string
88
+ tokenRefreshURL: string;
88
89
 
89
90
  /**
90
91
  * Array of Spotify authorization scopes.
@@ -92,5 +93,5 @@ export interface SpotifyConfig {
92
93
  * @see SpotifyScopes for available options
93
94
  * @example ["user-read-email", "streaming"]
94
95
  */
95
- scopes: SpotifyScopes[]
96
+ scopes: SpotifyScopes[];
96
97
  }
@@ -1,5 +1,30 @@
1
1
  import { createContext } from "react";
2
2
 
3
+ /**
4
+ * Available Spotify authorization scopes.
5
+ * @see https://developer.spotify.com/documentation/general/guides/authorization/scopes/
6
+ */
7
+ export type SpotifyScopes =
8
+ | 'app-remote-control'
9
+ | 'playlist-modify-private'
10
+ | 'playlist-modify-public'
11
+ | 'playlist-read-collaborative'
12
+ | 'playlist-read-private'
13
+ | 'streaming'
14
+ | 'user-follow-modify'
15
+ | 'user-follow-read'
16
+ | 'user-library-modify'
17
+ | 'user-library-read'
18
+ | 'user-modify-playback-state'
19
+ | 'user-read-currently-playing'
20
+ | 'user-read-email'
21
+ | 'user-read-playback-position'
22
+ | 'user-read-playback-state'
23
+ | 'user-read-private'
24
+ | 'user-read-recently-played'
25
+ | 'user-top-read'
26
+ | 'openid';
27
+
3
28
  /**
4
29
  * Event data structure for Spotify authorization events
5
30
  */
@@ -58,13 +83,10 @@ export type SpotifyAuthError = {
58
83
  }
59
84
 
60
85
  /**
61
- * Configuration for the authorization request
86
+ * Configuration for the authorization request.
87
+ * These are runtime options that can be changed between auth attempts.
62
88
  */
63
89
  export interface AuthorizeConfig {
64
- /** Spotify Client ID */
65
- clientId: string;
66
- /** OAuth redirect URL */
67
- redirectUrl: string;
68
90
  /** Whether to show the auth dialog */
69
91
  showDialog?: boolean;
70
92
  /** Campaign identifier for attribution */
@@ -1,6 +1,16 @@
1
- import { requireNativeModule } from "expo-modules-core";
1
+ import { requireNativeModule, NativeModule } from "expo-modules-core";
2
+ import type { AuthorizeConfig, SpotifyAuthEvent } from "./SpotifyAuth.types";
3
+
4
+ type SpotifyAuthEvents = {
5
+ onSpotifyAuth(event: SpotifyAuthEvent): void;
6
+ };
7
+
8
+ export declare class SpotifyAuthModule extends NativeModule<SpotifyAuthEvents> {
9
+ readonly AuthEventName: string;
10
+ readonly authorize: (config: AuthorizeConfig) => Promise<void>;
11
+ }
2
12
 
3
13
  // This call loads the native module object from the JSI.
4
- export default requireNativeModule("SpotifyAuth");
14
+ export default requireNativeModule("SpotifyAuth") as SpotifyAuthModule;
5
15
 
6
16
  export const AuthEventName = "onSpotifyAuth" as const;
package/src/index.tsx CHANGED
@@ -45,6 +45,7 @@ export function SpotifyAuthProvider({
45
45
  async (config: AuthorizeConfig): Promise<void> => {
46
46
  try {
47
47
  console.log('[SpotifyAuth] Starting authorization process in provider');
48
+ console.log('[SpotifyAuth] Authorization config:', JSON.stringify(config));
48
49
  setIsAuthenticating(true);
49
50
  setError(null);
50
51
  await SpotifyAuthModule.authorize(config);
@@ -1,51 +0,0 @@
1
- import Foundation
2
-
3
- class TokenManager {
4
- static let shared = TokenManager()
5
- private let tokenKey = "com.yourappname.spotifyToken"
6
-
7
- private init() {}
8
-
9
- func saveToken(_ token: String) -> Bool {
10
- guard let data = token.data(using: .utf8) else { return false }
11
-
12
- let query: [String: Any] = [
13
- kSecClass as String: kSecClassGenericPassword,
14
- kSecAttrAccount as String: tokenKey,
15
- kSecValueData as String: data,
16
- ]
17
-
18
- SecItemDelete(query as CFDictionary)
19
- let status = SecItemAdd(query as CFDictionary, nil)
20
- return status == errSecSuccess
21
- }
22
-
23
- func getToken() -> String? {
24
- let query: [String: Any] = [
25
- kSecClass as String: kSecClassGenericPassword,
26
- kSecAttrAccount as String: tokenKey,
27
- kSecReturnData as String: kCFBooleanTrue!,
28
- kSecMatchLimit as String: kSecMatchLimitOne,
29
- ]
30
-
31
- var dataTypeRef: AnyObject?
32
- let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
33
-
34
- if status == errSecSuccess {
35
- if let data = dataTypeRef as? Data {
36
- return String(data: data, encoding: .utf8)
37
- }
38
- }
39
- return nil
40
- }
41
-
42
- func deleteToken() -> Bool {
43
- let query: [String: Any] = [
44
- kSecClass as String: kSecClassGenericPassword,
45
- kSecAttrAccount as String: tokenKey,
46
- ]
47
-
48
- let status = SecItemDelete(query as CFDictionary)
49
- return status == errSecSuccess
50
- }
51
- }