• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

damienbod / angular-auth-oidc-client / 16102496676

06 Jul 2025 07:35PM UTC coverage: 92.115% (-0.6%) from 92.71%
16102496676

Pull #2107

github

web-flow
Merge b6293b5a6 into 5304cc1cd
Pull Request #2107: Fix silent refresh iframe with multiple idps

722 of 863 branches covered (83.66%)

Branch coverage included in aggregate %.

28 of 39 new or added lines in 4 files covered. (71.79%)

1 existing line in 1 file now uncovered.

2631 of 2777 relevant lines covered (94.74%)

8.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

49.28
/projects/angular-auth-oidc-client/src/lib/iframe/refresh-session-iframe.service.ts
1
import { DOCUMENT } from '@angular/common';
2
import { inject, Injectable, RendererFactory2 } from '@angular/core';
3
import { Observable } from 'rxjs';
4
import { switchMap } from 'rxjs/operators';
5
import { OpenIdConfiguration } from '../config/openid-configuration';
6
import { LoggerService } from '../logging/logger.service';
7
import { UrlService } from '../utils/url/url.service';
8
import { SilentRenewService, IFRAME_FOR_SILENT_RENEW_IDENTIFIER } from './silent-renew.service';
9

10
@Injectable({ providedIn: 'root' })
11
export class RefreshSessionIframeService {
1✔
12
  private readonly renderer = inject(RendererFactory2).createRenderer(
10✔
13
    null,
14
    null
15
  );
16
  private readonly loggerService = inject(LoggerService);
10✔
17
  private readonly urlService = inject(UrlService);
10✔
18
  private readonly silentRenewService = inject(SilentRenewService);
10✔
19
  private readonly document = inject(DOCUMENT);
10✔
20

21
  refreshSessionWithIframe(
22
    config: OpenIdConfiguration,
23
    allConfigs: OpenIdConfiguration[],
24
    customParams?: { [key: string]: string | number | boolean }
25
  ): Observable<boolean> {
26
    this.loggerService.logDebug(
1✔
27
      config,
28
      'BEGIN refresh session Authorize Iframe renew'
29
    );
30

31
    return this.urlService
1✔
32
      .getRefreshSessionSilentRenewUrl(config, customParams)
33
      .pipe(
34
        switchMap((url) => {
35
          return this.sendAuthorizeRequestUsingSilentRenew(
1✔
36
            url,
37
            config,
38
            allConfigs
39
          );
40
        })
41
      );
42
  }
43

44
  private sendAuthorizeRequestUsingSilentRenew(
45
    url: string | null,
46
    config: OpenIdConfiguration,
47
    allConfigs: OpenIdConfiguration[]
48
  ): Observable<boolean> {
49
    const sessionIframe = this.silentRenewService.getOrCreateIframe(config);
×
50

51
    this.initSilentRenewRequest(config, allConfigs);
×
52
    this.loggerService.logDebug(
×
53
      config,
54
      `sendAuthorizeRequestUsingSilentRenew for URL: ${url}`
55
    );
56

57
    return new Observable((observer) => {
×
58
      const onLoadHandler = (): void => {
×
59
        sessionIframe.removeEventListener('load', onLoadHandler);
×
60
        this.loggerService.logDebug(
×
61
          config,
62
          'removed event listener from IFrame'
63
        );
64
        observer.next(true);
×
65
        observer.complete();
×
66
      };
67

68
      sessionIframe.addEventListener('load', onLoadHandler);
×
69
      sessionIframe.contentWindow?.location.replace(url ?? '');
×
70
    });
71
  }
72

73
  private initSilentRenewRequest(
74
    config: OpenIdConfiguration,
75
    allConfigs: OpenIdConfiguration[]
76
  ): void {
77
    const instanceId = Math.random();
1✔
78

79
    this.loggerService.logDebug(
1✔
80
      config,
81
      `Creating new silent renew handlers for config: ${config.configId}, instance: ${instanceId}`
82
    );
83

84
    const initDestroyHandler = this.renderer.listen(
1✔
85
      'window',
86
      'oidc-silent-renew-init',
87
      (e: CustomEvent) => {
NEW
88
        const eventData = e.detail;
×
89

NEW
90
        if (typeof eventData === 'object' && eventData.configId && eventData.instanceId) {
×
NEW
91
          if (eventData.configId === config.configId && eventData.instanceId !== instanceId) {
×
NEW
92
            this.loggerService.logDebug(
×
93
              config,
94
              `Destroying old handlers for config: ${config.configId} (old instance: ${instanceId}, new instance: ${eventData.instanceId})`
95
            );
NEW
96
            initDestroyHandler();
×
NEW
97
            renewDestroyHandler();
×
98
          }
NEW
99
        } else if (e.detail !== instanceId) {
×
100
          // Fallback for backward compatibility
101
          initDestroyHandler();
×
102
          renewDestroyHandler();
×
103
        }
104
      }
105
    );
106
    const renewDestroyHandler = this.renderer.listen(
1✔
107
      'window',
108
      'oidc-silent-renew-message',
109
      (e: CustomEvent) => {
110

NEW
111
        if (this.shouldProcessEvent(e, config)) {
×
NEW
112
          const eventToPass = this.convertToLegacyEvent(e);
×
113

NEW
114
          this.silentRenewService.silentRenewEventHandler(eventToPass, config, allConfigs);
×
115
        }
116
      }
117
    );
118

119

120
    this.document.defaultView?.dispatchEvent(
1✔
121
      new CustomEvent('oidc-silent-renew-init', {
122
        detail: {
123
          instanceId,
124
          configId: config.configId
125
        }
126
      })
127
    );
128
  }
129

130
  private shouldProcessEvent(
131
    e: CustomEvent,
132
    config: OpenIdConfiguration
133
  ): boolean {
134

135
    if (e?.detail == null) {
5✔
136
      this.loggerService.logDebug(
1✔
137
        config,
138
        `Silent renew event has no payload: ${e?.detail}`
139
      );
140

141
      return false;
1✔
142
    }
143

144
    if (e.detail.srcFrameId != null) {
4✔
145

146
      const frameIdPrefix = `${IFRAME_FOR_SILENT_RENEW_IDENTIFIER}_`;
3✔
147
      let eventConfigId: string | null = null;
3✔
148

149
      if (e.detail.srcFrameId.startsWith(frameIdPrefix)) {
3✔
150
        eventConfigId = e.detail.srcFrameId.substring(frameIdPrefix.length);
2✔
151
      }
152

153
      const shouldProcess = eventConfigId === config.configId;
3✔
154

155
      this.loggerService.logDebug(
3✔
156
        config,
157
        `Silent renew event from frame: ${e.detail.srcFrameId}, extracted configId: ${eventConfigId}, current configId: ${config.configId}, processing: ${shouldProcess}`
158
      );
159

160
      return shouldProcess;
3✔
161
    }
162

163
    // Fallback for backward compatibility - if no srcFrameId but has detail (legacy format)
164
    this.loggerService.logDebug(
1✔
165
      config,
166
      'Silent renew event without srcFrameId - processing for backward compatibility'
167
    );
168

169
    return true;
1✔
170
  }
171

172
  private convertToLegacyEvent(e: CustomEvent): CustomEvent {
173
    // If event has the new format with url property, convert it to legacy format
174
    if (e?.detail?.url != null) {
2✔
175
      return new CustomEvent(e.type, { detail: e.detail.url });
1✔
176
    }
177

178
    // Otherwise, return as-is (already in legacy format)
179
    return e;
1✔
180
  }
181

182
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc