@symbo.ls/sdk 2.34.35 → 3.1.2

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 (187) hide show
  1. package/README.md +2 -143
  2. package/dist/cjs/config/environment.js +30 -98
  3. package/dist/cjs/index.js +24 -144
  4. package/dist/cjs/services/AIService.js +155 -0
  5. package/dist/cjs/services/AuthService.js +305 -738
  6. package/dist/cjs/services/BaseService.js +6 -158
  7. package/dist/cjs/services/BasedService.js +1185 -0
  8. package/dist/cjs/services/CoreService.js +1751 -0
  9. package/dist/cjs/services/SocketIOService.js +307 -0
  10. package/dist/cjs/services/SocketService.js +161 -0
  11. package/dist/cjs/services/SymstoryService.js +571 -0
  12. package/dist/cjs/services/index.js +16 -64
  13. package/dist/cjs/utils/TokenManager.js +30 -78
  14. package/dist/cjs/utils/basedQuerys.js +181 -0
  15. package/dist/cjs/utils/services.js +103 -301
  16. package/dist/cjs/utils/symstoryClient.js +259 -0
  17. package/dist/cjs/utils/validation.js +3 -0
  18. package/dist/esm/config/environment.js +30 -98
  19. package/dist/esm/index.js +8797 -49416
  20. package/dist/esm/services/AIService.js +185 -0
  21. package/dist/esm/services/AuthService.js +386 -1493
  22. package/dist/esm/services/BaseService.js +6 -757
  23. package/dist/esm/services/BasedService.js +5278 -0
  24. package/dist/esm/services/CoreService.js +2264 -0
  25. package/dist/esm/services/SocketIOService.js +470 -0
  26. package/dist/esm/services/SocketService.js +191 -0
  27. package/dist/esm/services/SymstoryService.js +7041 -0
  28. package/dist/esm/services/index.js +8690 -49015
  29. package/dist/esm/utils/TokenManager.js +30 -78
  30. package/dist/esm/utils/basedQuerys.js +163 -0
  31. package/dist/esm/utils/services.js +103 -301
  32. package/dist/esm/utils/symstoryClient.js +370 -0
  33. package/dist/esm/utils/validation.js +7 -4
  34. package/dist/node/config/environment.js +30 -98
  35. package/dist/node/index.js +32 -175
  36. package/dist/node/services/AIService.js +136 -0
  37. package/dist/node/services/AuthService.js +310 -742
  38. package/dist/node/services/BaseService.js +6 -148
  39. package/dist/node/services/BasedService.js +1156 -0
  40. package/dist/node/services/CoreService.js +1722 -0
  41. package/dist/node/services/SocketIOService.js +278 -0
  42. package/dist/node/services/SocketService.js +142 -0
  43. package/dist/node/services/SymstoryService.js +542 -0
  44. package/dist/node/services/index.js +16 -64
  45. package/dist/node/utils/TokenManager.js +30 -78
  46. package/dist/node/utils/basedQuerys.js +162 -0
  47. package/dist/node/utils/services.js +103 -301
  48. package/dist/node/utils/symstoryClient.js +230 -0
  49. package/dist/node/utils/validation.js +3 -0
  50. package/package.json +16 -35
  51. package/src/config/environment.js +28 -99
  52. package/src/index.js +36 -181
  53. package/src/services/AIService.js +150 -0
  54. package/src/services/AuthService.js +328 -874
  55. package/src/services/BaseService.js +6 -166
  56. package/src/services/BasedService.js +1301 -0
  57. package/src/services/CoreService.js +1943 -0
  58. package/src/services/SocketIOService.js +334 -0
  59. package/src/services/SocketService.js +168 -0
  60. package/src/services/SymstoryService.js +649 -0
  61. package/src/services/index.js +13 -80
  62. package/src/utils/TokenManager.js +33 -88
  63. package/src/utils/basedQuerys.js +164 -0
  64. package/src/utils/services.js +107 -326
  65. package/src/utils/symstoryClient.js +252 -0
  66. package/src/utils/validation.js +3 -0
  67. package/dist/cjs/services/AdminService.js +0 -351
  68. package/dist/cjs/services/BranchService.js +0 -484
  69. package/dist/cjs/services/CollabService.js +0 -743
  70. package/dist/cjs/services/DnsService.js +0 -340
  71. package/dist/cjs/services/FeatureFlagService.js +0 -175
  72. package/dist/cjs/services/FileService.js +0 -201
  73. package/dist/cjs/services/IntegrationService.js +0 -538
  74. package/dist/cjs/services/MetricsService.js +0 -62
  75. package/dist/cjs/services/PaymentService.js +0 -271
  76. package/dist/cjs/services/PlanService.js +0 -426
  77. package/dist/cjs/services/ProjectService.js +0 -1207
  78. package/dist/cjs/services/PullRequestService.js +0 -503
  79. package/dist/cjs/services/ScreenshotService.js +0 -304
  80. package/dist/cjs/services/SubscriptionService.js +0 -396
  81. package/dist/cjs/services/TrackingService.js +0 -661
  82. package/dist/cjs/services/WaitlistService.js +0 -148
  83. package/dist/cjs/state/RootStateManager.js +0 -65
  84. package/dist/cjs/state/rootEventBus.js +0 -74
  85. package/dist/cjs/utils/CollabClient.js +0 -223
  86. package/dist/cjs/utils/changePreprocessor.js +0 -199
  87. package/dist/cjs/utils/jsonDiff.js +0 -145
  88. package/dist/cjs/utils/ordering.js +0 -309
  89. package/dist/esm/services/AdminService.js +0 -1132
  90. package/dist/esm/services/BranchService.js +0 -1265
  91. package/dist/esm/services/CollabService.js +0 -26838
  92. package/dist/esm/services/DnsService.js +0 -1121
  93. package/dist/esm/services/FeatureFlagService.js +0 -956
  94. package/dist/esm/services/FileService.js +0 -982
  95. package/dist/esm/services/IntegrationService.js +0 -1319
  96. package/dist/esm/services/MetricsService.js +0 -843
  97. package/dist/esm/services/PaymentService.js +0 -1052
  98. package/dist/esm/services/PlanService.js +0 -1207
  99. package/dist/esm/services/ProjectService.js +0 -2526
  100. package/dist/esm/services/PullRequestService.js +0 -1284
  101. package/dist/esm/services/ScreenshotService.js +0 -1085
  102. package/dist/esm/services/SubscriptionService.js +0 -1177
  103. package/dist/esm/services/TrackingService.js +0 -18343
  104. package/dist/esm/services/WaitlistService.js +0 -929
  105. package/dist/esm/state/RootStateManager.js +0 -90
  106. package/dist/esm/state/rootEventBus.js +0 -56
  107. package/dist/esm/utils/CollabClient.js +0 -18901
  108. package/dist/esm/utils/changePreprocessor.js +0 -542
  109. package/dist/esm/utils/jsonDiff.js +0 -7011
  110. package/dist/esm/utils/ordering.js +0 -291
  111. package/dist/node/services/AdminService.js +0 -332
  112. package/dist/node/services/BranchService.js +0 -465
  113. package/dist/node/services/CollabService.js +0 -724
  114. package/dist/node/services/DnsService.js +0 -321
  115. package/dist/node/services/FeatureFlagService.js +0 -156
  116. package/dist/node/services/FileService.js +0 -182
  117. package/dist/node/services/IntegrationService.js +0 -519
  118. package/dist/node/services/MetricsService.js +0 -43
  119. package/dist/node/services/PaymentService.js +0 -252
  120. package/dist/node/services/PlanService.js +0 -407
  121. package/dist/node/services/ProjectService.js +0 -1188
  122. package/dist/node/services/PullRequestService.js +0 -484
  123. package/dist/node/services/ScreenshotService.js +0 -285
  124. package/dist/node/services/SubscriptionService.js +0 -377
  125. package/dist/node/services/TrackingService.js +0 -632
  126. package/dist/node/services/WaitlistService.js +0 -129
  127. package/dist/node/state/RootStateManager.js +0 -36
  128. package/dist/node/state/rootEventBus.js +0 -55
  129. package/dist/node/utils/CollabClient.js +0 -194
  130. package/dist/node/utils/changePreprocessor.js +0 -180
  131. package/dist/node/utils/jsonDiff.js +0 -116
  132. package/dist/node/utils/ordering.js +0 -290
  133. package/src/services/AdminService.js +0 -374
  134. package/src/services/BranchService.js +0 -536
  135. package/src/services/CollabService.js +0 -900
  136. package/src/services/DnsService.js +0 -366
  137. package/src/services/FeatureFlagService.js +0 -174
  138. package/src/services/FileService.js +0 -213
  139. package/src/services/IntegrationService.js +0 -548
  140. package/src/services/MetricsService.js +0 -40
  141. package/src/services/PaymentService.js +0 -287
  142. package/src/services/PlanService.js +0 -468
  143. package/src/services/ProjectService.js +0 -1366
  144. package/src/services/PullRequestService.js +0 -537
  145. package/src/services/ScreenshotService.js +0 -258
  146. package/src/services/SubscriptionService.js +0 -425
  147. package/src/services/TrackingService.js +0 -853
  148. package/src/services/WaitlistService.js +0 -130
  149. package/src/services/tests/BranchService/createBranch.test.js +0 -153
  150. package/src/services/tests/BranchService/deleteBranch.test.js +0 -173
  151. package/src/services/tests/BranchService/getBranchChanges.test.js +0 -146
  152. package/src/services/tests/BranchService/listBranches.test.js +0 -87
  153. package/src/services/tests/BranchService/mergeBranch.test.js +0 -210
  154. package/src/services/tests/BranchService/publishVersion.test.js +0 -183
  155. package/src/services/tests/BranchService/renameBranch.test.js +0 -240
  156. package/src/services/tests/BranchService/resetBranch.test.js +0 -152
  157. package/src/services/tests/FeatureFlagService/adminFeatureFlags.test.js +0 -67
  158. package/src/services/tests/FeatureFlagService/getFeatureFlags.test.js +0 -75
  159. package/src/services/tests/FileService/createFileFormData.test.js +0 -74
  160. package/src/services/tests/FileService/getFileUrl.test.js +0 -69
  161. package/src/services/tests/FileService/updateProjectIcon.test.js +0 -109
  162. package/src/services/tests/FileService/uploadDocument.test.js +0 -36
  163. package/src/services/tests/FileService/uploadFile.test.js +0 -78
  164. package/src/services/tests/FileService/uploadFileWithValidation.test.js +0 -114
  165. package/src/services/tests/FileService/uploadImage.test.js +0 -36
  166. package/src/services/tests/FileService/uploadMultipleFiles.test.js +0 -111
  167. package/src/services/tests/FileService/validateFile.test.js +0 -63
  168. package/src/services/tests/PlanService/createPlan.test.js +0 -104
  169. package/src/services/tests/PlanService/createPlanWithValidation.test.js +0 -523
  170. package/src/services/tests/PlanService/deletePlan.test.js +0 -92
  171. package/src/services/tests/PlanService/getActivePlans.test.js +0 -123
  172. package/src/services/tests/PlanService/getAdminPlans.test.js +0 -84
  173. package/src/services/tests/PlanService/getPlan.test.js +0 -50
  174. package/src/services/tests/PlanService/getPlanByKey.test.js +0 -109
  175. package/src/services/tests/PlanService/getPlanWithValidation.test.js +0 -85
  176. package/src/services/tests/PlanService/getPlans.test.js +0 -53
  177. package/src/services/tests/PlanService/getPlansByPriceRange.test.js +0 -109
  178. package/src/services/tests/PlanService/getPlansWithValidation.test.js +0 -48
  179. package/src/services/tests/PlanService/initializePlans.test.js +0 -75
  180. package/src/services/tests/PlanService/updatePlan.test.js +0 -111
  181. package/src/services/tests/PlanService/updatePlanWithValidation.test.js +0 -556
  182. package/src/state/RootStateManager.js +0 -76
  183. package/src/state/rootEventBus.js +0 -67
  184. package/src/utils/CollabClient.js +0 -248
  185. package/src/utils/changePreprocessor.js +0 -239
  186. package/src/utils/jsonDiff.js +0 -144
  187. package/src/utils/ordering.js +0 -271
