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

safe-global / safe-client-gateway / 11340871916

15 Oct 2024 06:58AM UTC coverage: 46.83% (-45.0%) from 91.836%
11340871916

push

github

web-flow
Bump typescript from 5.6.2 to 5.6.3 (#2015)

Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.6.2 to 5.6.3.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Changelog](https://github.com/microsoft/TypeScript/blob/main/azure-pipelines.release.yml)
- [Commits](https://github.com/microsoft/TypeScript/compare/v5.6.2...v5.6.3)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

500 of 3096 branches covered (16.15%)

Branch coverage included in aggregate %.

5092 of 8845 relevant lines covered (57.57%)

12.16 hits per line

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

29.73
/src/datasources/push-notifications-api/firebase-cloud-messaging-api.service.ts
1
import { IConfigurationService } from '@/config/configuration.service.interface';
16✔
2
import { CacheRouter } from '@/datasources/cache/cache.router';
16✔
3
import {
16✔
4
  CacheService,
5
  ICacheService,
6
} from '@/datasources/cache/cache.service.interface';
7
import { HttpErrorFactory } from '@/datasources/errors/http-error-factory';
16✔
8
import {
16✔
9
  NetworkService,
10
  INetworkService,
11
} from '@/datasources/network/network.service.interface';
12
import { IPushNotificationsApi } from '@/domain/interfaces/push-notifications-api.interface';
13
import { Inject, Injectable } from '@nestjs/common';
16✔
14
import { FirebaseNotification } from '@/datasources/push-notifications-api/entities/firebase-notification.entity';
15
import { IJwtService } from '@/datasources/jwt/jwt.service.interface';
16✔
16

17
@Injectable()
18
export class FirebaseCloudMessagingApiService implements IPushNotificationsApi {
16✔
19
  private static readonly OAuth2TokenUrl =
16✔
20
    'https://oauth2.googleapis.com/token';
21
  private static readonly OAuth2TokenTtlBufferInSeconds = 5;
16✔
22
  private static readonly Scope =
16✔
23
    'https://www.googleapis.com/auth/firebase.messaging';
24

25
  private readonly baseUrl: string;
26
  private readonly project: string;
27
  private readonly clientEmail: string;
28
  private readonly privateKey: string;
29

30
  constructor(
31
    @Inject(NetworkService)
32
    private readonly networkService: INetworkService,
×
33
    @Inject(IConfigurationService)
34
    private readonly configurationService: IConfigurationService,
×
35
    @Inject(CacheService)
36
    private readonly cacheService: ICacheService,
×
37
    @Inject(IJwtService)
38
    private readonly jwtService: IJwtService,
×
39
    private readonly httpErrorFactory: HttpErrorFactory,
×
40
  ) {
41
    this.baseUrl = this.configurationService.getOrThrow<string>(
×
42
      'pushNotifications.baseUri',
43
    );
44
    this.project = this.configurationService.getOrThrow<string>(
×
45
      'pushNotifications.project',
46
    );
47
    // Service account credentials are used for OAuth2 assertion
48
    this.clientEmail = this.configurationService.getOrThrow<string>(
×
49
      'pushNotifications.serviceAccount.clientEmail',
50
    );
51
    this.privateKey = this.configurationService.getOrThrow<string>(
×
52
      'pushNotifications.serviceAccount.privateKey',
53
    );
54
  }
55

56
  /**
57
   * Enqueues a notification to be sent to a device with given FCM token.
58
   *
59
   * @param fcmToken - device's FCM token
60
   * @param notification - notification payload
61
   */
62
  async enqueueNotification(
63
    fcmToken: string,
64
    notification: FirebaseNotification,
65
  ): Promise<void> {
66
    const url = `${this.baseUrl}/${this.project}/messages:send`;
×
67
    try {
×
68
      const accessToken = await this.getOauth2Token();
×
69
      await this.networkService.post({
×
70
        url,
71
        data: {
72
          message: {
73
            token: fcmToken,
74
            notification,
75
          },
76
        },
77
        networkRequest: {
78
          headers: {
79
            Authorization: `Bearer ${accessToken}`,
80
          },
81
        },
82
      });
83
    } catch (error) {
84
      /**
85
       * TODO: Error handling based on `error.details[i].reason`, e.g.
86
       * - expired OAuth2 token
87
       * - stale FCM token
88
       * - don't expose the error to clients, logging on domain level
89
       */
90
      throw this.httpErrorFactory.from(error);
×
91
    }
92
  }
93

94
  /**
95
   * Retrieves and caches OAuth2 token for Firebase Cloud Messaging API.
96
   *
97
   * @returns - OAuth2 token
98
   */
99
  // TODO: Use CacheFirstDataSource
100
  private async getOauth2Token(): Promise<string> {
101
    const cacheDir = CacheRouter.getFirebaseOAuth2TokenCacheDir();
×
102
    const cachedToken = await this.cacheService.hGet(cacheDir);
×
103

104
    if (cachedToken) {
×
105
      return cachedToken;
×
106
    }
107

108
    const { data } = await this.networkService.post<{
×
109
      access_token: string;
110
      expires_in: number;
111
      token_type: string;
112
    }>({
113
      url: FirebaseCloudMessagingApiService.OAuth2TokenUrl,
114
      data: {
115
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
116
        assertion: this.getAssertion(),
117
      },
118
    });
119

120
    // Token cached according to issuance
121
    await this.cacheService.hSet(
×
122
      cacheDir,
123
      data.access_token,
124
      // Buffer ensures token is not cached beyond expiration if caching took time
125
      data.expires_in -
126
        FirebaseCloudMessagingApiService.OAuth2TokenTtlBufferInSeconds,
127
    );
128

129
    return data.access_token;
×
130
  }
131

132
  /**
133
   * Generates a signed JWT assertion for OAuth2 token request.
134
   *
135
   * @returns - signed JWT assertion
136
   */
137
  private getAssertion(): string {
138
    const now = new Date();
×
139

140
    const payload = {
×
141
      alg: 'RS256',
142
      iss: this.clientEmail,
143
      scope: FirebaseCloudMessagingApiService.Scope,
144
      aud: FirebaseCloudMessagingApiService.OAuth2TokenUrl,
145
      iat: now,
146
      // Maximum expiration time is 1 hour
147
      exp: new Date(now.getTime() + 60 * 60 * 1_000),
148
    };
149

150
    return this.jwtService.sign(payload, {
×
151
      secretOrPrivateKey: this.privateKey,
152
    });
153
  }
154
}
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