@ley0x/better-auth-lastfm 1.2.1 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,14 +42,14 @@ export const auth = betterAuth({
42
42
  // Optional: customize redirect URL after successful auth
43
43
  redirectTo: '/dashboard', // default: '/dashboard'
44
44
  // Optional: set base URL (usually auto-detected)
45
- baseUrl: process.env.BETTER_AUTH_URL
46
- })
45
+ baseUrl: process.env.BETTER_AUTH_URL,
46
+ }),
47
47
  ],
48
48
  // Optional: customize session expiration (default: 7 days)
49
49
  session: {
50
50
  expiresIn: 60 * 60 * 24 * 30, // 30 days
51
- updateAge: 60 * 60 * 24 // Update session every day
52
- }
51
+ updateAge: 60 * 60 * 24, // Update session every day
52
+ },
53
53
  })
54
54
  ```
55
55
 
@@ -60,7 +60,7 @@ import { createAuthClient } from 'better-auth/react'
60
60
  import { lastfmClientPlugin } from '@ley0x/better-auth-lastfm/client'
61
61
 
62
62
  export const authClient = createAuthClient({
63
- plugins: [lastfmClientPlugin()]
63
+ plugins: [lastfmClientPlugin()],
64
64
  })
65
65
 
66
66
  export const { useSession, signOut, signIn, signUp } = authClient
@@ -122,11 +122,11 @@ async function getRecentTracks(username: string, sessionKey: string) {
122
122
  user: username,
123
123
  api_key: process.env.LASTFM_API_KEY!,
124
124
  sk: sessionKey,
125
- format: 'json'
125
+ format: 'json',
126
126
  }
127
127
 
128
128
  const signature = createLastfmApiSignature(params, process.env.LASTFM_SHARED_SECRET!)
129
-
129
+
130
130
  const url = new URL('https://ws.audioscrobbler.com/2.0/')
131
131
  Object.entries({ ...params, api_sig: signature }).forEach(([key, value]) => {
132
132
  url.searchParams.set(key, value)
@@ -158,11 +158,15 @@ By default, BetterAuth sessions expire after 7 days. You can customize the sessi
158
158
 
159
159
  ```typescript
160
160
  export const auth = betterAuth({
161
- plugins: [lastfmPlugin({ /* ... */ })],
161
+ plugins: [
162
+ lastfmPlugin({
163
+ /* ... */
164
+ }),
165
+ ],
162
166
  session: {
163
167
  expiresIn: 60 * 60 * 24 * 30, // 30 days (default: 7 days)
164
- updateAge: 60 * 60 * 24 // Update session every day (default: 1 day)
165
- }
168
+ updateAge: 60 * 60 * 24, // Update session every day (default: 1 day)
169
+ },
166
170
  })
167
171
  ```
168
172
 
@@ -174,23 +178,27 @@ export const auth = betterAuth({
174
178
 
175
179
  ```typescript
176
180
  export const auth = betterAuth({
177
- plugins: [lastfmPlugin({ /* ... */ })],
181
+ plugins: [
182
+ lastfmPlugin({
183
+ /* ... */
184
+ }),
185
+ ],
178
186
  session: {
179
187
  expiresIn: 60 * 60 * 24 * 30, // 30 days
180
188
  updateAge: 60 * 60 * 24,
181
189
  cookieCache: {
182
190
  enabled: true,
183
191
  maxAge: 60 * 60 * 24,
184
- strategy: 'compact'
185
- }
192
+ strategy: 'compact',
193
+ },
186
194
  },
187
195
  cookies: {
188
196
  sessionToken: {
189
197
  attributes: {
190
- maxAge: 60 * 60 * 24 * 30 // Persistent cookie
191
- }
192
- }
193
- }
198
+ maxAge: 60 * 60 * 24 * 30, // Persistent cookie
199
+ },
200
+ },
201
+ },
194
202
  })
195
203
  ```
196
204
 
@@ -203,7 +211,7 @@ Server-side plugin configuration.
203
211
  #### Options
204
212
 
205
213
  - `apiKey` (required): Your Last.fm API key
206
- - `sharedSecret` (required): Your Last.fm shared secret
214
+ - `sharedSecret` (required): Your Last.fm shared secret
207
215
  - `baseUrl` (optional): Your app's base URL for callbacks
208
216
  - `redirectTo` (optional): Path to redirect after successful auth
209
217
 
@@ -221,11 +229,11 @@ Client-side plugin for browser environments.
221
229
  Full TypeScript support with exported types:
222
230
 
223
231
  ```typescript
