Skip to content

Commit 0b6fc77

Browse files
committed
chore: add logger
1 parent 8a63dff commit 0b6fc77

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
@@ -40,6 +40,8 @@ Wrap your app's root component with ContentpassSdkProvider. The provider require
4040
- `propertyId` - Your unique property ID
4141
- `issuer` - The OAuth 2.0 server URL (e.g. `https://my.contentpass.net`)
4242
- `redirectUrl` - the redirect URL of your app to which the OAuth2 server will redirect after the authentication
43+
- `samplingRate` - Optional: The rate at which the SDK will send impression events for unauthenticated users. Default is 0.05 (5%)
44+
- `logLevel` - Optional: The log level for the SDK. By default logger is disabled. Possible values are 'info', 'warn', 'error' and 'debug'
4345

4446

4547
```jsx
@@ -50,6 +52,8 @@ const contentpassConfig = {
5052
propertyId: 'your-property-id',
5153
issuer: 'https://my.contentpass.net',
5254
redirectUrl: 'com.yourapp://oauthredirect',
55+
samplingRate: 0.1,
56+
logLevel: 'info'
5357
};
5458

5559
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
@@ -12,4 +12,5 @@ export const contentpassConfig: ContentpassConfig = {
1212

1313
samplingRate: 1,
1414
redirectUrl: 'de.contentpass.demo://oauth',
15+
logLevel: 'debug',
1516
};

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 {
@@ -85,11 +93,13 @@ export default class Contentpass implements ContentpassInterface {
8593

8694
throw err;
8795
}
96+
logger.info('Authentication flow finished, checking subscription...');
8897

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

92101
public registerObserver(observer: ContentpassObserver) {
102+
logger.info('Registering observer');
93103
if (this.contentpassStateObservers.includes(observer)) {
94104
return;
95105
}
@@ -99,12 +109,14 @@ export default class Contentpass implements ContentpassInterface {
99109
}
100110

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

107118
public logout = async () => {
119+
logger.info('Logging out and clearing auth state');
108120
await this.authStateStorage.clearOidcAuthState();
109121
this.changeContentpassState({
110122
state: ContentpassStateType.UNAUTHENTICATED,
@@ -113,6 +125,7 @@ export default class Contentpass implements ContentpassInterface {
113125
};
114126

115127
public recoverFromError = async () => {
128+
logger.info('Recovering from error');
116129
this.changeContentpassState({
117130
state: ContentpassStateType.INITIALISING,
118131
});
@@ -137,6 +150,7 @@ export default class Contentpass implements ContentpassInterface {
137150
};
138151

139152
private countPaidImpression = async () => {
153+
logger.info('Counting paid impression');
140154
const impressionId = uuid.v4();
141155

142156
await sendPageViewEvent(this.config.apiUrl, {
@@ -154,6 +168,7 @@ export default class Contentpass implements ContentpassInterface {
154168
return;
155169
}
156170

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

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

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

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

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

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

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

256+
logger.debug({ timeDiff }, 'Setting up refresh timer');
228257
this.refreshTimer = setTimeout(async () => {
229258
await this.refreshToken(0);
230259
}, timeDiff);
@@ -239,6 +268,7 @@ export default class Contentpass implements ContentpassInterface {
239268
}
240269

241270
try {
271+
logger.info('Refreshing token');
242272
const refreshResult = await refresh(
243273
{
244274
clientId: this.config.propertyId,
@@ -251,23 +281,26 @@ export default class Contentpass implements ContentpassInterface {
251281
}
252282
);
253283
await this.onNewAuthState(refreshResult);
284+
logger.info('Token refreshed successfully');
254285
} catch (err: any) {
255286
await this.onRefreshTokenError(counter, err);
256287
}
257288
};
258289

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

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

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,9 +1,12 @@
11
/* istanbul ignore file */
22

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

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)