@symbo.ls/sdk 3.2.3 → 3.2.7

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.
Files changed (183) hide show
  1. package/README.md +141 -0
  2. package/dist/cjs/config/environment.js +94 -10
  3. package/dist/cjs/index.js +152 -12
  4. package/dist/cjs/services/AdminService.js +351 -0
  5. package/dist/cjs/services/AuthService.js +738 -305
  6. package/dist/cjs/services/BaseService.js +158 -6
  7. package/dist/cjs/services/BranchService.js +484 -0
  8. package/dist/cjs/services/CollabService.js +439 -116
  9. package/dist/cjs/services/DnsService.js +340 -0
  10. package/dist/cjs/services/FeatureFlagService.js +175 -0
  11. package/dist/cjs/services/FileService.js +201 -0
  12. package/dist/cjs/services/IntegrationService.js +538 -0
  13. package/dist/cjs/services/MetricsService.js +62 -0
  14. package/dist/cjs/services/PaymentService.js +271 -0
  15. package/dist/cjs/services/PlanService.js +426 -0
  16. package/dist/cjs/services/ProjectService.js +1207 -0
  17. package/dist/cjs/services/PullRequestService.js +503 -0
  18. package/dist/cjs/services/ScreenshotService.js +304 -0
  19. package/dist/cjs/services/SubscriptionService.js +396 -0
  20. package/dist/cjs/services/TrackingService.js +661 -0
  21. package/dist/cjs/services/WaitlistService.js +148 -0
  22. package/dist/cjs/services/index.js +60 -4
  23. package/dist/cjs/state/RootStateManager.js +2 -23
  24. package/dist/cjs/state/rootEventBus.js +9 -0
  25. package/dist/cjs/utils/CollabClient.js +78 -12
  26. package/dist/cjs/utils/TokenManager.js +16 -3
  27. package/dist/cjs/utils/changePreprocessor.js +199 -0
  28. package/dist/cjs/utils/jsonDiff.js +46 -4
  29. package/dist/cjs/utils/ordering.js +309 -0
  30. package/dist/cjs/utils/services.js +285 -128
  31. package/dist/cjs/utils/validation.js +0 -3
  32. package/dist/esm/config/environment.js +94 -10
  33. package/dist/esm/index.js +47862 -18248
  34. package/dist/esm/services/AdminService.js +1132 -0
  35. package/dist/esm/services/AuthService.js +1493 -386
  36. package/dist/esm/services/BaseService.js +757 -6
  37. package/dist/esm/services/BranchService.js +1265 -0
  38. package/dist/esm/services/CollabService.js +24956 -16089
  39. package/dist/esm/services/DnsService.js +1121 -0
  40. package/dist/esm/services/FeatureFlagService.js +956 -0
  41. package/dist/esm/services/FileService.js +982 -0
  42. package/dist/esm/services/IntegrationService.js +1319 -0
  43. package/dist/esm/services/MetricsService.js +843 -0
  44. package/dist/esm/services/PaymentService.js +1052 -0
  45. package/dist/esm/services/PlanService.js +1207 -0
  46. package/dist/esm/services/ProjectService.js +2526 -0
  47. package/dist/esm/services/PullRequestService.js +1284 -0
  48. package/dist/esm/services/ScreenshotService.js +1085 -0
  49. package/dist/esm/services/SubscriptionService.js +1177 -0
  50. package/dist/esm/services/TrackingService.js +18454 -0
  51. package/dist/esm/services/WaitlistService.js +929 -0
  52. package/dist/esm/services/index.js +47373 -18027
  53. package/dist/esm/state/RootStateManager.js +11 -23
  54. package/dist/esm/state/rootEventBus.js +9 -0
  55. package/dist/esm/utils/CollabClient.js +17526 -16120
  56. package/dist/esm/utils/TokenManager.js +16 -3
  57. package/dist/esm/utils/changePreprocessor.js +542 -0
  58. package/dist/esm/utils/jsonDiff.js +958 -43
  59. package/dist/esm/utils/ordering.js +291 -0
  60. package/dist/esm/utils/services.js +285 -128
  61. package/dist/esm/utils/validation.js +116 -50
  62. package/dist/node/config/environment.js +94 -10
  63. package/dist/node/index.js +183 -16
  64. package/dist/node/services/AdminService.js +332 -0
  65. package/dist/node/services/AuthService.js +742 -310
  66. package/dist/node/services/BaseService.js +148 -6
  67. package/dist/node/services/BranchService.js +465 -0
  68. package/dist/node/services/CollabService.js +439 -116
  69. package/dist/node/services/DnsService.js +321 -0
  70. package/dist/node/services/FeatureFlagService.js +156 -0
  71. package/dist/node/services/FileService.js +182 -0
  72. package/dist/node/services/IntegrationService.js +519 -0
  73. package/dist/node/services/MetricsService.js +43 -0
  74. package/dist/node/services/PaymentService.js +252 -0
  75. package/dist/node/services/PlanService.js +407 -0
  76. package/dist/node/services/ProjectService.js +1188 -0
  77. package/dist/node/services/PullRequestService.js +484 -0
  78. package/dist/node/services/ScreenshotService.js +285 -0
  79. package/dist/node/services/SubscriptionService.js +377 -0
  80. package/dist/node/services/TrackingService.js +632 -0
  81. package/dist/node/services/WaitlistService.js +129 -0
  82. package/dist/node/services/index.js +60 -4
  83. package/dist/node/state/RootStateManager.js +2 -23
  84. package/dist/node/state/rootEventBus.js +9 -0
  85. package/dist/node/utils/CollabClient.js +77 -11
  86. package/dist/node/utils/TokenManager.js +16 -3
  87. package/dist/node/utils/changePreprocessor.js +180 -0
  88. package/dist/node/utils/jsonDiff.js +46 -4
  89. package/dist/node/utils/ordering.js +290 -0
  90. package/dist/node/utils/services.js +285 -128
  91. package/dist/node/utils/validation.js +0 -3
  92. package/package.json +30 -18
  93. package/src/config/environment.js +95 -10
  94. package/src/index.js +190 -23
  95. package/src/services/AdminService.js +374 -0
  96. package/src/services/AuthService.js +874 -328
  97. package/src/services/BaseService.js +166 -6
  98. package/src/services/BranchService.js +536 -0
  99. package/src/services/CollabService.js +557 -148
  100. package/src/services/DnsService.js +366 -0
  101. package/src/services/FeatureFlagService.js +174 -0
  102. package/src/services/FileService.js +213 -0
  103. package/src/services/IntegrationService.js +548 -0
  104. package/src/services/MetricsService.js +40 -0
  105. package/src/services/PaymentService.js +287 -0
  106. package/src/services/PlanService.js +468 -0
  107. package/src/services/ProjectService.js +1366 -0
  108. package/src/services/PullRequestService.js +537 -0
  109. package/src/services/ScreenshotService.js +258 -0
  110. package/src/services/SubscriptionService.js +425 -0
  111. package/src/services/TrackingService.js +853 -0
  112. package/src/services/WaitlistService.js +130 -0
  113. package/src/services/index.js +79 -5
  114. package/src/services/tests/BranchService/createBranch.test.js +153 -0
  115. package/src/services/tests/BranchService/deleteBranch.test.js +173 -0
  116. package/src/services/tests/BranchService/getBranchChanges.test.js +146 -0
  117. package/src/services/tests/BranchService/listBranches.test.js +87 -0
  118. package/src/services/tests/BranchService/mergeBranch.test.js +210 -0
  119. package/src/services/tests/BranchService/publishVersion.test.js +183 -0
  120. package/src/services/tests/BranchService/renameBranch.test.js +240 -0
  121. package/src/services/tests/BranchService/resetBranch.test.js +152 -0
  122. package/src/services/tests/FeatureFlagService/adminFeatureFlags.test.js +67 -0
  123. package/src/services/tests/FeatureFlagService/getFeatureFlags.test.js +75 -0
  124. package/src/services/tests/FileService/createFileFormData.test.js +74 -0
  125. package/src/services/tests/FileService/getFileUrl.test.js +69 -0
  126. package/src/services/tests/FileService/updateProjectIcon.test.js +109 -0
  127. package/src/services/tests/FileService/uploadDocument.test.js +36 -0
  128. package/src/services/tests/FileService/uploadFile.test.js +78 -0
  129. package/src/services/tests/FileService/uploadFileWithValidation.test.js +114 -0
  130. package/src/services/tests/FileService/uploadImage.test.js +36 -0
  131. package/src/services/tests/FileService/uploadMultipleFiles.test.js +111 -0
  132. package/src/services/tests/FileService/validateFile.test.js +63 -0
  133. package/src/services/tests/PlanService/createPlan.test.js +104 -0
  134. package/src/services/tests/PlanService/createPlanWithValidation.test.js +523 -0
  135. package/src/services/tests/PlanService/deletePlan.test.js +92 -0
  136. package/src/services/tests/PlanService/getActivePlans.test.js +123 -0
  137. package/src/services/tests/PlanService/getAdminPlans.test.js +84 -0
  138. package/src/services/tests/PlanService/getPlan.test.js +50 -0
  139. package/src/services/tests/PlanService/getPlanByKey.test.js +109 -0
  140. package/src/services/tests/PlanService/getPlanWithValidation.test.js +85 -0
  141. package/src/services/tests/PlanService/getPlans.test.js +53 -0
  142. package/src/services/tests/PlanService/getPlansByPriceRange.test.js +109 -0
  143. package/src/services/tests/PlanService/getPlansWithValidation.test.js +48 -0
  144. package/src/services/tests/PlanService/initializePlans.test.js +75 -0
  145. package/src/services/tests/PlanService/updatePlan.test.js +111 -0
  146. package/src/services/tests/PlanService/updatePlanWithValidation.test.js +556 -0
  147. package/src/state/RootStateManager.js +37 -32
  148. package/src/state/rootEventBus.js +19 -0
  149. package/src/utils/CollabClient.js +99 -12
  150. package/src/utils/TokenManager.js +20 -3
  151. package/src/utils/changePreprocessor.js +239 -0
  152. package/src/utils/jsonDiff.js +40 -5
  153. package/src/utils/ordering.js +271 -0
  154. package/src/utils/services.js +306 -139
  155. package/src/utils/validation.js +0 -3
  156. package/dist/cjs/services/AIService.js +0 -155
  157. package/dist/cjs/services/BasedService.js +0 -1185
  158. package/dist/cjs/services/CoreService.js +0 -2295
  159. package/dist/cjs/services/SocketService.js +0 -309
  160. package/dist/cjs/services/SymstoryService.js +0 -571
  161. package/dist/cjs/utils/basedQuerys.js +0 -181
  162. package/dist/cjs/utils/symstoryClient.js +0 -259
  163. package/dist/esm/services/AIService.js +0 -185
  164. package/dist/esm/services/BasedService.js +0 -5262
  165. package/dist/esm/services/CoreService.js +0 -2827
  166. package/dist/esm/services/SocketService.js +0 -456
  167. package/dist/esm/services/SymstoryService.js +0 -7025
  168. package/dist/esm/utils/basedQuerys.js +0 -163
  169. package/dist/esm/utils/symstoryClient.js +0 -354
  170. package/dist/node/services/AIService.js +0 -136
  171. package/dist/node/services/BasedService.js +0 -1156
  172. package/dist/node/services/CoreService.js +0 -2266
  173. package/dist/node/services/SocketService.js +0 -280
  174. package/dist/node/services/SymstoryService.js +0 -542
  175. package/dist/node/utils/basedQuerys.js +0 -162
  176. package/dist/node/utils/symstoryClient.js +0 -230
  177. package/src/services/AIService.js +0 -150
  178. package/src/services/BasedService.js +0 -1302
  179. package/src/services/CoreService.js +0 -2548
  180. package/src/services/SocketService.js +0 -336
  181. package/src/services/SymstoryService.js +0 -649
  182. package/src/utils/basedQuerys.js +0 -164
  183. package/src/utils/symstoryClient.js +0 -252
