Skip to content

Commit ef6abf5

Browse files
committed
chore: add logger
1 parent f561f92 commit ef6abf5

File tree

8 files changed

+79
-3
lines changed

8 files changed

+79
-3
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Wrap your app's root component with ContentpassSdkProvider. The provider require
4141
- `planId` - The ID of the plan you want to check the user's subscription status against (ask Contentpass team for details)
4242
- `issuer` - The OAuth 2.0 server URL (e.g. `https://my.contentpass.net`)
4343
- `redirectUrl` - the redirect URL of your app to which the OAuth2 server will redirect after the authentication
44+
- `samplingRate` - Optional: The rate at which the SDK will send impression events for unauthenticated users. Default is 0.05 (5%)
45+
- `logLevel` - Optional: The log level for the SDK. By default logger is disabled. Possible values are 'info', 'warn', 'error' and 'debug'
4446

4547

4648
```jsx
@@ -52,6 +54,8 @@ const contentpassConfig = {
5254
planId: 'plan-id',
5355
issuer: 'https://my.contentpass.net',
5456
redirectUrl: 'com.yourapp://oauthredirect',
57+
samplingRate: 0.1,
58+
logLevel: 'info'
5559
};
5660

5761
const App = () => {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@
151151
},
152152
"dependencies": {
153153
"@sentry/react-native": "^6.3.0",
154+
"react-native-logs": "^5.3.0",
154155
"react-native-uuid": "^2.0.3"
155156
}
156157
}

sharedExample/src/contentpassConfig.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export const contentpassConfig: ContentpassConfig = {
1414

1515
samplingRate: 1,
1616
redirectUrl: 'de.contentpass.demo://oauth',
17+
logLevel: 'debug',
1718
};

src/Contentpass.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { ContentpassConfig } from './types/ContentpassConfig';
1919
import { reportError, setSentryExtraAttribute } from './sentryIntegration';
2020
import sendStats from './countImpressionUtils/sendStats';
2121
import sendPageViewEvent from './countImpressionUtils/sendPageViewEvent';
22+
import logger, { enableLogger } from './logger';
2223

2324
const DEFAULT_SAMPLING_RATE = 0.05;
2425

@@ -47,10 +48,16 @@ export default class Contentpass implements ContentpassInterface {
4748
private refreshTimer: NodeJS.Timeout | null = null;
4849

4950
constructor(config: ContentpassConfig) {
51+
if (config.logLevel) {
52+
enableLogger(config.logLevel);
53+
}
54+
55+
logger.debug('Contentpass initialised with config', config);
5056
if (
5157
config.samplingRate &&
5258
(config.samplingRate < 0 || config.samplingRate > 1)
5359
) {
60+
logger.error('Sampling rate must be between 0 and 1');
5461
throw new Error('Sampling rate must be between 0 and 1');
5562
}
5663
this.samplingRate = config.samplingRate || DEFAULT_SAMPLING_RATE;
@@ -62,6 +69,7 @@ export default class Contentpass implements ContentpassInterface {
6269
}
6370

6471
public authenticate = async (): Promise<void> => {
72+
logger.info('Starting authentication flow');
6573
let result: AuthorizeResult;
6674

6775
try {
@@ -86,11 +94,13 @@ export default class Contentpass implements ContentpassInterface {
8694

8795
throw err;
8896
}
97+
logger.info('Authentication flow finished, checking subscription...');
8998

9099
await this.onNewAuthState(result);
91100
};
92101

93102
public registerObserver(observer: ContentpassObserver) {
103+
logger.info('Registering observer');
94104
if (this.contentpassStateObservers.includes(observer)) {
95105
return;
96106
}
@@ -100,12 +110,14 @@ export default class Contentpass implements ContentpassInterface {
100110
}
101111

102112
public unregisterObserver(observer: ContentpassObserver) {
113+
logger.info('Unregistering observer');
103114
this.contentpassStateObservers = this.contentpassStateObservers.filter(
104115
(o) => o !== observer
105116
);
106117
}
107118

108119
public logout = async () => {
120+
logger.info('Logging out and clearing auth state');
109121
await this.authStateStorage.clearOidcAuthState();
110122
this.changeContentpassState({
111123
state: ContentpassStateType.UNAUTHENTICATED,
@@ -114,6 +126,7 @@ export default class Contentpass implements ContentpassInterface {
114126
};
115127

116128
public recoverFromError = async () => {
129+
logger.info('Recovering from error');
117130
this.changeContentpassState({
118131
state: ContentpassStateType.INITIALISING,
119132
});
@@ -138,6 +151,7 @@ export default class Contentpass implements ContentpassInterface {
138151
};
139152

140153
private countPaidImpression = async () => {
154+
logger.info('Counting paid impression');
141155
const impressionId = uuid.v4();
142156

143157
await sendPageViewEvent(this.config.apiUrl, {
@@ -155,6 +169,7 @@ export default class Contentpass implements ContentpassInterface {
155169
return;
156170
}
157171

172+
logger.info('Counting sampled impression');
158173
await sendStats(this.config.apiUrl, {
159174
ea: 'load',
160175
ec: 'tcf-sampled',
@@ -167,38 +182,49 @@ export default class Contentpass implements ContentpassInterface {
167182
private initialiseAuthState = async () => {
168183
const authState = await this.authStateStorage.getOidcAuthState();
169184
if (authState) {
185+
logger.debug('Found auth state in storage, initialising with it');
170186
await this.onNewAuthState(authState);
171187
return;
172188
}
173189

190+
logger.debug(
191+
'No auth state found in storage, initialising unauthenticated'
192+
);
174193
this.changeContentpassState({
175194
state: ContentpassStateType.UNAUTHENTICATED,
176195
hasValidSubscription: false,
177196
});
178197
};
179198

180199
private onNewAuthState = async (authState: OidcAuthState) => {
200+
logger.debug('New auth state received');
181201
this.oidcAuthState = authState;
182202
await this.authStateStorage.storeOidcAuthState(authState);
183203

184204
const strategy = this.setupRefreshTimer();
185205
// if instant refresh, no need to check subscription as it will happen in the refresh
186206
if (strategy === RefreshTokenStrategy.INSTANTLY) {
207+
logger.debug('Instant refresh, skipping subscription check');
187208
return;
188209
}
189210

190211
try {
212+
logger.info('Checking subscription');
191213
const contentpassToken = await fetchContentpassToken({
192214
issuer: this.config.issuer,
193215
propertyId: this.config.propertyId,
194216
idToken: this.oidcAuthState.idToken,
195217
});
196218
const hasValidSubscription = validateSubscription(contentpassToken);
219+
logger.info({ hasValidSubscription }, 'Subscription check successful');
197220
this.changeContentpassState({
198221
state: ContentpassStateType.AUTHENTICATED,
199222
hasValidSubscription,
200223
});
201224
} catch (err: any) {
225+
reportError(err, {
226+
msg: 'Failed to fetch contentpass token and validate subscription',
227+
});
202228
this.changeContentpassState({
203229
state: ContentpassStateType.ERROR,
204230
error: err,
@@ -211,13 +237,15 @@ export default class Contentpass implements ContentpassInterface {
211237
this.oidcAuthState?.accessTokenExpirationDate;
212238

213239
if (!accessTokenExpirationDate) {
240+
logger.warn('No access token expiration date provided');
214241
return RefreshTokenStrategy.NO_REFRESH;
215242
}
216243

217244
const now = new Date();
218245
const expirationDate = new Date(accessTokenExpirationDate);
219246
const timeDiff = expirationDate.getTime() - now.getTime();
220247
if (timeDiff <= 0) {
248+
logger.debug('Access token expired, refreshing instantly');
221249
this.refreshToken(0);
222250
return RefreshTokenStrategy.INSTANTLY;
223251
}
@@ -226,6 +254,7 @@ export default class Contentpass implements ContentpassInterface {
226254
clearTimeout(this.refreshTimer);
227255
}
228256

257+
logger.debug({ timeDiff }, 'Setting up refresh timer');
229258
this.refreshTimer = setTimeout(async () => {
230259
await this.refreshToken(0);
231260
}, timeDiff);
@@ -240,6 +269,7 @@ export default class Contentpass implements ContentpassInterface {
240269
}
241270

242271
try {
272+
logger.info('Refreshing token');
243273
const refreshResult = await refresh(
244274
{
245275
clientId: this.config.propertyId,
@@ -252,23 +282,26 @@ export default class Contentpass implements ContentpassInterface {
252282
}
253283
);
254284
await this.onNewAuthState(refreshResult);
285+
logger.info('Token refreshed successfully');
255286
} catch (err: any) {
256287
await this.onRefreshTokenError(counter, err);
257288
}
258289
};
259290

260291
private onRefreshTokenError = async (counter: number, err: Error) => {
261-
reportError(err, {
262-
msg: `Failed to refresh token after ${counter} retries`,
263-
});
264292
// FIXME: add handling for specific error to not retry in every case
265293
if (counter < REFRESH_TOKEN_RETRIES) {
294+
logger.warn({ err, counter }, 'Failed to refresh token, retrying');
266295
const delay = counter * 1000 * 10;
267296
await new Promise((resolve) => setTimeout(resolve, delay));
268297
await this.refreshToken(counter + 1);
269298
return;
270299
}
271300

301+
reportError(err, {
302+
msg: `Failed to refresh token after ${counter} retries`,
303+
});
304+
272305
await this.logout();
273306
};
274307

src/logger.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { consoleTransport, logger } from 'react-native-logs';
2+
import type { Severity } from './types/ContentpassConfig';
3+
4+
const log = logger.createLogger({
5+
// by default loggger is disabled
6+
enabled: false,
7+
transport: consoleTransport,
8+
transportOptions: {
9+
colors: {
10+
info: 'blueBright',
11+
warn: 'yellowBright',
12+
error: 'redBright',
13+
},
14+
},
15+
});
16+
17+
export const enableLogger = (severity: Severity) => {
18+
log.setSeverity(severity);
19+
log.enable();
20+
21+
log.debug('Logger enabled with severity', severity);
22+
};
23+
24+
export default log;

src/sentryIntegration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as Sentry from '@sentry/react-native';
22
import { defaultStackParser, makeFetchTransport } from '@sentry/react';
33
import { getDefaultIntegrations } from '@sentry/react-native/dist/js/integrations/default';
4+
import logger from './logger';
45

56
// as it's only the open source package, we want to have minimal sentry configuration here to not override sentry instance,
67
// which can be used in the application
@@ -43,6 +44,7 @@ type ReportErrorOptions = {
4344
};
4445

4546
export const reportError = (err: Error, { msg }: ReportErrorOptions = {}) => {
47+
logger.error({ err }, msg || 'Unexpected error');
4648
if (msg) {
4749
sentryScope.addBreadcrumb({
4850
category: 'Error',

src/types/ContentpassConfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
/* istanbul ignore file */
22

3+
export type Severity = 'debug' | 'info' | 'warn' | 'error';
4+
35
export type ContentpassConfig = {
46
propertyId: string;
57
planId: string;
68
redirectUrl: string;
79
issuer: string;
810
apiUrl: string;
911
samplingRate?: number;
12+
logLevel?: Severity;
1013
};

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1844,6 +1844,7 @@ __metadata:
18441844
react-native-app-auth: ^8.0.0
18451845
react-native-builder-bob: ^0.32.1
18461846
react-native-encrypted-storage: ^4.0.3
1847+
react-native-logs: ^5.3.0
18471848
react-native-uuid: ^2.0.3
18481849
react-test-renderer: 18.3.1
18491850
release-it: ^17.10.0
@@ -12588,6 +12589,13 @@ __metadata:
1258812589
languageName: node
1258912590
linkType: hard
1259012591

12592+
"react-native-logs@npm:^5.3.0":
12593+
version: 5.3.0
12594+
resolution: "react-native-logs@npm:5.3.0"
12595+
checksum: b7fa58007e737fe337fbd664529054cfe71a1fa8f2f4cf50621ffca0f50b44f70dcb7d37f3222093c7b36a287a79b00f1b84d243fb15dc67650c72b6c514343a
12596+
languageName: node
12597+
linkType: hard
12598+
1259112599
"react-native-uuid@npm:^2.0.3":
1259212600
version: 2.0.3
1259312601
resolution: "react-native-uuid@npm:2.0.3"

0 commit comments

Comments
 (0)