Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn commitlint --edit $1
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn lint && yarn typescript && yarn test
13 changes: 13 additions & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'header-max-length': [2, 'always', 80],
// Disable all the body and footer max length rules since CommitLint cannot handle multiline text in body and footer making these rules too much of a nuisance
'body-max-length': [0, 'always'],
'body-max-line-length': [0, 'always'],
'footer-max-length': [0, 'always'],
'footer-max-line-length': [0, 'always'],
// Also disable this rule since it will always complain due to the multiline incompatibility
'footer-leading-blank': [0, 'always'],
},
};
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ PODS:
- sovran-react-native
- segment-analytics-react-native-plugin-idfa (0.2.1):
- React-Core
- sovran-react-native (0.2.6):
- sovran-react-native (0.2.7):
- React-Core
- Yoga (1.14.0)

Expand Down Expand Up @@ -461,9 +461,9 @@ SPEC CHECKSUMS:
RNGestureHandler: 77d59828d40838c9fabb76a12d2d0a80c006906f
segment-analytics-react-native: 5287504fa5aa60e64dbb497bee5c7eb6f94e5e49
segment-analytics-react-native-plugin-idfa: 80e5d610f537156833eabea12a1804523355de95
sovran-react-native: ef02f663b489ac5e63ea7b80cd8426bf82992263
sovran-react-native: 8d549886ad24ab51f8d471a7db83d1a3ace36358
Yoga: 90dcd029e45d8a7c1ff059e8b3c6612ff409061a

PODFILE CHECKSUM: 0c7eb82d495ca56953c50916b7b49e7512632eb6

COCOAPODS: 1.11.2
COCOAPODS: 1.11.3
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@react-native-community/masked-view": "^0.1.11",
"@react-navigation/native": "^6.0.2",
"@react-navigation/stack": "^6.0.7",
"@segment/sovran-react-native": "^0.2.6",
"@segment/sovran-react-native": "^0.2.7",
"react": "17.0.2",
"react-native": "0.67.3",
"react-native-bootsplash": "^3.2.4",
Expand Down
8 changes: 4 additions & 4 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1575,10 +1575,10 @@
color "^3.1.3"
warn-once "^0.1.0"

