Skip to content

feat: send client info to Flagsmith #293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 17, 2025
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
23 changes: 21 additions & 2 deletions flagsmith-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ const initError = function(caller: string) {
return "Attempted to " + caller + " a user before calling flagsmith.init. Call flagsmith.init first, if you wish to prevent it sending a request for flags, call init with preventFetch:true."
}

type Config = { browserlessStorage?: boolean, fetch?: LikeFetch, AsyncStorage?: AsyncStorageType, eventSource?: any };
type Config = {
browserlessStorage?: boolean,
fetch?: LikeFetch,
AsyncStorage?: AsyncStorageType,
eventSource?: any,
applicationMetadata?: IInitConfig['applicationMetadata'],
};

const FLAGSMITH_CONFIG_ANALYTICS_KEY = "flagsmith_value_";
const FLAGSMITH_FLAG_ANALYTICS_KEY = "flagsmith_enabled_";
Expand All @@ -63,6 +69,7 @@ const Flagsmith = class {
timestamp: number|null = null
isLoading = false
eventSource:EventSource|null = null
applicationMetadata: IInitConfig['applicationMetadata'];
constructor(props: Config) {
if (props.fetch) {
_fetch = props.fetch as LikeFetch;
Expand All @@ -71,6 +78,7 @@ const Flagsmith = class {
}

this.canUseStorage = typeof window !== 'undefined' || !!props.browserlessStorage;
this.applicationMetadata = props.applicationMetadata;

this.log("Constructing flagsmith instance " + props)
if (props.eventSource) {
Expand Down Expand Up @@ -305,6 +313,7 @@ const Flagsmith = class {
angularHttpClient,
_trigger,
_triggerLoadingState,
applicationMetadata,
} = config;
evaluationContext.environment = environmentID ? {apiKey: environmentID} : evaluationContext.environment;
if (!evaluationContext.environment || !evaluationContext.environment.apiKey) {
Expand Down Expand Up @@ -351,6 +360,7 @@ const Flagsmith = class {
this.ticks = 10000;
this.timer = this.enableLogs ? new Date().valueOf() : null;
this.cacheFlags = typeof AsyncStorage !== 'undefined' && !!cacheFlags;
this.applicationMetadata = applicationMetadata;

FlagsmithEvent = DEFAULT_FLAGSMITH_EVENT + "_" + evaluationContext.environment.apiKey;

Expand Down Expand Up @@ -775,7 +785,7 @@ const Flagsmith = class {
}

private getJSON = (url: string, method?: 'GET' | 'POST' | 'PUT', body?: string) => {
const { evaluationContext, headers } = this;
const { headers } = this;
const options: RequestOptions = {
method: method || 'GET',
body,
Expand All @@ -788,6 +798,15 @@ const Flagsmith = class {
if (method && method !== 'GET')
options.headers['Content-Type'] = 'application/json; charset=utf-8';


if (this.applicationMetadata?.name) {
options.headers['Flagsmith-Application-Name'] = this.applicationMetadata.name;
}

if (this.applicationMetadata?.version) {
options.headers['Flagsmith-Application-Version'] = this.applicationMetadata.version;
}

if (headers) {
Object.assign(options.headers, headers);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/flagsmith/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flagsmith",
"version": "9.1.0",
"version": "9.2.0",
"description": "Feature flagging to support continuous development",
"main": "./index.js",
"module": "./index.mjs",
Expand Down
2 changes: 1 addition & 1 deletion lib/react-native-flagsmith/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-flagsmith",
"version": "9.1.0",
"version": "9.2.0",
"description": "Feature flagging to support continuous development",
"main": "./index.js",
"repository": {
Expand Down
64 changes: 64 additions & 0 deletions test/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,68 @@ describe('Flagsmith.init', () => {
});
expect(onError).toHaveBeenCalledWith(new Error('Mocked fetch error'));
});
test('should send app name and version headers when provided', async () => {
const onChange = jest.fn();
const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({
onChange,
applicationMetadata: {
name: 'Test App',
version: '1.2.3',
},
});

await flagsmith.init(initConfig);
expect(mockFetch).toHaveBeenCalledTimes(1);
expect(mockFetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.objectContaining({
'Flagsmith-Application-Name': 'Test App',
'Flagsmith-Application-Version': '1.2.3',
}),
}),
);

});
test('should send app name headers when provided', async () => {
const onChange = jest.fn();
const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({
onChange,
applicationMetadata: {
name: 'Test App',
},
});

await flagsmith.init(initConfig);
expect(mockFetch).toHaveBeenCalledTimes(1);
expect(mockFetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.objectContaining({
'Flagsmith-Application-Name': 'Test App',
}),
}),
);

});

test('should not send app name and version headers when not provided', async () => {
const onChange = jest.fn();
const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({
onChange,
});

await flagsmith.init(initConfig);
expect(mockFetch).toHaveBeenCalledTimes(1);
expect(mockFetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.not.objectContaining({
'Flagsmith-Application-Name': 'Test App',
'Flagsmith-Application-Version': '1.2.3',
}),
}),
);
});

});
10 changes: 10 additions & 0 deletions types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ export declare type LoadingState = {
}

export type OnChange<F extends string = string> = (previousFlags: IFlags<F> | null, params: IRetrieveInfo, loadingState:LoadingState) => void

export type ApplicationMetadata = {
name: string;
version?: string;
}

export interface IInitConfig<F extends string = string, T extends string = string> {
AsyncStorage?: any;
api?: string;
Expand All @@ -115,6 +121,10 @@ export interface IInitConfig<F extends string = string, T extends string = strin
state?: IState;
_trigger?: () => void;
_triggerLoadingState?: () => void;
/**
* Customer application metadata
*/
applicationMetadata?: ApplicationMetadata;
}

export interface IFlagsmithResponse {
Expand Down