@@ -1,220 +1,885 @@
1
- /* eslint-disable require-await */
2
1
  import { BaseService } from './BaseService.js'
3
2
  import {
4
- PERMISSION_MAP,
5
3
  ROLE_PERMISSIONS,
6
4
  TIER_FEATURES,
7
5
  PROJECT_ROLE_PERMISSIONS
8
6
  } from '../utils/permission.js'
9
7
 
8
+ const PLUGIN_SESSION_STORAGE_KEY = 'plugin_auth_session'
9
+
10
10
  export class AuthService extends BaseService {
11
- constructor (config) {
11
+ constructor(config) {
12
12
  super(config)
13
13
  this._userRoles = new Set(['guest', 'editor', 'admin', 'owner'])
14
14
  this._projectTiers = new Set([
15
15
  'ready',
16
- 'free',
16
+ 'starter',
17
17
  'pro1',
18
18
  'pro2',
19
19
  'enterprise'
20
20
  ])
21
- this._initialized = false
21
+ this._projectRoleCache = new Map() // Cache for project roles
22
+ this._roleCacheExpiry = 5 * 60 * 1000 // 5 minutes cache expiry
23
+ this._pluginSession = null
24
+
25
+ this._resolvePluginSession(
26
+ config?.session ||
27
+ config?.pluginSession ||
28
+ config?.options?.pluginSession ||
29
+ config?.context?.pluginSession ||
30
+ null
31
+ )
22
32
  }
23
33
 
24
- // eslint-disable-next-line no-empty-pattern
25
- init ({}) {
34
+ // Use BaseService.init/_request/_requireReady implementations
35
+
36
+ // ==================== AUTH METHODS ====================
37
+
38
+ async register(userData, options = {}) {
26
39
  try {
27
- const { authToken, appKey } = this._context || {}
28
-
29
- // Store masked configuration info
30
- this._info = {
31
- config: {
32
- appKey: appKey
33
- ? `${appKey.substr(0, 4)}...${appKey.substr(-4)}`
34
- : undefined, // eslint-disable-line no-undefined
35
- hasToken: Boolean(authToken)
40
+ const { payload, session } = this._preparePluginPayload(
41
+ { ...(userData || {}) },
42
+ options.session
43
+ )
44
+
45
+ const response = await this._request('/auth/register', {
46
+ method: 'POST',
47
+ body: JSON.stringify(payload),
48
+ methodName: 'register'
49
+ })
50
+ if (response.success) {
51
+ if (session) {
52
+ this._clearPluginSession(session)
36
53
  }
54
+ return response.data
37
55
  }
38
-
39
- this._initialized = true
40
- this._setReady()
56
+ throw new Error(response.message)
41
57
  } catch (error) {
42
- this._setError(error)
43
- throw error
58
+ throw new Error(`Registration failed: ${error.message}`, { cause: error })
44
59
  }
45
60
  }
46
61
 
47
- _requiresInit (methodName) {
48
- const noInitMethods = new Set([
49
- 'users:login',
50
- 'users:register',
51
- 'users:request-password-reset',
52
- 'users:reset-password',
53
- 'users:reset-password-confirm',
54
- 'users:register-confirmation',
55
- 'users:google-auth',
56
- 'users:github-auth'
57
- ])
58
- return !noInitMethods.has(methodName)
62
+ async login(email, password, options = {}) {
63
+ try {
64
+ const { payload, session } = this._preparePluginPayload(
65
+ {
66
+ email,
67
+ password
68
+ },
69
+ options.session
70
+ )
71
+
72
+ const response = await this._request('/auth/login', {
73
+ method: 'POST',
74
+ body: JSON.stringify(payload),
75
+ methodName: 'login'
76
+ })
77
+
78
+ // Handle new response format: response.data.tokens
79
+ if (response.success && response.data && response.data.tokens) {
80
+ const { tokens } = response.data
81
+ const tokenData = {
82
+ access_token: tokens.accessToken,
83
+ refresh_token: tokens.refreshToken,
84
+ expires_in: tokens.accessTokenExp?.expiresIn,
85
+ token_type: 'Bearer'
86
+ }
87
+
88
+ // Set tokens in TokenManager (will handle persistence and refresh scheduling)
89
+ if (this._tokenManager) {
90
+ this._tokenManager.setTokens(tokenData)
91
+ }
92
+ }
93
+
94
+ if (response.success) {
95
+ if (session) {
96
+ this._clearPluginSession(session)
97
+ }
98
+ return response.data
99
+ }
100
+ throw new Error(response.message)
101
+ } catch (error) {
102
+ throw new Error(`Login failed: ${error.message}`, { cause: error })
103
+ }
59
104
  }
60
105
 
61
- _requireReady (methodName) {
62
- if (this._requiresInit(methodName) && !this._initialized) {
63
- throw new Error('Service not initialized')
106
+ async logout() {
107
+ this._requireReady('logout')
108
+ try {
109
+ // Call the logout API endpoint
110
+ await this._request('/auth/logout', {
111
+ method: 'POST',
112
+ methodName: 'logout'
113
+ })
114
+
115
+ // Clear tokens from TokenManager and context
116
+ if (this._tokenManager) {
117
+ this._tokenManager.clearTokens()
118
+ }
119
+ } catch (error) {
120
+ // Even if the API call fails, clear local tokens
121
+ if (this._tokenManager) {
122
+ this._tokenManager.clearTokens()
123
+ }
124
+
125
+ throw new Error(`Logout failed: ${error.message}`, { cause: error })
64
126
  }
65
127
  }
66
128
 
67
- _getBasedService (methodName) {
68
- const based = this._context.services?.based
69
- if (this._requiresInit(methodName) && !based) {
70
- throw new Error('Based service not available')
129
+ async refreshToken(refreshToken) {
130
+ try {
131
+ const response = await this._request('/auth/refresh', {
132
+ method: 'POST',
133
+ body: JSON.stringify({ refreshToken }),
134
+ methodName: 'refreshToken'
135
+ })
136
+ if (response.success) {
137
+ return response.data
138
+ }
139
+ throw new Error(response.message)
140
+ } catch (error) {
141
+ throw new Error(`Token refresh failed: ${error.message}`, { cause: error })
71
142
  }
72
- return based._client
73
143
  }
74
144
 
75
- async login (identifier, password) {
145
+ async googleAuth(idToken, inviteToken = null, options = {}) {
76
146
  try {
77
- const based = this._getBasedService('login')
78
- const response = await based.call('users:login', { identifier, password })
79
-
80
- if (this._initialized) {
81
- this.updateContext({ authToken: response.token })
82
- }
83
- based.setAuthState({
84
- token: response.token,
85
- userId: response.userId,
86
- projectRoles: response.projectRoles,
87
- globalRole: response.globalRole,
88
- persistent: true
147
+ const { payload, session } = this._preparePluginPayload({ idToken }, options.session)
148
+ if (inviteToken) {
149
+ payload.inviteToken = inviteToken
150
+ }
151
+
152
+ const response = await this._request('/auth/google', {
153
+ method: 'POST',
154
+ body: JSON.stringify(payload),
155
+ methodName: 'googleAuth'
89
156
  })
90
157
 
91
- return response
158
+ // Handle new response format: response.data.tokens
159
+ if (response.success && response.data && response.data.tokens) {
160
+ const { tokens } = response.data
161
+ const tokenData = {
162
+ access_token: tokens.accessToken,
163
+ refresh_token: tokens.refreshToken,
164
+ expires_in: tokens.accessTokenExp?.expiresIn,
165
+ token_type: 'Bearer'
166
+ }
167
+
168
+ // Set tokens in TokenManager
169
+ if (this._tokenManager) {
170
+ this._tokenManager.setTokens(tokenData)
171
+ }
172
+ }
173
+
174
+ if (response.success) {
175
+ if (session) {
176
+ this._clearPluginSession(session)
177
+ }
178
+ return response.data
179
+ }
180
+ throw new Error(response.message)
92
181
  } catch (error) {
93
- throw new Error(`Login failed: ${error.message}`)
182
+ throw new Error(`Google auth failed: ${error.message}`, { cause: error })
94
183
  }
95
184
  }
96
185
 
97
- async register (userData) {
186
+ async githubAuth(code, inviteToken = null, options = {}) {
98
187
  try {
99
- const based = this._getBasedService('register')
100
- return await based.call('users:register', userData)
188
+ const { payload, session } = this._preparePluginPayload({ code }, options.session)
189
+ if (inviteToken) {
190
+ payload.inviteToken = inviteToken
191
+ }
192
+
193
+ const response = await this._request('/auth/github', {
194
+ method: 'POST',
195
+ body: JSON.stringify(payload),
196
+ methodName: 'githubAuth'
197
+ })
198
+
199
+ // Handle new response format: response.data.tokens
200
+ if (response.success && response.data && response.data.tokens) {
201
+ const { tokens } = response.data
202
+ const tokenData = {
203
+ access_token: tokens.accessToken,
204
+ refresh_token: tokens.refreshToken,
205
+ expires_in: tokens.accessTokenExp?.expiresIn,
206
+ token_type: 'Bearer'
207
+ }
208
+
209
+ // Set tokens in TokenManager
210
+ if (this._tokenManager) {
211
+ this._tokenManager.setTokens(tokenData)
212
+ }
213
+ }
214
+
215
+ if (response.success) {
216
+ if (session) {
217
+ this._clearPluginSession(session)
218
+ }
219
+ return response.data
220
+ }
221
+ throw new Error(response.message)
101
222
  } catch (error) {
102
- throw new Error(`Registration failed: ${error.message}`)
223
+ throw new Error(`GitHub auth failed: ${error.message}`, { cause: error })
103
224
  }
104
225
  }
105
226
 
106
- async googleAuth (idToken) {
227
+ async googleAuthCallback (code, redirectUri, inviteToken = null, options = {}) {
107
228
  try {
108
- const based = this._getBasedService('googleAuth')
109
- const response = await based.call('users:google-auth', { idToken })
229
+ const { payload: body, session } = this._preparePluginPayload(
230
+ { code, redirectUri },
231
+ options.session
232
+ )
233
+ if (inviteToken) {
234
+ body.inviteToken = inviteToken
235
+ }
236
+
237
+ const response = await this._request('/auth/google/callback', {
238
+ method: 'POST',
239
+ body: JSON.stringify(body),
240
+ methodName: 'googleAuthCallback'
241
+ })
242
+
243
+ // Handle new response format: response.data.tokens
244
+ if (response.success && response.data && response.data.tokens) {
245
+ const { tokens } = response.data
246
+ const tokenData = {
247
+ access_token: tokens.accessToken,
248
+ refresh_token: tokens.refreshToken,
249
+ expires_in: tokens.accessTokenExp?.expiresIn,
250
+ token_type: 'Bearer'
251
+ }
110
252
 
111
- if (this._initialized) {
112
- this.updateContext({ authToken: response.token })
253
+ // Set tokens in TokenManager
254
+ if (this._tokenManager) {
255
+ this._tokenManager.setTokens(tokenData)
256
+ }
113
257
  }
114
258
 
115
- based.setAuthState({
116
- token: response.token,
117
- userId: response.userId,
118
- persistent: true
259
+ if (response.success) {
260
+ if (session) {
261
+ this._clearPluginSession(session)
262
+ }
263
+ return response.data
264
+ }
265
+ throw new Error(response.message)
266
+ } catch (error) {
267
+ throw new Error(`Google auth callback failed: ${error.message}`, { cause: error })
268
+ }
269
+ }
270
+
271
+ async requestPasswordReset(email) {
272
+ try {
273
+ const response = await this._request('/auth/request-password-reset', {
274
+ method: 'POST',
275
+ body: JSON.stringify({ email }),
276
+ methodName: 'requestPasswordReset'
119
277
  })
278
+ if (response.success) {
279
+ return response.data
280
+ }
281
+ throw new Error(response.message)
282
+ } catch (error) {
283
+ throw new Error(`Password reset request failed: ${error.message}`, { cause: error })
284
+ }
285
+ }
120
286
 
121
- return response
287
+ async confirmPasswordReset(token, password) {
288
+ try {
289
+ const response = await this._request('/auth/reset-password-confirm', {
290
+ method: 'POST',
291
+ body: JSON.stringify({ token, password }),
292
+ methodName: 'confirmPasswordReset'
293
+ })
294
+ if (response.success) {
295
+ return response.data
296
+ }
297
+ throw new Error(response.message)
122
298
  } catch (error) {
123
- throw new Error(`Google auth failed: ${error.message}`)
299
+ throw new Error(`Password reset confirmation failed: ${error.message}`, { cause: error })
124
300
  }
125
301
  }
126
302
 
127
- async googleAuthCallback (code, redirectUri) {
303
+ async confirmRegistration(token) {
128
304
  try {
129
- const based = this._getBasedService('googleAuthCallback')
130
- const response = await based.call('users:google-auth-callback', {
131
- code,
132
- redirectUri
305
+ const response = await this._request('/auth/register-confirmation', {
306
+ method: 'POST',
307
+ body: JSON.stringify({ token }),
308
+ methodName: 'confirmRegistration'
133
309
  })
310
+ if (response.success) {
311
+ return response.data
312
+ }
313
+ throw new Error(response.message)
314
+ } catch (error) {
315
+ throw new Error(`Registration confirmation failed: ${error.message}`, { cause: error })
316
+ }
317
+ }
134
318
 
135
- if (this._initialized) {
136
- this.updateContext({ authToken: response.token })
319
+ async requestPasswordChange() {
320
+ this._requireReady('requestPasswordChange')
321
+ try {
322
+ const response = await this._request('/auth/request-password-change', {
323
+ method: 'POST',
324
+ methodName: 'requestPasswordChange'
325
+ })
326
+ if (response.success) {
327
+ return response.data
137
328
  }
329
+ throw new Error(response.message)
330
+ } catch (error) {
331
+ throw new Error(`Password change request failed: ${error.message}`, { cause: error })
332
+ }
333
+ }
138
334
 
139
- based.setAuthState({
140
- token: response.token,
141
- userId: response.userId,
142
- persistent: true
335
+ async confirmPasswordChange(currentPassword, newPassword, code) {
336
+ this._requireReady('confirmPasswordChange')
337
+ try {
338
+ const response = await this._request('/auth/confirm-password-change', {
339
+ method: 'POST',
340
+ body: JSON.stringify({ currentPassword, newPassword, code }),
341
+ methodName: 'confirmPasswordChange'
143
342
  })
144
- return response
343
+ if (response.success) {
344
+ return response.data
345
+ }
346
+ throw new Error(response.message)
145
347
  } catch (error) {
146
- throw new Error(`Google auth callback failed: ${error.message}`)
348
+ throw new Error(`Password change confirmation failed: ${error.message}`, { cause: error })
147
349
  }
148
350
  }
149
351
 
150
- async githubAuth (code) {
352
+ async getMe(options = {}) {
353
+ this._requireReady('getMe')
151
354
  try {
152
- const based = this._getBasedService('githubAuth')
153
- const response = await based.call('users:github-auth', { code })
355
+ const session = this._resolvePluginSession(options.session)
356
+ const endpoint = session
357
+ ? `/auth/me?session=${encodeURIComponent(session)}`
358
+ : '/auth/me'
359
+
360
+ const response = await this._request(endpoint, {
361
+ method: 'GET',
362
+ methodName: 'getMe'
363
+ })
364
+ if (response.success) {
365
+ return response.data
366
+ }
367
+ throw new Error(response.message)
368
+ } catch (error) {
369
+ throw new Error(`Failed to get user profile: ${error.message}`, { cause: error })
370
+ }
371
+ }
372
+
373
+ getAuthToken() {
374
+ if (!this._tokenManager) {
375
+ return null
376
+ }
377
+ return this._tokenManager.getAccessToken()
378
+ }
379
+
380
+ /**
381
+ * Get stored authentication state (backward compatibility method)
382
+ * Replaces AuthService.getStoredAuthState()
383
+ */
384
+ async getStoredAuthState() {
385
+ try {
386
+ if (!this._tokenManager) {
387
+ return {
388
+ userId: false,
389
+ authToken: false
390
+ }
391
+ }
392
+
393
+ const tokenStatus = this._tokenManager.getTokenStatus()
394
+
395
+ if (!tokenStatus.hasTokens) {
396
+ return {
397
+ userId: false,
398
+ authToken: false
399
+ }
400
+ }
401
+
402
+ // If tokens exist but are invalid, try to refresh
403
+ if (!tokenStatus.isValid && tokenStatus.hasRefreshToken) {
404
+ try {
405
+ await this._tokenManager.ensureValidToken()
406
+ } catch (error) {
407
+ console.warn('[AuthService] Token refresh failed:', error.message)
408
+ // Only clear tokens if it's definitely an auth error, not a network error
409
+ if (
410
+ error.message.includes('401') ||
411
+ error.message.includes('403') ||
412
+ error.message.includes('invalid') ||
413
+ error.message.includes('expired')
414
+ ) {
415
+ this._tokenManager.clearTokens()
416
+ return {
417
+ userId: false,
418
+ authToken: false,
419
+ error: `Authentication failed: ${error.message}`
420
+ }
421
+ }
422
+ // For network errors, keep tokens and return what we have
423
+ return {
424
+ userId: false,
425
+ authToken: this._tokenManager.getAccessToken(),
426
+ error: `Network error during token refresh: ${error.message}`,
427
+ hasTokens: true
428
+ }
429
+ }
430
+ }
431
+
432
+ // Check if we have a valid token now
433
+ const currentAccessToken = this._tokenManager.getAccessToken()
434
+ if (!currentAccessToken) {
435
+ return {
436
+ userId: false,
437
+ authToken: false
438
+ }
439
+ }
440
+
441
+ // Get current user data if we have valid tokens
442
+ // Be more lenient with API failures - don't immediately clear tokens
443
+ try {
444
+ const currentUser = await this.getMe()
445
+
446
+ return {
447
+ userId: currentUser.user.id,
448
+ authToken: currentAccessToken,
449
+ ...currentUser,
450
+ error: null
451
+ }
452
+ } catch (error) {
453
+ console.warn('[AuthService] Failed to get user data:', error.message)
454
+
455
+ // Only clear tokens if it's an auth error (401, 403), not network errors
456
+ if (error.message.includes('401') || error.message.includes('403')) {
457
+ this._tokenManager.clearTokens()
458
+ return {
459
+ userId: false,
460
+ authToken: false,
461
+ error: `Authentication failed: ${error.message}`
462
+ }
463
+ }
154
464
 
155
- if (this._initialized) {
156
- this.updateContext({ authToken: response.token })
465
+ // For other errors (network, 500, etc.), keep tokens but return minimal state
466
+ return {
467
+ userId: false,
468
+ authToken: currentAccessToken,
469
+ error: `Failed to get user data: ${error.message}`,
470
+ hasTokens: true
471
+ }
472
+ }
473
+ } catch (error) {
474
+ console.error(
475
+ '[AuthService] Unexpected error in getStoredAuthState:',
476
+ error
477
+ )
478
+ return {
479
+ userId: false,
480
+ authToken: false,
481
+ error: `Failed to get stored auth state: ${error.message}`
157
482
  }
483
+ }
484
+ }
158
485
 
159
- based.setAuthState({
160
- token: response.token,
161
- userId: response.userId,
162
- persistent: true
486
+ // ==================== USER METHODS ====================
487
+
488
+ async getUserProfile() {
489
+ this._requireReady('getUserProfile')
490
+ try {
491
+ const response = await this._request('/users/profile', {
492
+ method: 'GET',
493
+ methodName: 'getUserProfile'
163
494
  })
495
+ if (response.success) {
496
+ return response.data
497
+ }
498
+ throw new Error(response.message)
499
+ } catch (error) {
500
+ throw new Error(`Failed to get user profile: ${error.message}`, { cause: error })
501
+ }
502
+ }
164
503
 
165
- return response
504
+ async updateUserProfile(profileData) {
505
+ this._requireReady('updateUserProfile')
506
+ try {
507
+ const response = await this._request('/users/profile', {
508
+ method: 'PATCH',
509
+ body: JSON.stringify(profileData),
510
+ methodName: 'updateUserProfile'
511
+ })
512
+ if (response.success) {
513
+ return response.data
514
+ }
515
+ throw new Error(response.message)
166
516
  } catch (error) {
167
- throw new Error(`GitHub auth failed: ${error.message}`)
517
+ throw new Error(`Failed to update user profile: ${error.message}`, { cause: error })
168
518
  }
169
519
  }
170
520
 
171
- async logout () {
172
- this._requireReady('logout')
521
+ async getUserProjects() {
522
+ this._requireReady('getUserProjects')
173
523
  try {
174
- const based = this._getBasedService('logout')
175
- await based.call('users:logout')
176
- this.updateContext({ authToken: null })
524
+ const response = await this._request('/users/projects', {
525
+ method: 'GET',
526
+ methodName: 'getUserProjects'
527
+ })
528
+ if (response.success) {
529
+ return response.data.map((project) => ({
530
+ ...project,
531
+ ...(project.icon && {
532
+ icon: {
533
+ src: `${this._apiUrl}/core/files/public/${project.icon.id}/download`,
534
+ ...project.icon
535
+ }
536
+ })
537
+ }))
538
+ }
539
+ throw new Error(response.message)
177
540
  } catch (error) {
178
- throw new Error(`Logout failed: ${error.message}`)
541
+ throw new Error(`Failed to get user projects: ${error.message}`, { cause: error })
179
542
  }
180
543
  }
181
544
 
182
- async updateUserRole (userId, newRole) {
183
- this._requireReady('updateUserRole')
545
+ async getUser(userId) {
546
+ this._requireReady('getUser')
184
547
  if (!userId) {
185
548
  throw new Error('User ID is required')
186
549
  }
187
- if (!this._userRoles.has(newRole)) {
188
- throw new Error(`Invalid role: ${newRole}`)
550
+ try {
551
+ const response = await this._request(`/users/${userId}`, {
552
+ method: 'GET',
553
+ methodName: 'getUser'
554
+ })
555
+ if (response.success) {
556
+ return response.data
557
+ }
558
+ throw new Error(response.message)
559
+ } catch (error) {
560
+ throw new Error(`Failed to get user: ${error.message}`, { cause: error })
561
+ }
562
+ }
563
+
564
+ async getUserByEmail(email) {
565
+ this._requireReady('getUserByEmail')
566
+ if (!email) {
567
+ throw new Error('Email is required')
189
568
  }
190
569
  try {
191
- const based = this._getBasedService('updateUserRole')
192
- return await based.call('users:update-role', { userId, role: newRole })
570
+ const response = await this._request(`/auth/user?email=${email}`, {
571
+ method: 'GET',
572
+ methodName: 'getUserByEmail'
573
+ })
574
+ if (response.success) {
575
+ return response.data.user
576
+ }
577
+ throw new Error(response.message)
193
578
  } catch (error) {
194
- throw new Error(`Failed to update user role: ${error.message}`)
579
+ throw new Error(`Failed to get user by email: ${error.message}`, { cause: error })
195
580
  }
196
581
  }
197
582
 
198
- async updateProjectTier (projectId, newTier) {
199
- this._requireReady('updateProjectTier')
583
+ // ==================== PROJECT ROLE METHODS ====================
584
+
585
+ /**
586
+ * Get the current user's role for a specific project by project ID
587
+ * Uses caching to avoid repeated API calls
588
+ */
589
+ async getMyProjectRole(projectId) {
590
+ this._requireReady('getMyProjectRole')
200
591
  if (!projectId) {
201
592
  throw new Error('Project ID is required')
202
593
  }
203
- if (!this._projectTiers.has(newTier)) {
204
- throw new Error(`Invalid project tier: ${newTier}`)
594
+
595
+ // If there are no valid tokens, treat user as guest for public access
596
+ if (!this.hasValidTokens()) {
597
+ return 'guest'
205
598
  }
599
+
600
+ // Check cache first
601
+ const cacheKey = `role_${projectId}`
602
+ const cached = this._projectRoleCache.get(cacheKey)
603
+
604
+ if (cached && Date.now() - cached.timestamp < this._roleCacheExpiry) {
605
+ return cached.role
606
+ }
607
+
206
608
  try {
207
- const based = this._getBasedService('updateProjectTier')
208
- return await based.call('projects:update-tier', {
209
- projectId,
210
- tier: newTier
609
+ const response = await this._request(`/projects/${projectId}/role`, {
610
+ method: 'GET',
611
+ methodName: 'getMyProjectRole'
211
612
  })
613
+
614
+ if (response.success) {
615
+ const { role } = response.data
616
+ // Cache the result
617
+ this._projectRoleCache.set(cacheKey, {
618
+ role,
619
+ timestamp: Date.now()
620
+ })
621
+ return role
622
+ }
623
+ throw new Error(response.message)
212
624
  } catch (error) {
213
- throw new Error(`Failed to update project tier: ${error.message}`)
625
+ const message = error?.message || ''
626
+ // If request failed due to missing/invalid auth, default to guest
627
+ if (/401|403|unauthorized|no token|invalid token/iu.test(message)) {
628
+ return 'guest'
629
+ }
630
+ throw new Error(`Failed to get project role: ${message}`, { cause: error })
214
631
  }
215
632
  }
216
633
 
217
- hasPermission (requiredPermission) {
634
+ /**
635
+ * Get the current user's role for a specific project by project key
636
+ * Uses caching to avoid repeated API calls
637
+ */
638
+ async getMyProjectRoleByKey(projectKey) {
639
+ this._requireReady('getMyProjectRoleByKey')
640
+ if (!projectKey) {
641
+ throw new Error('Project key is required')
642
+ }
643
+
644
+ // If there are no valid tokens, treat user as guest for public access
645
+ if (!this.hasValidTokens()) {
646
+ return 'guest'
647
+ }
648
+
649
+ // Check cache first
650
+ const cacheKey = `role_key_${projectKey}`
651
+ const cached = this._projectRoleCache.get(cacheKey)
652
+
653
+ if (cached && Date.now() - cached.timestamp < this._roleCacheExpiry) {
654
+ return cached.role
655
+ }
656
+
657
+ try {
658
+ const response = await this._request(`/projects/key/${projectKey}/role`, {
659
+ method: 'GET',
660
+ methodName: 'getMyProjectRoleByKey'
661
+ })
662
+
663
+ if (response.success) {
664
+ const { role } = response.data
665
+ // Cache the result
666
+ this._projectRoleCache.set(cacheKey, {
667
+ role,
668
+ timestamp: Date.now()
669
+ })
670
+ return role
671
+ }
672
+ throw new Error(response.message)
673
+ } catch (error) {
674
+ const message = error?.message || ''
675
+ // If request failed due to missing/invalid auth, default to guest
676
+ if (/401|403|unauthorized|no token|invalid token/iu.test(message)) {
677
+ return 'guest'
678
+ }
679
+ throw new Error(`Failed to get project role by key: ${message}`, { cause: error })
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Clear the project role cache for a specific project or all projects
685
+ */
686
+ clearProjectRoleCache(projectId = null) {
687
+ if (projectId) {
688
+ // Clear specific project cache
689
+ this._projectRoleCache.delete(`role_${projectId}`)
690
+ // Also clear by key if we have it cached
691
+ for (const [key] of this._projectRoleCache) {
692
+ if (key.startsWith('role_key_')) {
693
+ this._projectRoleCache.delete(key)
694
+ }
695
+ }
696
+ } else {
697
+ // Clear all cache
698
+ this._projectRoleCache.clear()
699
+ }
700
+ }
701
+
702
+ /**
703
+ * Get project role with fallback to user projects list
704
+ * This method tries to get the role from user projects first,
705
+ * then falls back to API call if not found
706
+ */
707
+ async getProjectRoleWithFallback(projectId, userProjects = null) {
708
+ this._requireReady('getProjectRoleWithFallback')
709
+ if (!projectId) {
710
+ throw new Error('Project ID is required')
711
+ }
712
+
713
+ // First try to find in user projects if provided
714
+ if (userProjects && Array.isArray(userProjects)) {
715
+ const userProject = userProjects.find(p => p.id === projectId)
716
+ if (userProject && userProject.role) {
717
+ return userProject.role
718
+ }
719
+ }
720
+
721
+ // Fallback to API call
722
+ return await this.getMyProjectRole(projectId)
723
+ }
724
+
725
+ /**
726
+ * Get project role with fallback to user projects list (by project key)
727
+ * This method tries to get the role from user projects first,
728
+ * then falls back to API call if not found
729
+ */
730
+ async getProjectRoleByKeyWithFallback(projectKey, userProjects = null) {
731
+ this._requireReady('getProjectRoleByKeyWithFallback')
732
+ if (!projectKey) {
733
+ throw new Error('Project key is required')
734
+ }
735
+
736
+ // First try to find in user projects if provided
737
+ if (userProjects && Array.isArray(userProjects)) {
738
+ const userProject = userProjects.find(p => p.key === projectKey)
739
+ if (userProject && userProject.role) {
740
+ return userProject.role
741
+ }
742
+ }
743
+
744
+ // Fallback to API call
745
+ return await this.getMyProjectRoleByKey(projectKey)
746
+ }
747
+
748
+ // ==================== AUTH HELPER METHODS ====================
749
+
750
+ /**
751
+ * Debug method to check token status
752
+ */
753
+ getTokenDebugInfo() {
754
+ if (!this._tokenManager) {
755
+ return {
756
+ tokenManagerExists: false,
757
+ error: 'TokenManager not initialized'
758
+ }
759
+ }
760
+
761
+ const tokenStatus = this._tokenManager.getTokenStatus()
762
+ const { tokens } = this._tokenManager
763
+
764
+ return {
765
+ tokenManagerExists: true,
766
+ tokenStatus,
767
+ hasAccessToken: Boolean(tokens.accessToken),
768
+ hasRefreshToken: Boolean(tokens.refreshToken),
769
+ accessTokenPreview: tokens.accessToken
770
+ ? `${tokens.accessToken.substring(0, 20)}...`
771
+ : null,
772
+ expiresAt: tokens.expiresAt,
773
+ timeToExpiry: tokenStatus.timeToExpiry,
774
+ authHeader: this._tokenManager.getAuthHeader()
775
+ }
776
+ }
777
+
778
+ /**
779
+ * Helper method to check if user is authenticated
780
+ */
781
+ isAuthenticated() {
782
+ if (!this._tokenManager) {
783
+ return false
784
+ }
785
+ return this._tokenManager.hasTokens()
786
+ }
787
+
788
+ /**
789
+ * Helper method to check if user has valid tokens
790
+ */
791
+ hasValidTokens() {
792
+ if (!this._tokenManager) {
793
+ return false
794
+ }
795
+ return (
796
+ this._tokenManager.hasTokens() && this._tokenManager.isAccessTokenValid()
797
+ )
798
+ }
799
+
800
+ /**
801
+ * Helper method to get current user info
802
+ */
803
+ async getCurrentUser() {
804
+ try {
805
+ return await this.getMe()
806
+ } catch (error) {
807
+ throw new Error(`Failed to get current user: ${error.message}`, { cause: error })
808
+ }
809
+ }
810
+
811
+ /**
812
+ * Helper method to validate user data for registration
813
+ */
814
+ validateRegistrationData(userData) {
815
+ const errors = []
816
+
817
+ if (!userData.email || typeof userData.email !== 'string') {
818
+ errors.push('Email is required and must be a string')
819
+ } else if (!this._isValidEmail(userData.email)) {
820
+ errors.push('Email must be a valid email address')
821
+ }
822
+
823
+ if (!userData.password || typeof userData.password !== 'string') {
824
+ errors.push('Password is required and must be a string')
825
+ } else if (userData.password.length < 8) {
826
+ errors.push('Password must be at least 8 characters long')
827
+ }
828
+
829
+ if (userData.username && typeof userData.username !== 'string') {
830
+ errors.push('Username must be a string')
831
+ } else if (userData.username && userData.username.length < 3) {
832
+ errors.push('Username must be at least 3 characters long')
833
+ }
834
+
835
+ return {
836
+ isValid: errors.length === 0,
837
+ errors
838
+ }
839
+ }
840
+
841
+ /**
842
+ * Helper method to register with validation
843
+ */
844
+ async registerWithValidation(userData, options = {}) {
845
+ const validation = this.validateRegistrationData(userData)
846
+ if (!validation.isValid) {
847
+ throw new Error(`Validation failed: ${validation.errors.join(', ')}`)
848
+ }
849
+
850
+ return await this.register(userData, options)
851
+ }
852
+
853
+ /**
854
+ * Helper method to login with validation
855
+ */
856
+ async loginWithValidation(email, password, options = {}) {
857
+ if (!email || typeof email !== 'string') {
858
+ throw new Error('Email is required and must be a string')
859
+ }
860
+
861
+ if (!password || typeof password !== 'string') {
862
+ throw new Error('Password is required and must be a string')
863
+ }
864
+
865
+ if (!this._isValidEmail(email)) {
866
+ throw new Error('Email must be a valid email address')
867
+ }
868
+
869
+ return await this.login(email, password, options)
870
+ }
871
+
872
+ /**
873
+ * Private helper to validate email format
874
+ */
875
+ _isValidEmail(email) {
876
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u
877
+ return emailRegex.test(email)
878
+ }
879
+
880
+ // ==================== PERMISSION METHODS (Existing) ====================
881
+
882
+ hasPermission(requiredPermission) {
218
883
  const authState = this._context?.state
219
884
  if (!authState) {
220
885
  return false
@@ -230,21 +895,21 @@ export class AuthService extends BaseService {
230
895
  )
231
896
  }
232
897
 
233
- hasGlobalPermission (globalRole, requiredPermission) {
898
+ hasGlobalPermission(globalRole, requiredPermission) {
234
899
  return ROLE_PERMISSIONS[globalRole]?.includes(requiredPermission) || false
235
900
  }
236
901
 
237
- checkProjectPermission (projectRole, requiredPermission) {
902
+ checkProjectPermission(projectRole, requiredPermission) {
238
903
  return (
239
904
  PROJECT_ROLE_PERMISSIONS[projectRole]?.includes(requiredPermission) ||
240
905
  false
241
906
  )
242
907
  }
243
908
 
244
- checkProjectFeature (projectTier, feature) {
909
+ checkProjectFeature(projectTier, feature) {
245
910
  if (feature.startsWith('aiCopilot') || feature.startsWith('aiChatbot')) {
246
911
  const [featureBase] = feature.split(':')
247
- const tierFeature = TIER_FEATURES[projectTier]?.find(f =>
912
+ const tierFeature = TIER_FEATURES[projectTier]?.find((f) =>
248
913
  f.startsWith(featureBase)
249
914
  )
250
915
  if (!tierFeature) {
@@ -258,7 +923,7 @@ export class AuthService extends BaseService {
258
923
  }
259
924
 
260
925
  // Operation checking
261
- async canPerformOperation (projectId, operation, options = {}) {
926
+ async canPerformOperation(projectId, operation, options = {}) {
262
927
  this._requireReady()
263
928
  if (!projectId) {
264
929
  throw new Error('Project ID is required')
@@ -270,17 +935,14 @@ export class AuthService extends BaseService {
270
935
  if (!operationConfig) {
271
936
  return false
272
937
  }
273
- if (!operationConfig) {
274
- return false
275
- }
276
938
 
277
939
  const { permissions = [], features = [] } = operationConfig
278
940
 
279
941
  try {
280
942
  // Check permissions
281
943
  const permissionResults = await Promise.all(
282
- permissions.map(permission =>
283
- this.hasProjectPermission(projectId, permission)
944
+ permissions.map((permission) =>
945
+ this.checkProjectPermission(projectId, permission)
284
946
  )
285
947
  )
286
948
 
@@ -291,13 +953,10 @@ export class AuthService extends BaseService {
291
953
  if (!hasPermissions) {
292
954
  return false
293
955
  }
294
- if (!hasPermissions) {
295
- return false
296
- }
297
956
 
298
957
  // Check features if required
299
958
  if (checkFeatures && features.length > 0) {
300
- const featureResults = features.map(feature => {
959
+ const featureResults = features.map((feature) => {
301
960
  const result = this.hasProjectFeature(projectId, feature)
302
961
  return feature.includes(':')
303
962
  ? typeof result === 'number' && result > 0
@@ -311,9 +970,6 @@ export class AuthService extends BaseService {
311
970
  if (!hasFeatures) {
312
971
  return false
313
972
  }
314
- if (!hasFeatures) {
315
- return false
316
- }
317
973
  }
318
974
 
319
975
  return true
@@ -324,7 +980,7 @@ export class AuthService extends BaseService {
324
980
  }
325
981
 
326
982
  // Higher-level permission methods
327
- async withPermission (projectId, operation, action) {
983
+ async withPermission(projectId, operation, action) {
328
984
  this._requireReady()
329
985
  if (!projectId) {
330
986
  throw new Error('Project ID is required')
@@ -340,225 +996,115 @@ export class AuthService extends BaseService {
340
996
  return action()
341
997
  }
342
998
 
343
- // Project access information
344
- async getProjectAccess (projectId) {
345
- this._requireReady()
346
- if (!projectId) {
347
- throw new Error('Project ID is required')
999
+ // Cleanup
1000
+ destroy() {
1001
+ if (this._tokenManager) {
1002
+ this._tokenManager.destroy()
1003
+ this._tokenManager = null
348
1004
  }
1005
+ // Clear project role cache
1006
+ this._projectRoleCache.clear()
1007
+ this._setReady(false)
1008
+ }
349
1009
 
350
- const operations = Object.keys(PERMISSION_MAP)
351
-
352
- const access = await Promise.all(
353
- operations.map(async operation => {
354
- const allowed = await this.canPerformOperation(projectId, operation)
355
- const config = PERMISSION_MAP[operation]
1010
+ _preparePluginPayload(payload, sessionOverride = null) {
1011
+ const target =
1012
+ payload && typeof payload === 'object'
1013
+ ? { ...payload }
1014
+ : {}
356
1015
 
357
- return {
358
- operation,
359
- allowed,
360
- permissions: config.permissions,
361
- features: config.features,
362
- aiTokens: operation.startsWith('ai')
363
- ? this._getAITokens(projectId, operation.replace('ai', ''))
364
- : null
365
- }
366
- })
367
- )
1016
+ const session = this._resolvePluginSession(sessionOverride)
368
1017
 
369
- return {
370
- projectId,
371
- permissions: access.reduce(
372
- (acc, { operation, ...details }) => ({
373
- ...acc,
374
- [operation]: details
375
- }),
376
- {}
377
- ),
378
- timestamp: new Date().toISOString()
379
- }
380
- }
381
-
382
- // AI token management
383
- _getAITokens (projectId, featureType) {
384
- const tokenFeatures = [
385
- `ai${featureType}:3`,
386
- `ai${featureType}:5`,
387
- `ai${featureType}:15`
388
- ]
389
-
390
- return tokenFeatures.reduce((total, feature) => {
391
- const tokens = this.hasProjectFeature(projectId, feature)
392
- return total + (typeof tokens === 'number' ? tokens : 0)
393
- }, 0)
394
- }
395
-
396
- async getProjectMembers (projectId) {
397
- this._requireReady('getProjectMembers')
398
- if (!projectId) {
399
- throw new Error('Project ID is required')
400
- }
401
- try {
402
- const based = this._getBasedService('getProjectMembers')
403
- return await based.call('projects:get-members', { projectId })
404
- } catch (error) {
405
- if (error.message?.includes('Authentication failed. Please try again'))
406
- window.location.reload()
407
- throw new Error(`Failed to get project members: ${error.message}`)
1018
+ if (session && !Object.hasOwn(target, 'session')) {
1019
+ target.session = session
1020
+ return { payload: target, session }
408
1021
  }
409
- }
410
1022
 
411
- async inviteMember (projectId, email, role, name, callbackUrl) {
412
- this._requireReady('inviteMember')
413
- if (!projectId) {
414
- throw new Error('Project ID is required')
415
- }
416
- if (!email) {
417
- throw new Error('Email is required')
418
- }
419
- if (!callbackUrl || Object.keys(callbackUrl).length === 0) {
420
- throw new Error('Callback Url is required')
421
- }
422
- if (!role || !this._userRoles.has(role)) {
423
- throw new Error(`Invalid role: ${role}`)
424
- }
425
- try {
426
- const based = this._getBasedService('inviteMember')
427
- return await based.call('projects:invite-member', {
428
- projectId,
429
- email,
430
- role,
431
- name,
432
- callbackUrl
433
- })
434
- } catch (error) {
435
- throw new Error(`Failed to invite member: ${error.message}`)
436
- }
1023
+ return { payload: target, session: null }
437
1024
  }
438
1025
 
439
- async acceptInvite (token) {
440
- this._requireReady('acceptInvite')
441
- try {
442
- const based = this._getBasedService('acceptInvite')
443
- return await based.call('projects:accept-invite', { token })
444
- } catch (error) {
445
- throw new Error(`Failed to accept invite: ${error.message}`)
1026
+ _resolvePluginSession(sessionOverride = null) {
1027
+ if (sessionOverride) {
1028
+ return this._cachePluginSession(sessionOverride)
446
1029
  }
447
- }
448
1030
 
449
- async updateMemberRole (projectId, userId, role) {
450
- this._requireReady('updateMemberRole')
451
- if (!projectId) {
452
- throw new Error('Project ID is required')
453
- }
454
- if (!userId) {
455
- throw new Error('User ID is required')
456
- }
457
- if (!this._userRoles.has(role)) {
458
- throw new Error(`Invalid role: ${role}`)
1031
+ if (this._pluginSession) {
1032
+ return this._pluginSession
459
1033
  }
460
- try {
461
- const based = this._getBasedService('updateMemberRole')
462
- return await based.call('projects:update-member-role', {
463
- projectId,
464
- userId,
465
- role
466
- })
467
- } catch (error) {
468
- throw new Error(`Failed to update member role: ${error.message}`)
469
- }
470
- }
471
1034
 
472
- async removeMember (projectId, userId) {
473
- this._requireReady('removeMember')
474
- if (!projectId || !userId) {
475
- throw new Error('Project ID and user ID are required')
1035
+ const optionSession = this._options?.pluginSession
1036
+ if (optionSession) {
1037
+ return this._cachePluginSession(optionSession)
476
1038
  }
477
- try {
478
- const based = this._getBasedService('removeMember')
479
- return await based.call('projects:remove-member', { projectId, userId })
480
- } catch (error) {
481
- throw new Error(`Failed to remove member: ${error.message}`)
482
- }
483
- }
484
1039
 
485
- async confirmRegistration (token) {
486
- try {
487
- const based = this._getBasedService('confirmRegistration')
488
- return await based.call('users:register-confirmation', { token })
489
- } catch (error) {
490
- throw new Error(`Registration confirmation failed: ${error.message}`)
1040
+ const contextSession = this._context?.pluginSession
1041
+ if (contextSession) {
1042
+ return this._cachePluginSession(contextSession)
491
1043
  }
492
- }
493
1044
 
494
- async requestPasswordReset (email, callbackUrl) {
495
- try {
496
- const based = this._getBasedService('requestPasswordReset')
497
- return await based.call('users:reset-password', { email, callbackUrl })
498
- } catch (error) {
499
- throw new Error(`Password reset request failed: ${error.message}`)
1045
+ if (typeof window !== 'undefined') {
1046
+ try {
1047
+ const sessionFromUrl = new URL(window.location.href).searchParams.get('session')
1048
+ if (sessionFromUrl) {
1049
+ return this._cachePluginSession(sessionFromUrl)
1050
+ }
1051
+ } catch {
1052
+ // Ignore URL parsing errors
1053
+ }
1054
+
1055
+ try {
1056
+ if (window.localStorage) {
1057
+ const stored = window.localStorage.getItem(PLUGIN_SESSION_STORAGE_KEY)
1058
+ if (stored) {
1059
+ this._pluginSession = stored
1060
+ return stored
1061
+ }
1062
+ }
1063
+ } catch {
1064
+ // Ignore storage access issues
1065
+ }
500
1066
  }
1067
+
1068
+ return null
501
1069
  }
502
1070
 
503
- async confirmPasswordReset (token, newPassword) {
504
- try {
505
- const based = this._getBasedService('confirmPasswordReset')
506
- return await based.call('users:reset-password-confirm', {
507
- token,
508
- newPassword
509
- })
510
- } catch (error) {
511
- throw new Error(`Password reset confirmation failed: ${error.message}`)
1071
+ _cachePluginSession(session) {
1072
+ if (!session) {
1073
+ return null
512
1074
  }
513
- }
514
1075
 
515
- async getStoredAuthState () {
516
- try {
517
- const based = this._getBasedService('getStoredAuthState')
518
- const { authState } = based
1076
+ this._pluginSession = session
519
1077
 
520
- if (authState?.token) {
521
- return {
522
- userId: authState.userId,
523
- authToken: authState.token,
524
- projectRoles: authState.projectRoles,
525
- globalRole: authState.globalRole,
526
- error: null
1078
+ if (typeof window !== 'undefined') {
1079
+ try {
1080
+ if (window.localStorage) {
1081
+ window.localStorage.setItem(PLUGIN_SESSION_STORAGE_KEY, session)
527
1082
  }
528
- }
529
-
530
- return {
531
- userId: false,
532
- authToken: false
533
- }
534
- } catch (error) {
535
- this._setError(error)
536
- return {
537
- userId: false,
538
- authToken: false,
539
- error: `Failed to get stored auth state: ${error.message}`
1083
+ } catch {
1084
+ // Ignore storage access issues
540
1085
  }
541
1086
  }
1087
+
1088
+ return session
542
1089
  }
543
1090
 
544
- async subscribeToAuthChanges (callback) {
545
- const based = this._getBasedService('subscribeToAuthChanges')
546
- based.on('authstate-change', async authState => {
547
- const formattedState = authState?.token
548
- ? {
549
- userId: authState.userId,
550
- authToken: authState.token,
551
- projectRoles: authState.projectRoles,
552
- globalRole: authState.globalRole,
553
- error: null
554
- }
555
- : {
556
- userId: false,
557
- authToken: false
558
- }
559
- await callback(formattedState)
560
- })
1091
+ setPluginSession(session) {
1092
+ this._cachePluginSession(session)
1093
+ }
561
1094
 
562
- return () => based.off('authstate-change')
1095
+ _clearPluginSession(session = null) {
1096
+ if (!session || this._pluginSession === session) {
1097
+ this._pluginSession = null
1098
+ }
1099
+
1100
+ if (typeof window !== 'undefined') {
1101
+ try {
1102
+ if (window.localStorage) {
1103
+ window.localStorage.removeItem(PLUGIN_SESSION_STORAGE_KEY)
1104
+ }
1105
+ } catch {
1106
+ // Ignore storage access issues
1107
+ }
1108
+ }
563
1109
  }
564
1110
  }