"@segment/sovran-react-native@^0.2.6":
version "0.2.6"
resolved "https://registry.yarnpkg.com/@segment/sovran-react-native/-/sovran-react-native-0.2.6.tgz#895ef37b71c299f56c89515cf8d200f13fce2251"
integrity sha512-SxqKvMvgu9PZo0jSkZ0yys08H9qJbI1uGbaeBvJFZfs92TIvQmcW0PYQZsspqt85RPzrL4J9KDio6xoLXmHurw==
"@segment/sovran-react-native@^0.2.7":
version "0.2.7"
resolved "https://registry.yarnpkg.com/@segment/sovran-react-native/-/sovran-react-native-0.2.7.tgz#5df47d00a862481ab1f3f07bcc4b8e0a737930ae"
integrity sha512-P4pv3yIbUMv1X54TGioZb+8m4DiDCyKBRuyheqbFEblZeZSckI+WuRp/pooQeyHoGBMQbOY/j0yKksY3Yydkvg==
dependencies:
"@react-native-async-storage/async-storage" "^1.15.15"
ansi-regex "5.0.1"
Expand Down
19 changes: 4 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"packages/plugins/*"
],
"scripts": {
"bootstrap": "yarn install && yarn example install && yarn example pods",
"bootstrap": "yarn install && yarn example install && yarn example pods && husky install",
"core": "yarn workspace @segment/analytics-react-native",
"example": "yarn --cwd example",
"build": "yarn workspaces run build",
Expand All @@ -20,27 +20,16 @@
"<rootDir>/packages/*"
]
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "yarn lint && yarn typescript && yarn test"
}
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"devDependencies": {
"@changesets/cli": "^2.16.0",
"@commitlint/config-conventional": "^11.0.0",
"@commitlint/config-conventional": "^16.2.4",
"@react-native-community/eslint-config": "^2.0.0",
"@release-it/conventional-changelog": "^2.0.0",
"commitlint": "^11.0.0",
"commitlint": "^16.2.4",
"eslint": "^7.2.0",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-prettier": "^3.1.3",
"husky": "^4.2.5",
"husky": "^8.0.0",
"jest": "^27.3.1",
"prettier": "^2.3.2",
"release-it": "14.12.4",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"homepage": "https://github.com/segmentio/analytics-react-native#readme",
"dependencies": {
"@react-native-async-storage/async-storage": "^1.15.17",
"@segment/sovran-react-native": "^0.2.6",
"@segment/sovran-react-native": "^0.2.7",
"deepmerge": "^4.2.2",
"js-base64": "^3.7.2",
"nanoid": "^3.1.25"
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/__tests__/__helpers__/mockSegmentStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export class MockSegmentStore implements Storage {
set: (value: DeepPartial<Context>) => {
this.data.context = { ...value };
this.callbacks.context.run(value);
return this.data.context;
},
};

Expand All @@ -110,6 +111,7 @@ export class MockSegmentStore implements Storage {
set: (value: SegmentAPIIntegrations) => {
this.data.settings = value;
this.callbacks.settings.run(value);
return this.data.settings;
},
add: (key: string, value: IntegrationSettings) => {
this.data.settings[key] = value;
Expand Down Expand Up @@ -143,6 +145,7 @@ export class MockSegmentStore implements Storage {
set: (value: UserInfoState) => {
this.data.userInfo = value;
this.callbacks.userInfo.run(value);
return this.data.userInfo;
},
};

Expand Down
23 changes: 23 additions & 0 deletions packages/core/src/__tests__/internal/checkInstalledVersion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,27 @@ describe('internal #checkInstalledVersion', () => {
deepmerge(newContext, injectedContextByPlugins)
);
});

it('executes callback when context is updated in store', async () => {
client = new SegmentClient(clientArgs);
const callback = jest.fn().mockImplementation(() => {
expect(store.context.get()).toEqual(currentContext);
});
client.onContextLoaded(callback);
jest.spyOn(context, 'getContext').mockResolvedValueOnce(currentContext);
await client.init();
expect(callback).toHaveBeenCalled();
});

it('executes callback immediatley if registered after context was already loaded', async () => {
client = new SegmentClient(clientArgs);
jest.spyOn(context, 'getContext').mockResolvedValueOnce(currentContext);
await client.init();
// Register callback after context is loaded
const callback = jest.fn().mockImplementation(() => {
expect(store.context.get()).toEqual(currentContext);
});
client.onContextLoaded(callback);
expect(callback).toHaveBeenCalled();
});
});
30 changes: 29 additions & 1 deletion packages/core/src/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ import {
SegmentAPIIntegrations,
SegmentAPISettings,
SegmentEvent,
UpdateType,
UserInfoState,
UserTraits,
} from './types';
import { getPluginsWithFlush, getPluginsWithReset } from './util';
import { getUUID } from './uuid';

type OnContextLoadCallback = (type: UpdateType) => void | Promise<void>;

export class SegmentClient {
// the config parameters for the client - a merge of user provided and default options
private config: Config;
Expand Down Expand Up @@ -71,6 +74,10 @@ export class SegmentClient {

private isInitialized = false;

private isContextLoaded = false;

private onContextLoadedCallback: OnContextLoadCallback | undefined;

get platformPlugins() {
const plugins: PlatformPlugin[] = [];

Expand Down Expand Up @@ -568,8 +575,14 @@ export class SegmentClient {
const previousContext = this.store.context.get();

// Only overwrite the previous context values to preserve any values that are added by enrichment plugins like IDFA
this.store.context.set(deepmerge(previousContext ?? {}, context));
await this.store.context.set(deepmerge(previousContext ?? {}, context));

// Only callback during the intial context load
if (this.onContextLoadedCallback !== undefined && !this.isContextLoaded) {
this.onContextLoadedCallback(UpdateType.initial);
}

this.isContextLoaded = true;
if (!this.config.trackAppLifecycleEvents) {
return;
}
Expand Down Expand Up @@ -669,4 +682,19 @@ export class SegmentClient {

this.logger.info('Client has been reset');
}

/**
* Registers a callback for when the client has loaded the device context. This happens at the startup of the app, but
* it is handy for plugins that require context data during configure as it guarantees the context data is available.
*
* If the context is already loaded it will call the callback immediately.
*
* @param callback Function to call when context is ready.
*/
onContextLoaded(callback: OnContextLoadCallback) {
this.onContextLoadedCallback = callback;
if (this.isContextLoaded) {
this.onContextLoadedCallback(UpdateType.initial);
}
}
}
15 changes: 9 additions & 6 deletions packages/core/src/storage/sovranStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,21 +160,23 @@ export class SovranStorage implements Storage {
get: () => this.contextStore.getState().context,
onChange: (callback: (value?: DeepPartial<Context>) => void) =>
this.contextStore.subscribe((store) => callback(store.context)),
set: (value: DeepPartial<Context>) => {
this.contextStore.dispatch((state) => {
set: async (value: DeepPartial<Context>) => {
const { context } = await this.contextStore.dispatch((state) => {
return { context: { ...state.context, ...value } };
});
return context;
},
};
readonly settings = {
get: () => this.settingsStore.getState().settings,
onChange: (
callback: (value?: SegmentAPIIntegrations | undefined) => void
) => this.settingsStore.subscribe((store) => callback(store.settings)),
set: (value: SegmentAPIIntegrations) => {
this.settingsStore.dispatch((state) => {
set: async (value: SegmentAPIIntegrations) => {
const { settings } = await this.settingsStore.dispatch((state) => {
return { settings: { ...state.settings, ...value } };
});
return settings;
},
add: (key: string, value: IntegrationSettings) => {
this.settingsStore.dispatch((state) => ({
Expand Down Expand Up @@ -209,10 +211,11 @@ export class SovranStorage implements Storage {
get: () => this.userInfoStore.getState().userInfo,
onChange: (callback: (value: UserInfoState) => void) =>
this.userInfoStore.subscribe((store) => callback(store.userInfo)),
set: (value: UserInfoState) => {
this.userInfoStore.dispatch((state) => ({
set: async (value: UserInfoState) => {
const { userInfo } = await this.userInfoStore.dispatch((state) => ({
userInfo: { ...state.userInfo, ...value },
}));
return userInfo;
},
};

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/storage/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface Watchable<T> {
* Implements a value that can be set
*/
export interface Settable<T> {
set: (value: T) => void;
set: (value: T) => T | Promise<T>;
}

/**
Expand Down
Loading