@@ -1,885 +1,220 @@
1
+ /* eslint-disable require-await */
1
2
  import { BaseService } from './BaseService.js'
2
3
  import {
4
+ PERMISSION_MAP,
3
5
  ROLE_PERMISSIONS,
4
6
  TIER_FEATURES,
5
7
  PROJECT_ROLE_PERMISSIONS
6
8
  } from '../utils/permission.js'
7
9
 
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
- 'starter',
16
+ 'free',
17
17
  'pro1',
18
18
  'pro2',
19
19
  'enterprise'
20
20
  ])
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
- )
21
+ this._initialized = false
32
22
  }
33
23
 
34
- // Use BaseService.init/_request/_requireReady implementations
35
-
36
- // ==================== AUTH METHODS ====================
37
-
38
- async register(userData, options = {}) {
24
+ // eslint-disable-next-line no-empty-pattern
25
+ init ({}) {
39
26
  try {
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)
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)
53
36
  }
54
- return response.data
55
37
  }
56
- throw new Error(response.message)
57
- } catch (error) {
58
- throw new Error(`Registration failed: ${error.message}`, { cause: error })
59
- }
60
- }
61
38
 
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)
39
+ this._initialized = true
40
+ this._setReady()
101
41
  } catch (error) {
102
- throw new Error(`Login failed: ${error.message}`, { cause: error })
42
+ this._setError(error)
43
+ throw error
103
44
  }
