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

input-output-hk / lace / 7115777528

06 Dec 2023 02:18PM UTC coverage: 52.945% (-0.9%) from 53.839%
7115777528

push

github

8d78ac
pczeglik-iohk
chore(extension): use dapp-connector patch (#739)

1989 of 4674 branches covered (0.0%)

Branch coverage included in aggregate %.

4483 of 7550 relevant lines covered (59.38%)

56.08 hits per line

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

90.91
/apps/browser-extension-wallet/src/lib/scripts/background/services/userIdService.ts
1
import { exposeApi } from '@cardano-sdk/web-extension';
2✔
2
import { Wallet } from '@lace/cardano';
3
import { of, BehaviorSubject } from 'rxjs';
2✔
4
import { runtime } from 'webextension-polyfill';
2✔
5
import {
2✔
6
  clearBackgroundStorage,
7
  getBackgroundStorage,
8
  hashExtendedAccountPublicKey,
9
  setBackgroundStorage
10
} from '@lib/scripts/background/util';
11
import { USER_ID_SERVICE_BASE_CHANNEL, UserIdService as UserIdServiceInterface } from '@lib/scripts/types';
2✔
12
import randomBytes from 'randombytes';
2✔
13
import { userIdServiceProperties } from '../config';
2✔
14
import { getChainNameByNetworkMagic } from '@src/utils/get-chain-name-by-network-magic';
2✔
15
import { UserTrackingType } from '@providers/AnalyticsProvider/analyticsTracker';
2✔
16
import isUndefined from 'lodash/isUndefined';
2✔
17

18
// eslint-disable-next-line no-magic-numbers
19
export const SESSION_LENGTH = Number(process.env.SESSION_LENGTH_IN_SECONDS || 1800) * 1000;
2✔
20
export const USER_ID_BYTE_SIZE = 8;
2✔
21

22
export class UserIdService implements UserIdServiceInterface {
2✔
23
  private randomizedUserId?: string;
24
  private walletBasedUserId?: string;
25
  private sessionTimeout?: NodeJS.Timeout;
26
  private userIdRestored = false;
26✔
27
  public userTrackingType$ = new BehaviorSubject<UserTrackingType>(UserTrackingType.Basic);
26✔
28
  private hasNewSessionStarted = false;
26✔
29

30
  constructor(
31
    private getStorage: typeof getBackgroundStorage = getBackgroundStorage,
26✔
32
    private setStorage: typeof setBackgroundStorage = setBackgroundStorage,
26✔
33
    private clearStorage: typeof clearBackgroundStorage = clearBackgroundStorage,
26✔
34
    private sessionLength: number = SESSION_LENGTH
26✔
35
  ) {}
36

37
  async init(): Promise<void> {
38
    if (!this.userIdRestored) {
44✔
39
      console.debug('[ANALYTICS] Restoring user ID...');
22✔
40
      await this.restoreUserId();
22✔
41
    }
42

43
    if (!this.randomizedUserId) {
44✔
44
      console.debug('[ANALYTICS] User ID not found - generating new one');
22✔
45
      this.randomizedUserId = randomBytes(USER_ID_BYTE_SIZE).toString('hex');
22✔
46
    }
47
  }
48

49
  private async getWalletBasedUserId(networkMagic: Wallet.Cardano.NetworkMagic): Promise<string | undefined> {
50
    const { keyAgentsByChain, usePersistentUserId } = await this.getStorage();
16✔
51

52
    if (!keyAgentsByChain) {
16✔
53
      console.debug('[ANALYTICS] Key agents not found - Wallet not created yet');
8✔
54
      return undefined;
8✔
55
    }
56

57
    if (!usePersistentUserId) {
8✔
58
      return undefined;
4✔
59
    }
60

61
    if (!this.walletBasedUserId) {
4✔
62
      const chainName = getChainNameByNetworkMagic(networkMagic);
4✔
63
      const extendedAccountPublicKey = keyAgentsByChain[chainName].keyAgentData.extendedAccountPublicKey;
4✔
64
      this.walletBasedUserId = this.generateWalletBasedUserId(extendedAccountPublicKey);
4✔
65

66
      if (this.userTrackingType$.value !== UserTrackingType.Enhanced) {
4✔
67
        this.userTrackingType$.next(UserTrackingType.Enhanced);
2✔
68
      }
69
    }
70
    console.debug(`[ANALYTICS] getwalletBasedUserId() called (current Wallet Based ID: ${this.walletBasedUserId})`);
4✔
71
    // eslint-disable-next-line consistent-return
72
    return this.walletBasedUserId;
4✔
73
  }
74

75
  // TODO: make this method private when Motamo is not longer in use
76
  async getRandomizedUserId(): Promise<string> {
77
    await this.init();
40✔
78

79
    console.debug(`[ANALYTICS] getId() called (current ID: ${this.randomizedUserId})`);
40✔
80
    return this.randomizedUserId;
40✔
81
  }
82

83
  async getUserId(networkMagic: Wallet.Cardano.NetworkMagic): Promise<string> {
84
    const walletBasedId = await this.getWalletBasedUserId(networkMagic);
10✔
85

86
    if (!walletBasedId) {
10✔
87
      return await this.getRandomizedUserId();
8✔
88
    }
89

90
    return walletBasedId;
2✔
91
  }
92

93
  async getAliasProperties(networkMagic: Wallet.Cardano.NetworkMagic): Promise<{ alias: string; id: string }> {
94
    const id = await this.getWalletBasedUserId(networkMagic);
6✔
95
    const alias = await this.getRandomizedUserId();
6✔
96
    return { alias, id };
6✔
97
  }
98

99
  async resetToDefaultValues(): Promise<void> {
100
    const { usePersistentUserId, userId } = await this.getStorage();
×
101
    if (isUndefined(usePersistentUserId) && isUndefined(userId)) {
×
102
      await this.clearId();
×
103
      this.userIdRestored = false;
×
104
    }
105
  }
106

107
  async clearId(): Promise<void> {
108
    console.debug('[ANALYTICS] clearId() called');
2✔
109
    this.randomizedUserId = undefined;
2✔
110
    this.walletBasedUserId = undefined;
2✔
111
    this.userTrackingType$.next(UserTrackingType.Basic);
2✔
112
    this.clearSessionTimeout();
2✔
113
    this.hasNewSessionStarted = false;
2✔
114
    await this.clearStorage({ keys: ['userId', 'usePersistentUserId'] });
2✔
115
  }
116

117
  async makePersistent(): Promise<void> {
118
    console.debug('[ANALYTICS] Converting user ID into persistent');
4✔
119
    this.clearSessionTimeout();
4✔
120
    this.setSessionTimeout();
4✔
121
    const userId = await this.getRandomizedUserId();
4✔
122
    await this.setStorage({ usePersistentUserId: true, userId });
4✔
123
    this.userTrackingType$.next(UserTrackingType.Enhanced);
4✔
124
  }
125

126
  async makeTemporary(): Promise<void> {
127
    console.debug('[ANALYTICS] Converting user ID into temporary');
4✔
128
    await this.setStorage({ usePersistentUserId: false, userId: undefined });
4✔
129
    this.setSessionTimeout();
4✔
130
    this.userTrackingType$.next(UserTrackingType.Basic);
4✔
131
  }
132

133
  async extendLifespan(): Promise<void> {
134
    console.debug('[ANALYTICS] Extending temporary ID lifespan');
2✔
135
    this.clearSessionTimeout();
2✔
136
    this.setSessionTimeout();
2✔
137
  }
138

139
  private async restoreUserId(): Promise<void> {
140
    const { userId, usePersistentUserId } = await this.getStorage();
22✔
141

142
    if (usePersistentUserId) {
22✔
143
      console.debug('[ANALYTICS] Restoring user ID from extension storage');
8✔
144
      this.randomizedUserId = userId;
8✔
145
    }
146

147
    this.userIdRestored = true;
22✔
148
    const trackingType = usePersistentUserId ? UserTrackingType.Enhanced : UserTrackingType.Basic;
22✔
149
    if (trackingType !== this.userTrackingType$.value) {
22✔
150
      this.userTrackingType$.next(trackingType);
8✔
151
    }
152
  }
153

154
  private setSessionTimeout(): void {
155
    if (this.sessionTimeout) {
10!
156
      return;
×
157
    }
158
    this.sessionTimeout = setTimeout(() => {
10✔
159
      if (this.userTrackingType$.value === UserTrackingType.Basic) {
4✔
160
        this.randomizedUserId = undefined;
4✔
161
      }
162
      this.hasNewSessionStarted = false;
4✔
163
    }, this.sessionLength);
164
  }
165

166
  private clearSessionTimeout(): void {
167
    clearTimeout(this.sessionTimeout);
8✔
168
    this.sessionTimeout = undefined;
8✔
169
  }
170

171
  private generateWalletBasedUserId(extendedAccountPublicKey: Wallet.Crypto.Bip32PublicKeyHex) {
172
    console.debug('[ANALYTICS] Wallet based ID not found - generating new one');
4✔
173
    // by requirement, we want to hash the extended account public key twice
174
    const hash = hashExtendedAccountPublicKey(extendedAccountPublicKey);
4✔
175
    return hashExtendedAccountPublicKey(hash);
4✔
176
  }
177

178
  async isNewSession(): Promise<boolean> {
179
    const isNewSession = !this.hasNewSessionStarted;
×
180
    this.hasNewSessionStarted = true;
×
181
    return isNewSession;
×
182
  }
183
}
184

185
const userIdService = new UserIdService();
2✔
186

187
exposeApi<UserIdServiceInterface>(
2✔
188
  {
189
    api$: of(userIdService),
190
    baseChannel: USER_ID_SERVICE_BASE_CHANNEL,
191
    properties: userIdServiceProperties
192
  },
193
  { logger: console, runtime }
194
);
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

© 2025 Coveralls, Inc