224
- import type {
232
+ import type {
225
233
  LastfmPluginOptions,
226
234
  LastfmSession,
227
235
  LastfmAuthResponse,
228
- LastfmUserProfile
236
+ LastfmUserProfile,
229
237
  } from '@ley0x/better-auth-lastfm'
230
238
  ```
231
239
 
package/dist/index.cjs CHANGED
@@ -86,89 +86,77 @@ function lastfmPlugin(options) {
86
86
  const authUrl = `https://www.last.fm/api/auth/?api_key=${apiKey}&cb=${encodeURIComponent(callbackUrl)}`;
87
87
  return ctx.redirect(authUrl);
88
88
  }),
89
- "/lastfm/callback": (0, import_api.createAuthEndpoint)(
90
- "/lastfm/callback",
91
- { method: "GET" },
92
- async (ctx) => {
93
- const { token } = ctx.query;
94
- if (!token) {
95
- ctx.context.logger?.error("Last.fm callback: Missing token parameter");
96
- return ctx.json({ error: "Authentication failed: Missing token" }, { status: 400 });
97
- }
98
- try {
99
- const sessionData = await exchangeTokenForSession(token, apiKey, sharedSecret);
100
- const { username, sessionKey } = sessionData;
101
- const existingAccount = await ctx.context.adapter.findOne({
89
+ "/lastfm/callback": (0, import_api.createAuthEndpoint)("/lastfm/callback", { method: "GET" }, async (ctx) => {
90
+ const { token } = ctx.query;
91
+ if (!token) {
92
+ ctx.context.logger?.error("Last.fm callback: Missing token parameter");
93
+ return ctx.json({ error: "Authentication failed: Missing token" }, { status: 400 });
94
+ }
95
+ try {
96
+ const sessionData = await exchangeTokenForSession(token, apiKey, sharedSecret);
97
+ const { username, sessionKey } = sessionData;
98
+ const existingAccount = await ctx.context.adapter.findOne({
99
+ model: "account",
100
+ where: [
101
+ { field: "providerId", value: "lastfm" },
102
+ { field: "accountId", value: username }
103
+ ]
104
+ });
105
+ let user;
106
+ if (existingAccount) {
107
+ const validatedAccount = accountSchema.parse(existingAccount);
108
+ await ctx.context.adapter.update({
102
109
  model: "account",
103
- where: [
104
- { field: "providerId", value: "lastfm" },
105
- { field: "accountId", value: username }
106
- ]
107
- });
108
- let user;
109
- if (existingAccount) {
110
- const validatedAccount = accountSchema.parse(existingAccount);
111
- await ctx.context.adapter.update({
112
- model: "account",
113
- where: [{ field: "id", value: validatedAccount.id }],
114
- update: {
115
- accessToken: sessionKey,
116
- updatedAt: /* @__PURE__ */ new Date()
117
- }
118
- });
119
- const existingUser = await ctx.context.adapter.findOne({
120
- model: "user",
121
- where: [{ field: "id", value: validatedAccount.userId }]
122
- });
123
- if (!existingUser) {
124
- return ctx.json({ error: "User not found" }, { status: 404 });
110
+ where: [{ field: "id", value: validatedAccount.id }],
111
+ update: {
112
+ accessToken: sessionKey,
113
+ updatedAt: /* @__PURE__ */ new Date()
125
114
  }
126
- user = userSchema.parse(existingUser);
127
- } else {
128
- const newUser = await ctx.context.adapter.create({
129
- model: "user",
130
- data: {
131
- name: username,
132
- email: `${username}@lastfm.local`,
133
- emailVerified: true,
134
- image: null
135
- }
136
- });
137
- user = userSchema.parse(newUser);
138
- await ctx.context.adapter.create({
139
- model: "account",
140
- data: {
141
- accountId: username,
142
- providerId: "lastfm",
143
- userId: user.id,
144
- accessToken: sessionKey,
145
- createdAt: /* @__PURE__ */ new Date(),
146
- updatedAt: /* @__PURE__ */ new Date()
147
- }
148
- });
115
+ });
116
+ const existingUser = await ctx.context.adapter.findOne({
117
+ model: "user",
118
+ where: [{ field: "id", value: validatedAccount.userId }]
119
+ });
120
+ if (!existingUser) {
121
+ return ctx.json({ error: "User not found" }, { status: 404 });
149
122
  }
150
- const session = await ctx.context.internalAdapter.createSession(user.id);
151
- const cookieName = ctx.context.authCookies.sessionToken.name;
152
- const cookieOptions = ctx.context.authCookies.sessionToken.attributes;
153
- await ctx.setSignedCookie(
154
- cookieName,
155
- session.token,
156
- ctx.context.secret,
157
- {
158
- ...cookieOptions,
159
- maxAge: cookieOptions.maxAge || void 0
123
+ user = userSchema.parse(existingUser);
124
+ } else {
125
+ const newUser = await ctx.context.adapter.create({
126
+ model: "user",
127
+ data: {
128
+ name: username,
129
+ email: `${username}@lastfm.local`,
130
+ emailVerified: true,
131
+ image: null
160
132
  }
161
- );
162
- return ctx.redirect(redirectTo);
163
- } catch (error) {
164
- ctx.context.logger?.error("Last.fm authentication error:", error);
165
- return ctx.json(
166
- { error: "Authentication failed" },
167
- { status: 500 }
168
- );
133
+ });
134
+ user = userSchema.parse(newUser);
135
+ await ctx.context.adapter.create({
136
+ model: "account",
137
+ data: {
138
+ accountId: username,
139
+ providerId: "lastfm",
140
+ userId: user.id,
141
+ accessToken: sessionKey,
142
+ createdAt: /* @__PURE__ */ new Date(),
143
+ updatedAt: /* @__PURE__ */ new Date()
144
+ }
145
+ });
169
146
  }
147
+ const session = await ctx.context.internalAdapter.createSession(user.id);
148
+ const cookieName = ctx.context.authCookies.sessionToken.name;
149
+ const cookieOptions = ctx.context.authCookies.sessionToken.attributes;
150
+ await ctx.setSignedCookie(cookieName, session.token, ctx.context.secret, {
151
+ ...cookieOptions,
152
+ maxAge: cookieOptions.maxAge || void 0
153
+ });
154
+ return ctx.redirect(redirectTo);
155
+ } catch (error) {
156
+ ctx.context.logger?.error("Last.fm authentication error:", error);
157
+ return ctx.json({ error: "Authentication failed" }, { status: 500 });
170
158
  }
171
- )
159
+ })
172
160
  }
173
161
  };
174
162
  }
package/dist/index.js CHANGED
@@ -59,89 +59,77 @@ function lastfmPlugin(options) {
59
59
  const authUrl = `https://www.last.fm/api/auth/?api_key=${apiKey}&cb=${encodeURIComponent(callbackUrl)}`;
60
60
  return ctx.redirect(authUrl);
61
61
  }),
62
- "/lastfm/callback": createAuthEndpoint(
63
- "/lastfm/callback",
64
- { method: "GET" },
65
- async (ctx) => {
66
- const { token } = ctx.query;
67
- if (!token) {
68
- ctx.context.logger?.error("Last.fm callback: Missing token parameter");
69
- return ctx.json({ error: "Authentication failed: Missing token" }, { status: 400 });
70
- }
71
- try {
72
- const sessionData = await exchangeTokenForSession(token, apiKey, sharedSecret);
73
- const { username, sessionKey } = sessionData;
74
- const existingAccount = await ctx.context.adapter.findOne({
62
+ "/lastfm/callback": createAuthEndpoint("/lastfm/callback", { method: "GET" }, async (ctx) => {
63
+ const { token } = ctx.query;
64
+ if (!token) {
65
+ ctx.context.logger?.error("Last.fm callback: Missing token parameter");
66
+ return ctx.json({ error: "Authentication failed: Missing token" }, { status: 400 });
67
+ }
68
+ try {
69
+ const sessionData = await exchangeTokenForSession(token, apiKey, sharedSecret);
70
+ const { username, sessionKey } = sessionData;
71
+ const existingAccount = await ctx.context.adapter.findOne({
72
+ model: "account",
73
+ where: [
74
+ { field: "providerId", value: "lastfm" },
75
+ { field: "accountId", value: username }
76
+ ]
77
+ });
78
+ let user;
79
+ if (existingAccount) {
80
+ const validatedAccount = accountSchema.parse(existingAccount);
81
+ await ctx.context.adapter.update({
75
82
  model: "account",
76
- where: [
77
- { field: "providerId", value: "lastfm" },
78
- { field: "accountId", value: username }
79
- ]
80
- });
81
- let user;
82
- if (existingAccount) {
83
- const validatedAccount = accountSchema.parse(existingAccount);
84
- await ctx.context.adapter.update({
85
- model: "account",
86
- where: [{ field: "id", value: validatedAccount.id }],
87
- update: {
88
- accessToken: sessionKey,
89
- updatedAt: /* @__PURE__ */ new Date()
90
- }
91
- });
92
- const existingUser = await ctx.context.adapter.findOne({
93
- model: "user",
94
- where: [{ field: "id", value: validatedAccount.userId }]
95
- });
96
- if (!existingUser) {
97
- return ctx.json({ error: "User not found" }, { status: 404 });
83
+ where: [{ field: "id", value: validatedAccount.id }],
84
+ update: {
85
+ accessToken: sessionKey,
86
+ updatedAt: /* @__PURE__ */ new Date()
98
87
  }
99
- user = userSchema.parse(existingUser);
100
- } else {
101
- const newUser = await ctx.context.adapter.create({
102
- model: "user",
103
- data: {
104
- name: username,
105
- email: `${username}@lastfm.local`,
106
- emailVerified: true,
107
- image: null
108
- }
109
- });
110
- user = userSchema.parse(newUser);
111
- await ctx.context.adapter.create({
112
- model: "account",
113
- data: {
114
- accountId: username,
115
- providerId: "lastfm",
116
- userId: user.id,
117
- accessToken: sessionKey,
118
- createdAt: /* @__PURE__ */ new Date(),
119
- updatedAt: /* @__PURE__ */ new Date()
120
- }
121
- });
88
+ });
89
+ const existingUser = await ctx.context.adapter.findOne({
90
+ model: "user",
91
+ where: [{ field: "id", value: validatedAccount.userId }]
92
+ });
93
+ if (!existingUser) {
94
+ return ctx.json({ error: "User not found" }, { status: 404 });
122
95
  }
123
- const session = await ctx.context.internalAdapter.createSession(user.id);
124
- const cookieName = ctx.context.authCookies.sessionToken.name;
125
- const cookieOptions = ctx.context.authCookies.sessionToken.attributes;
126
- await ctx.setSignedCookie(
127
- cookieName,
128
- session.token,
129
- ctx.context.secret,
130
- {
131
- ...cookieOptions,
132
- maxAge: cookieOptions.maxAge || void 0
96
+ user = userSchema.parse(existingUser);
97
+ } else {
98
+ const newUser = await ctx.context.adapter.create({
99
+ model: "user",
100
+ data: {
101
+ name: username,
102
+ email: `${username}@lastfm.local`,
103
+ emailVerified: true,
104
+ image: null
133
105
  }
134
- );
135
- return ctx.redirect(redirectTo);
136
- } catch (error) {
137
- ctx.context.logger?.error("Last.fm authentication error:", error);
138
- return ctx.json(
139
- { error: "Authentication failed" },
140
- { status: 500 }
141
- );
106
+ });
107
+ user = userSchema.parse(newUser);
108
+ await ctx.context.adapter.create({
109
+ model: "account",
110
+ data: {
111
+ accountId: username,
112
+ providerId: "lastfm",
113
+ userId: user.id,
114
+ accessToken: sessionKey,
115
+ createdAt: /* @__PURE__ */ new Date(),
116
+ updatedAt: /* @__PURE__ */ new Date()
117
+ }
118
+ });
142
119
  }
120
+ const session = await ctx.context.internalAdapter.createSession(user.id);
121
+ const cookieName = ctx.context.authCookies.sessionToken.name;
122
+ const cookieOptions = ctx.context.authCookies.sessionToken.attributes;
123
+ await ctx.setSignedCookie(cookieName, session.token, ctx.context.secret, {
124
+ ...cookieOptions,
125
+ maxAge: cookieOptions.maxAge || void 0
126
+ });
127
+ return ctx.redirect(redirectTo);
128
+ } catch (error) {
129
+ ctx.context.logger?.error("Last.fm authentication error:", error);
130
+ return ctx.json({ error: "Authentication failed" }, { status: 500 });
143
131
  }
144
- )
132
+ })
145
133
  }
146
134
  };
147
135
  }
package/package.json CHANGED
@@ -1,8 +1,30 @@
1
1
  {
2
2
  "name": "@ley0x/better-auth-lastfm",
3
- "version": "1.2.1",
4
- "type": "module",
3
+ "version": "1.2.4",
5
4
  "description": "Last.fm authentication plugin for BetterAuth",
5
+ "keywords": [
6
+ "auth",
7
+ "authentication",
8
+ "better-auth",
9
+ "lastfm",
10
+ "plugin",
11
+ "typescript"
12
+ ],
13
+ "homepage": "https://codeberg.org/ley0x/betterauth-lastfm#readme",
14
+ "bugs": {
15
+ "url": "https://codeberg.org/ley0x/betterauth-lastfm/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": "ley0x <ley0x@pm.me>",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://codeberg.org/ley0x/betterauth-lastfm.git"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md"
26
+ ],
27
+ "type": "module",
6
28
  "main": "dist/index.cjs",
7
29
  "module": "dist/index.js",
8
30
  "types": "dist/index.d.ts",
@@ -18,57 +40,35 @@
18
40
  "require": "./dist/client/index.cjs"
19
41
  }
20
42
  },
21
- "files": [
22
- "dist",
23
- "README.md"
24
- ],
25
- "keywords": [
26
- "better-auth",
27
- "lastfm",
28
- "authentication",
29
- "auth",
30
- "plugin",
31
- "typescript"
32
- ],
33
- "author": "ley0x <ley0x@pm.me>",
34
- "license": "MIT",
35
- "repository": {
36
- "type": "git",
37
- "url": "https://codeberg.org/ley0x/betterauth-lastfm.git"
38
- },
39
- "bugs": {
40
- "url": "https://codeberg.org/ley0x/betterauth-lastfm/issues"
41
- },
42
- "homepage": "https://codeberg.org/ley0x/betterauth-lastfm#readme",
43
- "peerDependencies": {
44
- "better-auth": "^1.x.x"
43
+ "publishConfig": {
44
+ "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "zod": "^4.3.6"
47
+ "zod": "^4.4.3"
48
48
  },
49
49
  "devDependencies": {
50
- "@eslint/js": "^10.0.1",
51
- "@types/node": "^25.5.0",
52
- "better-auth": "latest",
53
- "eslint": "^10.1.0",
50
+ "@types/node": "^26.0.0",
51
+ "better-auth": "^1.6.20",
52
+ "knip": "^6.17.1",
53
+ "oxfmt": "^0.55.0",
54
+ "oxlint": "^1.70.0",
54
55
  "tsup": "^8.5.1",
55
- "typescript": "^5.9.3",
56
- "typescript-eslint": "^8.57.2",
57
- "vitest": "^1.6.1"
56
+ "typescript": "^6.0.3"
57
+ },
58
+ "peerDependencies": {
59
+ "better-auth": "^1.x.x"
58
60
  },
59
61
  "engines": {
60
62
  "node": ">=18.0.0"
61
63
  },
62
- "publishConfig": {
63
- "access": "public"
64
- },
65
64
  "scripts": {
66
65
  "build": "tsup",
67
66
  "dev": "tsup --watch",
68
- "type-check": "tsc --noEmit",
69
- "lint": "eslint src --max-warnings 0",
70
- "lint:fix": "eslint src --fix",
71
- "test": "vitest",
72
- "test:ui": "vitest --ui"
67
+ "typecheck": "tsc --noEmit",
68
+ "lint": "oxlint",
69
+ "lint:fix": "oxlint --fix",
70
+ "fmt": "oxfmt",
71
+ "fmt:check": "oxfmt --check",
72
+ "knip": "knip"
73
73
  }
74
74
  }