104
45
  }
105
46
 
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 })
126
- }
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)
127
59
  }
128
60
 
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 })
61
+ _requireReady (methodName) {
62
+ if (this._requiresInit(methodName) && !this._initialized) {
63
+ throw new Error('Service not initialized')
142
64
  }
143
65
  }
144
66
 
145
- async googleAuth(idToken, inviteToken = null, options = {}) {
146
- try {
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'
156
- })
157
-
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)
181
- } catch (error) {
182
- throw new Error(`Google auth failed: ${error.message}`, { cause: error })
67
+ _getBasedService (methodName) {
68
+ const based = this._context.services?.based
69
+ if (this._requiresInit(methodName) && !based) {
70
+ throw new Error('Based service not available')
183
71
  }
72
+ return based._client
184
73
  }
185
74
 
186
- async githubAuth(code, inviteToken = null, options = {}) {
75
+ async login (identifier, password) {
187
76
  try {
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'
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
197
89
  })
198
90
 
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)
91
+ return response
222
92
  } catch (error) {
223
- throw new Error(`GitHub auth failed: ${error.message}`, { cause: error })
93
+ throw new Error(`Login failed: ${error.message}`)
224
94
  }
225
95
  }
226
96
 
227
- async googleAuthCallback (code, redirectUri, inviteToken = null, options = {}) {
97
+ async register (userData) {
228
98
  try {
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
- }
252
-
253
- // Set tokens in TokenManager
254
- if (this._tokenManager) {
255
- this._tokenManager.setTokens(tokenData)
256
- }
257
- }
258
-
259
- if (response.success) {
260
- if (session) {
261
- this._clearPluginSession(session)
262
- }
263
- return response.data
264
- }
265
- throw new Error(response.message)
99
+ const based = this._getBasedService('register')
100
+ return await based.call('users:register', userData)
266
101
  } catch (error) {
267
- throw new Error(`Google auth callback failed: ${error.message}`, { cause: error })
102
+ throw new Error(`Registration failed: ${error.message}`)
268
103
  }
