@progalaxyelabs/ngx-stonescriptphp-client 1.9.0 → 1.11.1

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.
@@ -110,8 +110,8 @@ class TokenService {
110
110
  localStorage.removeItem(this.lsRefreshTokenKey);
111
111
  }
112
112
  /**
113
- * Check if there is a valid (non-empty) access token
114
- * @returns True if access token exists and is not empty
113
+ * Check if there is a non-empty access token.
114
+ * Token is treated as opaque validity is determined by the auth server.
115
115
  */
116
116
  hasValidAccessToken() {
117
117
  const token = this.getAccessToken();
@@ -200,6 +200,12 @@ class MyEnvironmentModel {
200
200
  messagingSenderId: '',
201
201
  measurementId: ''
202
202
  };
203
+ /**
204
+ * Platform's own API base URL (e.g., '//api.medstoreapp.in')
205
+ * Used to route registration through the platform API proxy instead of auth directly
206
+ * @example '//api.medstoreapp.in'
207
+ */
208
+ apiUrl;
203
209
  apiServer = { host: '' };
204
210
  chatServer = { host: '' };
205
211
  /**
@@ -240,6 +246,22 @@ class MyEnvironmentModel {
240
246
  * ```
241
247
  */
242
248
  customProviders;
249
+ /**
250
+ * Auth response field mapping.
251
+ * Defaults to StoneScriptPHP format: { status: 'ok', data: { access_token, user, ... } }
252
+ *
253
+ * For external auth servers that return flat responses like
254
+ * { access_token, identity, ... }, override with:
255
+ * ```typescript
256
+ * authResponseMap: {
257
+ * accessTokenPath: 'access_token',
258
+ * refreshTokenPath: 'refresh_token',
259
+ * userPath: 'identity',
260
+ * errorMessagePath: 'message'
261
+ * }
262
+ * ```
263
+ */
264
+ authResponseMap;
243
265
  /**
244
266
  * Branding configuration for auth components
245
267
  * Allows platforms to customize login/register pages without creating wrappers
@@ -775,6 +797,73 @@ class AuthService {
775
797
  isMultiServerMode() {
776
798
  return !!(this.environment.authServers && Object.keys(this.environment.authServers).length > 0);
777
799
  }
800
+ /**
801
+ * Get the platform's own API base URL
802
+ * Used for routes that go through the platform API proxy (e.g. register-tenant)
803
+ * @throws Error if no API URL is configured
804
+ */
805
+ getPlatformApiUrl() {
806
+ if (this.environment.apiUrl) {
807
+ return this.environment.apiUrl;
808
+ }
809
+ if (this.environment.apiServer?.host) {
810
+ return this.environment.apiServer.host;
811
+ }
812
+ throw new Error('No platform API URL configured. Set apiUrl in environment config.');
813
+ }
814
+ // ===== Auth Response Mapping Helpers =====
815
+ /** Default response map: StoneScriptPHP format */
816
+ get responseMap() {
817
+ return this.environment.authResponseMap ?? {
818
+ successPath: 'status',
819
+ successValue: 'ok',
820
+ accessTokenPath: 'data.access_token',
821
+ refreshTokenPath: 'data.refresh_token',
822
+ userPath: 'data.user',
823
+ errorMessagePath: 'message'
824
+ };
825
+ }
826
+ /** Resolve a dot-notation path on an object (e.g., 'data.access_token') */
827
+ resolvePath(obj, path) {
828
+ return path.split('.').reduce((o, key) => o?.[key], obj);
829
+ }
830
+ /** Check if an auth response indicates success */
831
+ isAuthSuccess(data) {
832
+ const map = this.responseMap;
833
+ if (map.successPath) {
834
+ return this.resolvePath(data, map.successPath) === (map.successValue ?? 'ok');
835
+ }
836
+ // No successPath configured — success = access token present
837
+ return !!this.resolvePath(data, map.accessTokenPath);
838
+ }
839
+ /** Extract access token from auth response */
840
+ getAccessToken(data) {
841
+ return this.resolvePath(data, this.responseMap.accessTokenPath);
842
+ }
843
+ /** Extract refresh token from auth response */
844
+ getRefreshToken(data) {
845
+ return this.resolvePath(data, this.responseMap.refreshTokenPath);
846
+ }
847
+ /** Extract user/identity object from auth response */
848
+ getUserFromResponse(data) {
849
+ return this.resolvePath(data, this.responseMap.userPath);
850
+ }
851
+ /** Extract error message from auth response */
852
+ getErrorMessage(data, fallback) {
853
+ const path = this.responseMap.errorMessagePath ?? 'message';
854
+ return this.resolvePath(data, path) || fallback;
855
+ }
856
+ /** Normalize a user/identity object into the standard User shape */
857
+ normalizeUser(raw) {
858
+ return {
859
+ user_id: raw.user_id ?? (raw.id ? this.hashUUID(raw.id) : 0),
860
+ id: raw.id ?? String(raw.user_id),
861
+ email: raw.email,
862
+ display_name: raw.display_name ?? raw.email?.split('@')[0] ?? '',
863
+ photo_url: raw.photo_url,
864
+ is_email_verified: raw.is_email_verified ?? false
865
+ };
866
+ }
778
867
  /**
779
868
  * Hash UUID to numeric ID for backward compatibility
780
869
  * Converts UUID string to a consistent numeric ID for legacy code
@@ -838,7 +927,7 @@ class AuthService {
838
927
  const response = await fetch(`${accountsUrl}/api/auth/login`, {
839
928
  method: 'POST',
840
929
  headers: { 'Content-Type': 'application/json' },
841
- credentials: 'include', // Include cookies for refresh token
930
+ credentials: 'include',
842
931
  body: JSON.stringify({
843
932
  email,
844
933
  password,
@@ -846,24 +935,21 @@ class AuthService {
846
935
  })
847
936
  });
848
937
  const data = await response.json();
849
- if (data.success && data.access_token) {
850
- this.tokens.setAccessToken(data.access_token);
938
+ if (this.isAuthSuccess(data)) {
939
+ const accessToken = this.getAccessToken(data);
940
+ this.tokens.setAccessToken(accessToken);
851
941
  this.signinStatus.setSigninStatus(true);
852
- // Normalize user object to handle both response formats
853
- const normalizedUser = {
854
- user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
855
- id: data.user.id ?? String(data.user.user_id),
856
- email: data.user.email,
857
- display_name: data.user.display_name ?? data.user.email.split('@')[0],
858
- photo_url: data.user.photo_url,
859
- is_email_verified: data.user.is_email_verified ?? false
860
- };
861
- this.updateUser(normalizedUser);
862
- return { success: true, user: normalizedUser };
942
+ const rawUser = this.getUserFromResponse(data);
943
+ if (rawUser) {
944
+ const normalizedUser = this.normalizeUser(rawUser);
945
+ this.updateUser(normalizedUser);
946
+ return { success: true, user: normalizedUser };
947
+ }
948
+ return { success: true };
863
949
  }
864
950
  return {
865
951
  success: false,
866
- message: data.message || 'Invalid credentials'
952
+ message: this.getErrorMessage(data, 'Invalid credentials')
867
953
  };
868
954
  }
869
955
  catch (error) {
@@ -959,16 +1045,11 @@ class AuthService {
959
1045
  if (event.data.type === 'oauth_success') {
960
1046
  this.tokens.setAccessToken(event.data.access_token);
961
1047
  this.signinStatus.setSigninStatus(true);
962
- // Normalize user object to handle both response formats
963
- const normalizedUser = {
964
- user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
965
- id: event.data.user.id ?? String(event.data.user.user_id),
966
- email: event.data.user.email,
967
- display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
968
- photo_url: event.data.user.photo_url,
969
- is_email_verified: event.data.user.is_email_verified ?? false
970
- };
971
- this.updateUser(normalizedUser);
1048
+ const rawUser = event.data.user || this.getUserFromResponse(event.data);
1049
+ const normalizedUser = rawUser ? this.normalizeUser(rawUser) : undefined;
1050
+ if (normalizedUser) {
1051
+ this.updateUser(normalizedUser);
1052
+ }
972
1053
  window.removeEventListener('message', messageHandler);
973
1054
  popup.close();
974
1055
  resolve({
@@ -1021,28 +1102,25 @@ class AuthService {
1021
1102
  })
1022
1103
  });
1023
1104
  const data = await response.json();
1024
- if (data.success && data.access_token) {
1025
- this.tokens.setAccessToken(data.access_token);
1105
+ if (this.isAuthSuccess(data)) {
1106
+ const accessToken = this.getAccessToken(data);
1107
+ this.tokens.setAccessToken(accessToken);
1026
1108
  this.signinStatus.setSigninStatus(true);
1027
- // Normalize user object to handle both response formats
1028
- const normalizedUser = {
1029
- user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
1030
- id: data.user.id ?? String(data.user.user_id),
1031
- email: data.user.email,
1032
- display_name: data.user.display_name ?? data.user.email.split('@')[0],
1033
- photo_url: data.user.photo_url,
1034
- is_email_verified: data.user.is_email_verified ?? false
1035
- };
1036
- this.updateUser(normalizedUser);
1037
- return {
1038
- success: true,
1039
- user: normalizedUser,
1040
- message: data.needs_verification ? 'Please verify your email' : undefined
1041
- };
1109
+ const rawUser = this.getUserFromResponse(data);
1110
+ if (rawUser) {
1111
+ const normalizedUser = this.normalizeUser(rawUser);
1112
+ this.updateUser(normalizedUser);
1113
+ return {
1114
+ success: true,
1115
+ user: normalizedUser,
1116
+ message: data.needs_verification ? 'Please verify your email' : undefined
1117
+ };
1118
+ }
1119
+ return { success: true };
1042
1120
  }
1043
1121
  return {
1044
1122
  success: false,
1045
- message: data.message || 'Registration failed'
1123
+ message: this.getErrorMessage(data, 'Registration failed')
1046
1124
  };
1047
1125
  }
1048
1126
  catch (error) {
@@ -1099,30 +1177,33 @@ class AuthService {
1099
1177
  credentials: 'include'
1100
1178
  });
1101
1179
  if (!response.ok) {
1180
+ // Refresh failed (expired, revoked, wrong keypair) — clear stale tokens
1181
+ // so the user gets a clean login page, not a broken retry loop
1182
+ this.tokens.clear();
1183
+ this.updateUser(null);
1102
1184
  this.signinStatus.setSigninStatus(false);
1103
1185
  return false;
1104
1186
  }
1105
1187
  const data = await response.json();
1106
- if (data.access_token) {
1107
- this.tokens.setAccessToken(data.access_token);
1108
- // Normalize user object to handle both response formats
1109
- if (data.user) {
1110
- const normalizedUser = {
1111
- user_id: data.user.user_id ?? (data.user.id ? this.hashUUID(data.user.id) : 0),
1112
- id: data.user.id ?? String(data.user.user_id),
1113
- email: data.user.email,
1114
- display_name: data.user.display_name ?? data.user.email.split('@')[0],
1115
- photo_url: data.user.photo_url,
1116
- is_email_verified: data.user.is_email_verified ?? false
1117
- };
1118
- this.updateUser(normalizedUser);
1188
+ const accessToken = this.getAccessToken(data) ?? data.access_token;
1189
+ if (accessToken) {
1190
+ this.tokens.setAccessToken(accessToken);
1191
+ const rawUser = this.getUserFromResponse(data) ?? data.user;
1192
+ if (rawUser) {
1193
+ this.updateUser(this.normalizeUser(rawUser));
1119
1194
  }
1120
1195
  this.signinStatus.setSigninStatus(true);
1121
1196
  return true;
1122
1197
  }
1198
+ // Response OK but no token — clear stale state
1199
+ this.tokens.clear();
1200
+ this.updateUser(null);
1123
1201
  return false;
1124
1202
  }
1125
1203
  catch (error) {
1204
+ // Network error — clear stale state to avoid retry loops
1205
+ this.tokens.clear();
1206
+ this.updateUser(null);
1126
1207
  this.signinStatus.setSigninStatus(false);
1127
1208
  return false;
1128
1209
  }
@@ -1150,9 +1231,9 @@ class AuthService {
1150
1231
  if (data.provider !== 'emailPassword') {
1151
1232
  return await this.registerTenantWithOAuth(data.tenantName, data.tenantSlug, data.provider);
1152
1233
  }
1153
- // Email/password registration
1154
- const accountsUrl = this.getAccountsUrl();
1155
- const response = await fetch(`${accountsUrl}/api/auth/register-tenant`, {
1234
+ // Email/password registration — route through platform API proxy
1235
+ const apiUrl = this.getPlatformApiUrl();
1236
+ const response = await fetch(`${apiUrl}/auth/register-tenant`, {
1156
1237
  method: 'POST',
1157
1238
  headers: { 'Content-Type': 'application/json' },
1158
1239
  credentials: 'include',
@@ -1167,20 +1248,15 @@ class AuthService {
1167
1248
  })
1168
1249
  });
1169
1250
  const result = await response.json();
1170
- if (result.success && result.access_token) {
1171
- this.tokens.setAccessToken(result.access_token);
1172
- this.signinStatus.setSigninStatus(true);
1173
- if (result.user) {
1174
- // Normalize user object to handle both response formats
1175
- const normalizedUser = {
1176
- user_id: result.user.user_id ?? (result.user.id ? this.hashUUID(result.user.id) : 0),
1177
- id: result.user.id ?? String(result.user.user_id),
1178
- email: result.user.email,
1179
- display_name: result.user.display_name ?? result.user.email.split('@')[0],
1180
- photo_url: result.user.photo_url,
1181
- is_email_verified: result.user.is_email_verified ?? false
1182
- };
1183
- this.updateUser(normalizedUser);
1251
+ if (this.isAuthSuccess(result)) {
1252
+ const accessToken = this.getAccessToken(result);
1253
+ if (accessToken) {
1254
+ this.tokens.setAccessToken(accessToken);
1255
+ this.signinStatus.setSigninStatus(true);
1256
+ }
1257
+ const rawUser = this.getUserFromResponse(result);
1258
+ if (rawUser) {
1259
+ this.updateUser(this.normalizeUser(rawUser));
1184
1260
  }
1185
1261
  }
1186
1262
  return result;
@@ -1225,22 +1301,13 @@ class AuthService {
1225
1301
  return;
1226
1302
  }
1227
1303
  if (event.data.type === 'tenant_register_success') {
1228
- // Set tokens and user
1229
1304
  if (event.data.access_token) {
1230
1305
  this.tokens.setAccessToken(event.data.access_token);
1231
1306
  this.signinStatus.setSigninStatus(true);
1232
1307
  }
1233
- if (event.data.user) {
1234
- // Normalize user object to handle both response formats
1235
- const normalizedUser = {
1236
- user_id: event.data.user.user_id ?? (event.data.user.id ? this.hashUUID(event.data.user.id) : 0),
1237
- id: event.data.user.id ?? String(event.data.user.user_id),
1238
- email: event.data.user.email,
1239
- display_name: event.data.user.display_name ?? event.data.user.email.split('@')[0],
1240
- photo_url: event.data.user.photo_url,
1241
- is_email_verified: event.data.user.is_email_verified ?? false
1242
- };
1243
- this.updateUser(normalizedUser);
1308
+ const rawUser = event.data.user || this.getUserFromResponse(event.data);
1309
+ if (rawUser) {
1310
+ this.updateUser(this.normalizeUser(rawUser));
1244
1311
  }
1245
1312
  window.removeEventListener('message', messageHandler);
1246
1313
  popup.close();
@@ -1316,16 +1383,17 @@ class AuthService {
1316
1383
  body: JSON.stringify({ tenant_id: tenantId })
1317
1384
  });
1318
1385
  const data = await response.json();
1319
- if (data.success && data.access_token) {
1320
- this.tokens.setAccessToken(data.access_token);
1386
+ if (this.isAuthSuccess(data)) {
1387
+ const accessToken = this.getAccessToken(data);
1388
+ this.tokens.setAccessToken(accessToken);
1321
1389
  return {
1322
1390
  success: true,
1323
- access_token: data.access_token
1391
+ access_token: accessToken
1324
1392
  };
1325
1393
  }
1326
1394
  return {
1327
1395
  success: false,
1328
- message: data.message || 'Failed to select tenant'
1396
+ message: this.getErrorMessage(data, 'Failed to select tenant')
1329
1397
  };
1330
1398
  }
1331
1399
  catch (error) {
@@ -1475,8 +1543,9 @@ class AuthService {
1475
1543
  if (!accessToken) {
1476
1544
  throw new Error('Not authenticated');
1477
1545
  }
1478
- const accountsUrl = this.getAccountsUrl(serverName);
1479
- const response = await fetch(`${accountsUrl}/api/auth/register-tenant`, {
1546
+ // Route through platform API proxy — PHP API adds platform_secret header
1547
+ const apiUrl = this.getPlatformApiUrl();
1548
+ const response = await fetch(`${apiUrl}/auth/register-tenant`, {
1480
1549
  method: 'POST',
1481
1550
  headers: {
1482
1551
  'Content-Type': 'application/json',