@progalaxyelabs/ngx-stonescriptphp-client 1.14.0 → 1.16.0

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.
@@ -140,19 +140,36 @@ class StoneScriptPHPAuth {
140
140
  const raw = this.resolvePath(data, this.responseMap.userPath);
141
141
  return raw ? this.normalizeUser(raw) : undefined;
142
142
  }
143
+ resolveMembership(data) {
144
+ const raw = this.resolvePath(data, 'data.membership');
145
+ return raw ? raw : undefined;
146
+ }
143
147
  resolveErrorMessage(data, fallback) {
144
148
  const path = this.responseMap.errorMessagePath ?? 'message';
145
149
  return this.resolvePath(data, path) || fallback;
146
150
  }
147
151
  normalizeUser(raw) {
148
- return {
149
- user_id: raw.user_id ?? (raw.id ? this.hashUUID(raw.id) : 0),
150
- id: raw.id ?? String(raw.user_id),
152
+ const user = {
151
153
  email: raw.email,
152
154
  display_name: raw.display_name ?? raw.email?.split('@')[0] ?? '',
153
155
  photo_url: raw.photo_url,
154
156
  is_email_verified: raw.is_email_verified ?? false
155
157
  };
158
+ // id and user_id are optional after auth response cleanup (task #1552)
159
+ // Only populate them if the raw data contains them
160
+ if (raw.id) {
161
+ user.id = raw.id;
162
+ }
163
+ else if (raw.user_id) {
164
+ user.id = String(raw.user_id);
165
+ }
166
+ if (raw.user_id) {
167
+ user.user_id = raw.user_id;
168
+ }
169
+ else if (raw.id) {
170
+ user.user_id = this.hashUUID(raw.id);
171
+ }
172
+ return user;
156
173
  }
157
174
  hashUUID(uuid) {
158
175
  let hash = 0;
@@ -249,7 +266,8 @@ class StoneScriptPHPAuth {
249
266
  success: true,
250
267
  accessToken: this.resolveAccessToken(data),
251
268
  refreshToken: this.resolveRefreshToken(data),
252
- user: this.resolveUser(data)
269
+ user: this.resolveUser(data),
270
+ membership: this.resolveMembership(data)
253
271
  };
254
272
  }
255
273
  return { success: false, message: this.resolveErrorMessage(data, 'Invalid credentials') };
@@ -399,22 +417,48 @@ class StoneScriptPHPAuth {
399
417
  resolve({ success: false, message: 'Popup blocked. Please allow popups for this site.' });
400
418
  return;
401
419
  }
420
+ const cleanup = () => {
421
+ window.removeEventListener('message', messageHandler);
422
+ clearInterval(checkClosed);
423
+ if (popup && !popup.closed)
424
+ popup.close();
425
+ };
402
426
  const messageHandler = (event) => {
403
427
  if (event.origin !== new URL(accountsUrl).origin)
404
428
  return;
405
- if (event.data.type === 'oauth_success') {
406
- window.removeEventListener('message', messageHandler);
407
- popup.close();
429
+ if (event.data.type === 'oauth_new_identity') {
430
+ cleanup();
431
+ resolve({
432
+ success: true,
433
+ accessToken: event.data.access_token,
434
+ refreshToken: event.data.refresh_token,
435
+ isNewIdentity: true,
436
+ authMethod: event.data.auth_method,
437
+ oauthProvider: event.data.oauth_provider,
438
+ identity: event.data.identity,
439
+ });
440
+ }
441
+ else if (event.data.type === 'oauth_success') {
442
+ cleanup();
408
443
  const rawUser = event.data.user || this.resolveUser(event.data);
409
444
  resolve({
410
445
  success: true,
411
446
  accessToken: event.data.access_token,
412
- user: rawUser ? this.normalizeUser(rawUser) : undefined
447
+ refreshToken: event.data.refresh_token,
448
+ user: rawUser ? this.normalizeUser(rawUser) : undefined,
449
+ membership: event.data.membership,
450
+ });
451
+ }
452
+ else if (event.data.type === 'oauth_tenant_selection') {
453
+ cleanup();
454
+ resolve({
455
+ success: true,
456
+ accessToken: event.data.selection_token,
457
+ memberships: event.data.memberships,
413
458
  });
414
459
  }
415
460
  else if (event.data.type === 'oauth_error') {
416
- window.removeEventListener('message', messageHandler);
417
- popup.close();
461
+ cleanup();
418
462
  resolve({ success: false, message: event.data.message || 'OAuth login failed' });
419
463
  }
420
464
  };
@@ -454,7 +498,8 @@ class StoneScriptPHPAuth {
454
498
  async getTenantMemberships(accessToken) {
455
499
  try {
456
500
  const accountsUrl = this.getAccountsUrl();
457
- const response = await fetch(`${accountsUrl}/api/auth/memberships`, {
501
+ const platformCode = encodeURIComponent(this.config.platformCode ?? '');
502
+ const response = await fetch(`${accountsUrl}/api/auth/memberships?platform_code=${platformCode}`, {
458
503
  method: 'GET',
459
504
  headers: {
460
505
  'Authorization': `Bearer ${accessToken}`,
@@ -590,6 +635,26 @@ class StoneScriptPHPAuth {
590
635
  }
591
636
  return response.json();
592
637
  }
638
+ async provisionTenant(storeName, countryCode, accessToken) {
639
+ const apiUrl = this.getPlatformApiUrl();
640
+ const response = await fetch(`${apiUrl}/auth/provision-tenant`, {
641
+ method: 'POST',
642
+ headers: {
643
+ 'Content-Type': 'application/json',
644
+ 'Authorization': `Bearer ${accessToken}`
645
+ },
646
+ credentials: 'include',
647
+ body: JSON.stringify({
648
+ store_name: storeName,
649
+ country_code: countryCode,
650
+ })
651
+ });
652
+ if (!response.ok) {
653
+ const errorData = await response.json();
654
+ throw new Error(errorData.message || 'Failed to provision tenant');
655
+ }
656
+ return response.json();
657
+ }
593
658
  async checkEmail(email) {
594
659
  try {
595
660
  const accountsUrl = this.getAccountsUrl();
@@ -1014,6 +1079,17 @@ class AuthService {
1014
1079
  throw new Error('checkOnboardingStatus not supported');
1015
1080
  return this.plugin.checkOnboardingStatus(identityId);
1016
1081
  }
1082
+ async provisionTenant(storeName, countryCode = 'IN') {
1083
+ if (!this.plugin.provisionTenant) {
1084
+ throw new Error('provisionTenant not supported by the configured auth plugin');
1085
+ }
1086
+ const result = await this.plugin.provisionTenant(storeName, countryCode, this.tokens.getAccessToken());
1087
+ if (result?.access_token) {
1088
+ this.tokens.setAccessToken(result.access_token);
1089
+ this.signinStatus.setSigninStatus(true);
1090
+ }
1091
+ return result;
1092
+ }
1017
1093
  async completeTenantOnboarding(countryCode, tenantName, serverName) {
1018
1094
  if (!this.plugin.completeTenantOnboarding)
1019
1095
  throw new Error('completeTenantOnboarding not supported');
@@ -1762,6 +1838,7 @@ class TenantLoginComponent {
1762
1838
  createTenantLinkAction = 'Create New Organization';
1763
1839
  // Outputs
1764
1840
  tenantSelected = new EventEmitter();
1841
+ needsOnboarding = new EventEmitter();
1765
1842
  createTenant = new EventEmitter();
1766
1843
  // Form Fields
1767
1844
  email = '';
@@ -1830,8 +1907,8 @@ class TenantLoginComponent {
1830
1907
  this.error = result.message || 'Login failed';
1831
1908
  return;
1832
1909
  }
1833
- // Authentication successful, now handle tenant selection
1834
- await this.handlePostAuthFlow();
1910
+ // Authentication successful pass result so membership can be reused
1911
+ await this.handlePostAuthFlow(result);
1835
1912
  }
1836
1913
  catch (err) {
1837
1914
  this.error = err.message || 'An unexpected error occurred';
@@ -1849,8 +1926,24 @@ class TenantLoginComponent {
1849
1926
  this.error = result.message || 'OAuth login failed';
1850
1927
  return;
1851
1928
  }
1852
- // Authentication successful, now handle tenant selection
1853
- await this.handlePostAuthFlow();
1929
+ // New identity user exists but has no tenant membership
1930
+ if (result.isNewIdentity && result.identity) {
1931
+ this.needsOnboarding.emit({
1932
+ auth_method: result.authMethod || 'oauth',
1933
+ oauth_provider: result.oauthProvider,
1934
+ is_new_identity: true,
1935
+ identity: result.identity,
1936
+ });
1937
+ return;
1938
+ }
1939
+ // Multi-tenant selection — user has multiple memberships
1940
+ if (result.memberships && result.memberships.length > 0) {
1941
+ this.memberships = result.memberships;
1942
+ this.showingTenantSelector = true;
1943
+ return;
1944
+ }
1945
+ // Standard success — pass result so membership can be reused if present
1946
+ await this.handlePostAuthFlow(result);
1854
1947
  }
1855
1948
  catch (err) {
1856
1949
  this.error = err.message || 'An unexpected error occurred';
@@ -1859,7 +1952,7 @@ class TenantLoginComponent {
1859
1952
  this.loading = false;
1860
1953
  }
1861
1954
  }
1862
- async handlePostAuthFlow() {
1955
+ async handlePostAuthFlow(loginResult) {
1863
1956
  if (!this.showTenantSelector) {
1864
1957
  // Tenant selection is disabled, emit event immediately
1865
1958
  this.tenantSelected.emit({
@@ -1869,11 +1962,20 @@ class TenantLoginComponent {
1869
1962
  });
1870
1963
  return;
1871
1964
  }
1872
- // Fetch user's tenant memberships
1965
+ // Resolve memberships prefer data from login response to avoid extra API call
1873
1966
  this.loading = true;
1874
1967
  try {
1875
- const result = await this.auth.getTenantMemberships();
1876
- if (!result.memberships || result.memberships.length === 0) {
1968
+ let memberships;
1969
+ if (loginResult?.membership) {
1970
+ // Login response already included membership — use it directly
1971
+ memberships = [loginResult.membership];
1972
+ }
1973
+ else {
1974
+ // Fall back to fetching memberships from API (with platform_code param)
1975
+ const result = await this.auth.getTenantMemberships();
1976
+ memberships = result.memberships;
1977
+ }
1978
+ if (!memberships || memberships.length === 0) {
1877
1979
  // User has no tenants, prompt to create one
1878
1980
  this.error = 'You are not a member of any organization. Please create one.';
1879
1981
  if (this.allowTenantCreation) {
@@ -1881,7 +1983,7 @@ class TenantLoginComponent {
1881
1983
  }
1882
1984
  return;
1883
1985
  }
1884
- this.memberships = result.memberships;
1986
+ this.memberships = memberships;
1885
1987
  // Get user name if available
1886
1988
  const currentUser = this.auth.getCurrentUser();
1887
1989
  if (currentUser) {
@@ -1969,7 +2071,7 @@ class TenantLoginComponent {
1969
2071
  this.createTenant.emit();
1970
2072
  }
1971
2073
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginComponent, deps: [{ token: AuthService }, { token: ProviderRegistryService }], target: i0.ɵɵFactoryTarget.Component });
1972
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TenantLoginComponent, isStandalone: true, selector: "lib-tenant-login", inputs: { title: "title", providers: "providers", showTenantSelector: "showTenantSelector", autoSelectSingleTenant: "autoSelectSingleTenant", prefillEmail: "prefillEmail", allowTenantCreation: "allowTenantCreation", tenantSelectorTitle: "tenantSelectorTitle", tenantSelectorDescription: "tenantSelectorDescription", continueButtonText: "continueButtonText", registerLinkText: "registerLinkText", registerLinkAction: "registerLinkAction", createTenantLinkText: "createTenantLinkText", createTenantLinkAction: "createTenantLinkAction" }, outputs: { tenantSelected: "tenantSelected", createTenant: "createTenant" }, ngImport: i0, template: `
2074
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TenantLoginComponent, isStandalone: true, selector: "lib-tenant-login", inputs: { title: "title", providers: "providers", showTenantSelector: "showTenantSelector", autoSelectSingleTenant: "autoSelectSingleTenant", prefillEmail: "prefillEmail", allowTenantCreation: "allowTenantCreation", tenantSelectorTitle: "tenantSelectorTitle", tenantSelectorDescription: "tenantSelectorDescription", continueButtonText: "continueButtonText", registerLinkText: "registerLinkText", registerLinkAction: "registerLinkAction", createTenantLinkText: "createTenantLinkText", createTenantLinkAction: "createTenantLinkAction" }, outputs: { tenantSelected: "tenantSelected", needsOnboarding: "needsOnboarding", createTenant: "createTenant" }, ngImport: i0, template: `
1973
2075
  <div class="tenant-login-dialog">
1974
2076
  @if (!showingTenantSelector) {
1975
2077
  <!-- Step 1: Authentication -->
@@ -2334,6 +2436,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2334
2436
  type: Input
2335
2437
  }], tenantSelected: [{
2336
2438
  type: Output
2439
+ }], needsOnboarding: [{
2440
+ type: Output
2337
2441
  }], createTenant: [{
2338
2442
  type: Output
2339
2443
  }] } });
@@ -2784,7 +2888,7 @@ class AuthPageComponent {
2784
2888
  }
2785
2889
  </div>
2786
2890
  </div>
2787
- `, isInline: true, styles: [".auth-container{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;background:linear-gradient(135deg,#667eea,#764ba2)}.auth-card{background:#fff;border-radius:12px;box-shadow:0 10px 40px #0000001a;padding:40px;width:100%;max-width:480px}.logo{display:block;max-width:200px;max-height:80px;margin:0 auto 24px}.app-name{margin:0 0 12px;font-size:28px;font-weight:600;text-align:center;color:#1a202c}.subtitle{margin:0 0 32px;font-size:16px;text-align:center;color:#718096}:host ::ng-deep .tenant-login-dialog,:host ::ng-deep .register-dialog{padding:0;max-width:none}:host ::ng-deep .login-title,:host ::ng-deep .register-title{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "prefillEmail", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "createTenant"] }, { kind: "component", type: RegisterComponent, selector: "lib-register", outputs: ["navigateToLogin"] }] });
2891
+ `, isInline: true, styles: [".auth-container{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;background:linear-gradient(135deg,#667eea,#764ba2)}.auth-card{background:#fff;border-radius:12px;box-shadow:0 10px 40px #0000001a;padding:40px;width:100%;max-width:480px}.logo{display:block;max-width:200px;max-height:80px;margin:0 auto 24px}.app-name{margin:0 0 12px;font-size:28px;font-weight:600;text-align:center;color:#1a202c}.subtitle{margin:0 0 32px;font-size:16px;text-align:center;color:#718096}:host ::ng-deep .tenant-login-dialog,:host ::ng-deep .register-dialog{padding:0;max-width:none}:host ::ng-deep .login-title,:host ::ng-deep .register-title{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "prefillEmail", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "needsOnboarding", "createTenant"] }, { kind: "component", type: RegisterComponent, selector: "lib-register", outputs: ["navigateToLogin"] }] });
2788
2892
  }
2789
2893
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthPageComponent, decorators: [{
2790
2894
  type: Component,
@@ -3839,7 +3943,7 @@ class TenantLoginDialogComponent {
3839
3943
  (createTenant)="onCreateTenant()">
3840
3944
  </lib-tenant-login>
3841
3945
  </div>
3842
- `, isInline: true, styles: [".dialog-wrapper{padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "prefillEmail", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "createTenant"] }] });
3946
+ `, isInline: true, styles: [".dialog-wrapper{padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "prefillEmail", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "needsOnboarding", "createTenant"] }] });
3843
3947
  }
3844
3948
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginDialogComponent, decorators: [{
3845
3949
  type: Component,