269
104
  }
270
105
 
271
- async requestPasswordReset(email) {
106
+ async googleAuth (idToken) {
272
107
  try {
273
- const response = await this._request('/auth/request-password-reset', {
274
- method: 'POST',
275
- body: JSON.stringify({ email }),
276
- methodName: 'requestPasswordReset'
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
- }
108
+ const based = this._getBasedService('googleAuth')
109
+ const response = await based.call('users:google-auth', { idToken })
286
110
 
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
111
+ if (this._initialized) {
112
+ this.updateContext({ authToken: response.token })
296
113
  }
297
- throw new Error(response.message)
298
- } catch (error) {
299
- throw new Error(`Password reset confirmation failed: ${error.message}`, { cause: error })
300
- }
301
- }
302
114
 
303
- async confirmRegistration(token) {
304
- try {
305
- const response = await this._request('/auth/register-confirmation', {
306
- method: 'POST',
307
- body: JSON.stringify({ token }),
308
- methodName: 'confirmRegistration'
115
+ based.setAuthState({
116
+ token: response.token,
117
+ userId: response.userId,
118
+ persistent: true
309
119
  })
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
- }
318
120
 
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
328
- }
329
- throw new Error(response.message)
121
+ return response
330
122
  } catch (error) {
331
- throw new Error(`Password change request failed: ${error.message}`, { cause: error })
123
+ throw new Error(`Google auth failed: ${error.message}`)
332
124
  }
333
125
  }
334
126
 
335
- async confirmPasswordChange(currentPassword, newPassword, code) {
336
- this._requireReady('confirmPasswordChange')
127
+ async googleAuthCallback (code, redirectUri) {
337
128
  try {
338
- const response = await this._request('/auth/confirm-password-change', {
339
- method: 'POST',
340
- body: JSON.stringify({ currentPassword, newPassword, code }),
341
- methodName: 'confirmPasswordChange'
129
+ const based = this._getBasedService('googleAuthCallback')
130
+ const response = await based.call('users:google-auth-callback', {
131
+ code,
132
+ redirectUri
342
133
  })
343
- if (response.success) {
344
- return response.data
134
+
135
+ if (this._initialized) {
136
+ this.updateContext({ authToken: response.token })
345
137
  }
346
- throw new Error(response.message)
347
- } catch (error) {
348
- throw new Error(`Password change confirmation failed: ${error.message}`, { cause: error })
349
- }
350
- }
351
138
 
352
- async getMe(options = {}) {
353
- this._requireReady('getMe')
354
- try {
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'
139
+ based.setAuthState({
140
+ token: response.token,
141
+ userId: response.userId,
142
+ persistent: true
363
143
  })
364
- if (response.success) {
365
- return response.data
366
- }
367
- throw new Error(response.message)
144
+ return response
368
145
  } catch (error) {
369
- throw new Error(`Failed to get user profile: ${error.message}`, { cause: error })
146
+ throw new Error(`Google auth callback failed: ${error.message}`)
370
147
  }
371
148
  }
372
149
 
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() {
150
+ async githubAuth (code) {
385
151
  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
- }
152
+ const based = this._getBasedService('githubAuth')
153
+ const response = await based.call('users:github-auth', { code })
464
154
 
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}`
155
+ if (this._initialized) {
156
+ this.updateContext({ authToken: response.token })
482
157
  }
483
- }
484
- }
485
158
 
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'
159
+ based.setAuthState({
160
+ token: response.token,
161
+ userId: response.userId,
162
+ persistent: true
494
163
  })
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
- }
503
164
 
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)
165
+ return response
516
166
  } catch (error) {
517
- throw new Error(`Failed to update user profile: ${error.message}`, { cause: error })
167
+ throw new Error(`GitHub auth failed: ${error.message}`)
518
168
  }
519
169
  }
520
170
 
521
- async getUserProjects() {
522
- this._requireReady('getUserProjects')
171
+ async logout () {
172
+ this._requireReady('logout')
523
173
  try {
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)
174
+ const based = this._getBasedService('logout')
175
+ await based.call('users:logout')
176
+ this.updateContext({ authToken: null })
540
177
  } catch (error) {
541
- throw new Error(`Failed to get user projects: ${error.message}`, { cause: error })
178
+ throw new Error(`Logout failed: ${error.message}`)
542
179
  }
543
180
  }
544
181
 
545
- async getUser(userId) {
546
- this._requireReady('getUser')
182
+ async updateUserRole (userId, newRole) {
183
+ this._requireReady('updateUserRole')
547
184
  if (!userId) {
548
185
  throw new Error('User ID is required')
549
186
  }
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')
187
+ if (!this._userRoles.has(newRole)) {
188
+ throw new Error(`Invalid role: ${newRole}`)
568
189
  }
569
190
  try {
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)
191
+ const based = this._getBasedService('updateUserRole')
192
+ return await based.call('users:update-role', { userId, role: newRole })
578
193
  } catch (error) {
579
- throw new Error(`Failed to get user by email: ${error.message}`, { cause: error })
194
+ throw new Error(`Failed to update user role: ${error.message}`)
580
195
  }
581
196
  }
582
197
 
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')
198
+ async updateProjectTier (projectId, newTier) {
199
+ this._requireReady('updateProjectTier')
591
200
  if (!projectId) {
592
201
  throw new Error('Project ID is required')
593
202
  }
594
-
595
- // If there are no valid tokens, treat user as guest for public access
596
- if (!this.hasValidTokens()) {
597
- return 'guest'
203
+ if (!this._projectTiers.has(newTier)) {
204
+ throw new Error(`Invalid project tier: ${newTier}`)
598
205
  }
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
-
608
206
  try {
609
- const response = await this._request(`/projects/${projectId}/role`, {
610
- method: 'GET',
611
- methodName: 'getMyProjectRole'
207
+ const based = this._getBasedService('updateProjectTier')
208
+ return await based.call('projects:update-tier', {
209
+ projectId,
210
+ tier: newTier
612
211
  })
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)
624
212
  } catch (error) {
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 })
213
+ throw new Error(`Failed to update project tier: ${error.message}`)
631
214
  }
632
215
  }
633
216
 
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) {
217
+ hasPermission (requiredPermission) {
883
218
  const authState = this._context?.state
884
219
  if (!authState) {
885
220
  return false
@@ -895,21 +230,21 @@ export class AuthService extends BaseService {
895
230
  )
896
231
  }
897
232
 
898
- hasGlobalPermission(globalRole, requiredPermission) {
233
+ hasGlobalPermission (globalRole, requiredPermission) {
899
234
  return ROLE_PERMISSIONS[globalRole]?.includes(requiredPermission) || false
900
235
  }
901
236
 
902
- checkProjectPermission(projectRole, requiredPermission) {
237
+ checkProjectPermission (projectRole, requiredPermission) {
903
238
  return (
904
239
  PROJECT_ROLE_PERMISSIONS[projectRole]?.includes(requiredPermission) ||
905
240
  false
906
241
  )
907
242
  }
908
243
 
909
- checkProjectFeature(projectTier, feature) {
244
+ checkProjectFeature (projectTier, feature) {
910
245
  if (feature.startsWith('aiCopilot') || feature.startsWith('aiChatbot')) {
911
246
  const [featureBase] = feature.split(':')
912
- const tierFeature = TIER_FEATURES[projectTier]?.find((f) =>
247
+ const tierFeature = TIER_FEATURES[projectTier]?.find(f =>
913
248
  f.startsWith(featureBase)
914
249
  )
915
250
  if (!tierFeature) {
@@ -923,7 +258,7 @@ export class AuthService extends BaseService {
923
258
  }
924
259
 
925
260
  // Operation checking
926
- async canPerformOperation(projectId, operation, options = {}) {
261
+ async canPerformOperation (projectId, operation, options = {}) {
927
262
  this._requireReady()
928
263
  if (!projectId) {
929
264
  throw new Error('Project ID is required')
@@ -935,14 +270,17 @@ export class AuthService extends BaseService {
935
270
  if (!operationConfig) {
936
271
  return false
937
272
  }
273
+ if (!operationConfig) {
274
+ return false
275
+ }
938
276
 
939
277
  const { permissions = [], features = [] } = operationConfig
940
278
 
941
279
  try {
942
280
  // Check permissions
943
281
  const permissionResults = await Promise.all(
944
- permissions.map((permission) =>
945
- this.checkProjectPermission(projectId, permission)
282
+ permissions.map(permission =>
283
+ this.hasProjectPermission(projectId, permission)
946
284
  )
947
285
  )
948
286
 
@@ -953,10 +291,13 @@ export class AuthService extends BaseService {
953
291
  if (!hasPermissions) {
954
292
  return false
955
293
  }
294
+ if (!hasPermissions) {
295
+ return false
296
+ }
956
297
 
957
298
  // Check features if required
958
299
  if (checkFeatures && features.length > 0) {
959
- const featureResults = features.map((feature) => {
300
+ const featureResults = features.map(feature => {
960
301
  const result = this.hasProjectFeature(projectId, feature)
961
302
  return feature.includes(':')
962
303
  ? typeof result === 'number' && result > 0
@@ -970,6 +311,9 @@ export class AuthService extends BaseService {
970
311
  if (!hasFeatures) {
971
312
  return false
972
313
  }
314
+ if (!hasFeatures) {
315
+ return false
316
+ }
973
317
  }
974
318
 
975
319
  return true
@@ -980,7 +324,7 @@ export class AuthService extends BaseService {
980
324
  }
981
325
 
982
326
  // Higher-level permission methods
983
- async withPermission(projectId, operation, action) {
327
+ async withPermission (projectId, operation, action) {
984
328
  this._requireReady()
985
329
  if (!projectId) {
986
330
  throw new Error('Project ID is required')
@@ -996,115 +340,225 @@ export class AuthService extends BaseService {
996
340
  return action()
997
341
  }
998
342
 
999
- // Cleanup
1000
- destroy() {
1001
- if (this._tokenManager) {
1002
- this._tokenManager.destroy()
1003
- this._tokenManager = null
343
+ // Project access information
344
+ async getProjectAccess (projectId) {
345
+ this._requireReady()
346
+ if (!projectId) {
347
+ throw new Error('Project ID is required')
1004
348
  }
1005
- // Clear project role cache
1006
- this._projectRoleCache.clear()
1007
- this._setReady(false)
1008
- }
1009
349
 
1010
- _preparePluginPayload(payload, sessionOverride = null) {
1011
- const target =
1012
- payload && typeof payload === 'object'
1013
- ? { ...payload }
1014
- : {}
350
+ const operations = Object.keys(PERMISSION_MAP)
1015
351
 
1016
- const session = this._resolvePluginSession(sessionOverride)
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]
1017
356
 
1018
- if (session && !Object.hasOwn(target, 'session')) {
1019
- target.session = session
1020
- return { payload: target, session }
1021
- }
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
+ )
1022
368
 
1023
- return { payload: target, session: null }
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}`)
408
+ }
1024
409
  }
1025
410
 
1026
- _resolvePluginSession(sessionOverride = null) {
1027
- if (sessionOverride) {
1028
- return this._cachePluginSession(sessionOverride)
411
+ async inviteMember (projectId, email, role, name, callbackUrl) {
412
+ this._requireReady('inviteMember')
413
+ if (!projectId) {
414
+ throw new Error('Project ID is required')
1029
415
  }
1030
-
1031
- if (this._pluginSession) {
1032
- return this._pluginSession
416
+ if (!email) {
417
+ throw new Error('Email is required')
1033
418
  }
1034
-
1035
- const optionSession = this._options?.pluginSession
1036
- if (optionSession) {
1037
- return this._cachePluginSession(optionSession)
419
+ if (!callbackUrl || Object.keys(callbackUrl).length === 0) {
420
+ throw new Error('Callback Url is required')
1038
421
  }
1039
-
1040
- const contextSession = this._context?.pluginSession
1041
- if (contextSession) {
1042
- return this._cachePluginSession(contextSession)
422
+ if (!role || !this._userRoles.has(role)) {
423
+ throw new Error(`Invalid role: ${role}`)
1043
424
  }
1044
-
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
- }
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}`)
1066
436
  }
1067
-
1068
- return null
1069
437
  }
1070
438
 
1071
- _cachePluginSession(session) {
1072
- if (!session) {
1073
- return null
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}`)
1074
446
  }
447
+ }
1075
448
 
1076
- this._pluginSession = session
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}`)
459
+ }
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
+ }
1077
471
 
1078
- if (typeof window !== 'undefined') {
1079
- try {
1080
- if (window.localStorage) {
1081
- window.localStorage.setItem(PLUGIN_SESSION_STORAGE_KEY, session)
1082
- }
1083
- } catch {
1084
- // Ignore storage access issues
1085
- }
472
+ async removeMember (projectId, userId) {
473
+ this._requireReady('removeMember')
474
+ if (!projectId || !userId) {
475
+ throw new Error('Project ID and user ID are required')
476
+ }
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}`)
1086
482
  }
483
+ }
1087
484
 
1088
- return session
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}`)
491
+ }
1089
492
  }
1090
493
 
1091
- setPluginSession(session) {
1092
- this._cachePluginSession(session)
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}`)
500
+ }
1093
501
  }
1094
502
 
1095
- _clearPluginSession(session = null) {
1096
- if (!session || this._pluginSession === session) {
1097
- this._pluginSession = null
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}`)
1098
512
  }
513
+ }
514
+
515
+ async getStoredAuthState () {
516
+ try {
517
+ const based = this._getBasedService('getStoredAuthState')
518
+ const { authState } = based
1099
519
 
1100
- if (typeof window !== 'undefined') {
1101
- try {
1102
- if (window.localStorage) {
1103
- window.localStorage.removeItem(PLUGIN_SESSION_STORAGE_KEY)
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
1104
527
  }
1105
- } catch {
1106
- // Ignore storage access issues
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}`
1107
540
  }
1108
541
  }
1109
542
  }
543
+
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
+ })
561
+
562
+ return () => based.off('authstate-change')
563
+ }
1110
564
  }