Skip to content

Commit

Permalink
Remote settings small update
Browse files Browse the repository at this point in the history
  • Loading branch information
RNEvok committed Jun 24, 2024
1 parent cbafc04 commit a94d8c1
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 35 deletions.
8 changes: 3 additions & 5 deletions api/api.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { create } from "apisauce";
import { CONFIG } from './../config';
import {
GetRemoteSettingsRequest,
GetRemoteSettingsResponse
} from "../types/types";
import * as T from "../types/api";

const SOCKET_EVENTS_LISTEN = {
CONNECT: "connect",
Expand Down Expand Up @@ -58,7 +55,8 @@ const updateAuthorizationHeaderWithApiKey = (apiKey: string) => {

const api = {
sauce,
getRemoteSettingsGet: (params: GetRemoteSettingsRequest) => sauceAuthorizedApiKey.get<GetRemoteSettingsResponse>(`/project/${params.projectId}/remotesettings`)
getRemoteSettingsGet: (params: T.GetRemoteSettingsRequest) => sauceAuthorizedApiKey.get<T.GetRemoteSettingsResponse>(`/project/${params.projectId}/remotesettings`),
personalSettingGet: (params: T.PersonalSettingRequest) => sauce.get<T.PersonalSettingResponse>(`/client/one/${params.apiKey}`),
};

export {
Expand Down
7 changes: 7 additions & 0 deletions helpers/apiResponseValidators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ApiResponse } from "apisauce";

export type ApiResponseValidator = (apiResponse: ApiResponse<any>) => boolean;

export const classicApiResponseValidator = (apiResponse: ApiResponse<any>) => {
return Boolean(apiResponse.ok && apiResponse.data);
};
36 changes: 26 additions & 10 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,6 @@ declare module '@appklaar/codebud' {
onResponse: (data: NetworkInterceptorOnResponsePayload) => void;
};

export type ErrorResponse = {
message?: string;
invalidParameters?: string[];
};

type InstructionPrototype = "login" | "logout";

type BaseParamType = "number" | "string" | "object" | "array" | "boolean";
Expand Down Expand Up @@ -171,12 +166,17 @@ declare module '@appklaar/codebud' {

export type RemoteSettingsListenersTable = ListenersTable<RemoteSettings>;

export type GetRemoteSettingsResponse = {
remoteSettings: RemoteSettings
} & ErrorResponse;

export type RefreshRemoteSettingsCallback = (r: RemoteSettings) => void;

export type ProjectSetting = {
remoteSettingsEnabled: boolean;
};

// Key - projectId
export type PersonalProjectsSetting = {[key: string]: ProjectSetting};

export type RefreshPersonalProjectsSettingCallback = (s: PersonalProjectsSetting) => void;

export interface AppKlaarSdk {
/**
* Initialize the module.
Expand Down Expand Up @@ -206,12 +206,28 @@ declare module '@appklaar/codebud' {
* @param {string} env Remote settings environment.
* @returns {ObjectT<string> | null} Last fetched remote settings object (selected environment).
*/
getRemoteSettingsByEnv: (env: RemoteSettingsEnv) => ObjectT<string> | null,
getRemoteSettingsByEnv: (env: RemoteSettingsEnv) => ObjectT<string> | null;
/**
* @returns {boolean} Flag that determines that CodeBud remote settings are currently preferable for your project. Note: if package mode is "prod", false will be returned.
*/
getIsRemoteSettingsPreferableForSelectedProject: () => boolean;
/**
* Function that takes 2 args and returns one of them depending on package mode and your personal "preferable" toogle for chosen projectId on Control tab in GUI
* @param {any} valueA Option "A" that will be returned if CodeBud remote settings are currently preferable for your project
* @param {any} valueB Option "B" that will be returned if CodeBud remote settings are currently NOT preferable for your project
* @returns {boolean} valueA if CodeBud remote settings are currently preferable for your project, and valueB otherwise.
*/
getPersonalPreferableValueForSelectedProject: (valueA: any, valueB: any) => any;
/**
* Function for refreshing remote settings.
* @param {RefreshRemoteSettingsCallback} callbackFn Function that will be called if request succeeded.
*/
refreshRemoteSettings: (callbackFn?: RefreshRemoteSettingsCallback) => void;
/**
* Function for refreshing personal projects settings.
* @param {RefreshPersonalProjectsSettingCallback} callbackFn Function that will be called if request succeeded.
*/
refreshPersonalProjectsSettings: (callbackFn?: RefreshPersonalProjectsSettingCallback) => void;
/**
* Function that creates Redux Store Change Handler, that you can use to subscribe to Store Changes.
* @param {any} store Your store.
Expand Down
19 changes: 17 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { connector } from './Connector';
import { OnEventUsersCustomCallback, RefreshRemoteSettingsCallback, RemoteSettingsEnv } from './types';
import { OnEventUsersCustomCallback, RefreshPersonalProjectsSettingCallback, RefreshRemoteSettingsCallback, RemoteSettingsEnv } from './types';
import { MODULE_STATES } from './States';
import { validateApiKey } from './constants/regex';
import { AppKlaarSdk as ModuleInterface } from './moduleInterface';
Expand Down Expand Up @@ -39,7 +39,7 @@ export const CodeBud: ModuleInterface = {

updateAuthorizationHeaderWithApiKey(apiKey);
if (config?.projectInfo) {
remoteSettingsService.init(config.projectInfo.projectId, config.remoteSettingsAutoUpdateInterval);
remoteSettingsService.init(config.projectInfo.projectId, apiKey, config.remoteSettingsAutoUpdateInterval);
}

if (config?.mode === "prod") {
Expand Down Expand Up @@ -72,10 +72,25 @@ export const CodeBud: ModuleInterface = {
return remoteSettingsService.remoteSettings?.[env] ?? null;
},

getIsRemoteSettingsPreferableForSelectedProject() {
if (this._mode === "prod")
return false;

return remoteSettingsService.isRemoteSettingsPreferable();
},

getPersonalPreferableValueForSelectedProject(valueA: any, valueB: any) {
return this.getIsRemoteSettingsPreferableForSelectedProject() ? valueA : valueB;
},

async refreshRemoteSettings(callbackFn?: RefreshRemoteSettingsCallback) {
remoteSettingsService.refreshRemoteSettings(callbackFn);
},

async refreshPersonalProjectsSettings(callbackFn?: RefreshPersonalProjectsSettingCallback) {
remoteSettingsService.refreshPersonalProjectsSetting(callbackFn);
},

createReduxStoreChangeHandler(store, selectFn, batchingTimeMs = 500) {
try {
if (!connector.isInit)
Expand Down
20 changes: 18 additions & 2 deletions moduleInterface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { OnEventUsersCustomCallback, RefreshRemoteSettingsCallback, RemoteSettings, PackageConfig, Instruction, InstructionGroup, PackageMode, ObjectT, RemoteSettingsEnv } from './types/types';
import { OnEventUsersCustomCallback, RefreshRemoteSettingsCallback, RemoteSettings, PackageConfig, Instruction, InstructionGroup, PackageMode, ObjectT, RemoteSettingsEnv, RefreshPersonalProjectsSettingCallback } from './types/types';
import { ModuleState } from './States';

export interface AppKlaarSdk {
Expand Down Expand Up @@ -33,12 +33,28 @@ export interface AppKlaarSdk {
* @param {string} env Remote settings environment.
* @returns {ObjectT<string> | null} Last fetched remote settings object (selected environment).
*/
getRemoteSettingsByEnv: (env: RemoteSettingsEnv) => ObjectT<string> | null,
getRemoteSettingsByEnv: (env: RemoteSettingsEnv) => ObjectT<string> | null;
/**
* @returns {boolean} Flag that determines that CodeBud remote settings are currently preferable for your project. Note: if package mode is "prod", false will be returned.
*/
getIsRemoteSettingsPreferableForSelectedProject: () => boolean;
/**
* Function that takes 2 args and returns one of them depending on package mode and your personal "preferable" toogle for chosen projectId on Control tab in GUI
* @param {any} valueA Option "A" that will be returned if CodeBud remote settings are currently preferable for your project
* @param {any} valueB Option "B" that will be returned if CodeBud remote settings are currently NOT preferable for your project
* @returns {boolean} valueA if CodeBud remote settings are currently preferable for your project, and valueB otherwise.
*/
getPersonalPreferableValueForSelectedProject: (valueA: any, valueB: any) => any;
/**
* Function for refreshing remote settings.
* @param {RefreshRemoteSettingsCallback} callbackFn Function that will be called if request succeeded.
*/
refreshRemoteSettings: (callbackFn?: RefreshRemoteSettingsCallback) => void;
/**
* Function for refreshing personal projects settings.
* @param {RefreshPersonalProjectsSettingCallback} callbackFn Function that will be called if request succeeded.
*/
refreshPersonalProjectsSettings: (callbackFn?: RefreshPersonalProjectsSettingCallback) => void;
/**
* Function that creates Redux Store Change Handler, that you can use to subscribe to Store Changes.
* @param {any} store Your store.
Expand Down
58 changes: 53 additions & 5 deletions services/remoteSettingsService.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { validateHexId24Symbols } from "../constants/regex";
import { codebudConsoleWarn } from "../helpers/helperFunctions";
import { RefreshRemoteSettingsCallback, RemoteSettings, RemoteSettingsListenersTable } from "../types/types";
import { PersonalProjectsSetting, RefreshPersonalProjectsSettingCallback, RefreshRemoteSettingsCallback, RemoteSettings, RemoteSettingsListenersTable } from "../types/types";
import { api } from './../api/api';
import { classicApiResponseValidator } from './../helpers/apiResponseValidators';

class RemoteSettingsService {
private _projectId: string = "";
private _apiKey: string = "";
private _isInit: boolean = false;
private _remoteSettings: RemoteSettings | null = null;
private _remoteSettingsListenersTable: RemoteSettingsListenersTable = {};
private _personalProjectsSetting: PersonalProjectsSetting | null = null;
private _autoUpdateTimer: NodeJS.Timer | null = null;

public addRemoteSettingsListener(key: string, handler: (r: RemoteSettings) => any) {
Expand All @@ -29,18 +32,49 @@ class RemoteSettingsService {
this._remoteSettingsListenersTable[key](r);
}

private _onGotNewPersonalProjectsSetting(s: PersonalProjectsSetting) {
if (!this._isInit)
return;

this._personalProjectsSetting = s;
}

public async refreshPersonalProjectsSetting(callbackFn?: RefreshPersonalProjectsSettingCallback) {
try {
if (!this._isInit) {
throw new Error("Remote settings service is not initiated. Please make sure that you've passed correct projectId and check console for related errors.");
}

const personalProjectsSetting = await api.personalSettingGet({apiKey: this._apiKey})
.then((response) => {
if (classicApiResponseValidator(response)) {
return response.data!.client.projectsSetting;
} else {
throw new Error("Server returned an error (personalSettingGet)");
}
});

this._onGotNewPersonalProjectsSetting(personalProjectsSetting);
callbackFn?.(personalProjectsSetting);
} catch (e) {
codebudConsoleWarn("Error while trying to fetch personal projects settings", e);
}
}

public async refreshRemoteSettings(callbackFn?: RefreshRemoteSettingsCallback) {
try {
if (!this._isInit) {
throw new Error("Remote settings service is not initiated. Please make sure that you've passed correct projectId and check console for related errors.");
}

this.refreshPersonalProjectsSetting();

const remoteSettings = await api.getRemoteSettingsGet({projectId: this._projectId})
.then((response) => {
if (response.ok && response.data) {
return response.data?.remoteSettings
if (classicApiResponseValidator(response)) {
return response.data!.remoteSettings;
} else {
throw new Error("Response returned an error");
throw new Error("Server returned an error (getRemoteSettingsGet)");
}
});

Expand All @@ -51,7 +85,7 @@ class RemoteSettingsService {
}
}

public init(projectId: string, autoUpdateInterval?: number) {
public init(projectId: string, apiKey: string, autoUpdateInterval?: number) {
try {
if (this._isInit)
throw new Error("Already initiated!");
Expand All @@ -66,6 +100,7 @@ class RemoteSettingsService {
}

this._projectId = projectId;
this._apiKey = apiKey; // We assume that apiKey has already been validated

this.refreshRemoteSettings();
if (autoUpdateInterval !== undefined)
Expand All @@ -79,13 +114,26 @@ class RemoteSettingsService {
return this._remoteSettings;
}

public isRemoteSettingsPreferable() {
if (!this._isInit)
return false;

return !!this._personalProjectsSetting?.[this._projectId]?.remoteSettingsEnabled;
}

public getPreferableValue(valueA: any, valueB: any) {
return this.isRemoteSettingsPreferable() ? valueA : valueB;
}

public clear() {
if (this._autoUpdateTimer !== null)
clearInterval(this._autoUpdateTimer);

this._projectId = "";
this._apiKey = "";
this._remoteSettings = null;
this._remoteSettingsListenersTable = {};
this._personalProjectsSetting = null;
this._isInit = false;
}
};
Expand Down
22 changes: 22 additions & 0 deletions types/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RemoteSettings, UserProfilePublicData } from "./types";

export type ErrorResponse = {
message?: string;
invalidParameters?: string[];
};

export type GetRemoteSettingsResponse = {
remoteSettings: RemoteSettings
} & ErrorResponse;

export type GetRemoteSettingsRequest = {
projectId: string;
};

export type PersonalSettingRequest = {
apiKey: string;
};

export type PersonalSettingResponse = {
client: UserProfilePublicData;
} & ErrorResponse;
22 changes: 11 additions & 11 deletions types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,6 @@ export type NetworkInterceptorCallbacksTable = {
onResponse: (data: NetworkInterceptorOnResponsePayload) => void;
};

export type ErrorResponse = {
message?: string;
invalidParameters?: string[];
};

type InstructionPrototype = "login" | "logout";

type BaseParamType = "number" | "string" | "object" | "array" | "boolean";
Expand Down Expand Up @@ -207,15 +202,20 @@ export type RemoteSettings = {[env in RemoteSettingsEnv]: ObjectT<string>};

export type RemoteSettingsListenersTable = ListenersTable<RemoteSettings>;

export type GetRemoteSettingsResponse = {
remoteSettings: RemoteSettings
} & ErrorResponse;
export type RefreshRemoteSettingsCallback = (r: RemoteSettings) => void;

export type GetRemoteSettingsRequest = {
projectId: string;
export type ProjectSetting = {
remoteSettingsEnabled: boolean;
};

export type RefreshRemoteSettingsCallback = (r: RemoteSettings) => void;
// Key - projectId
export type PersonalProjectsSetting = {[key: string]: ProjectSetting};

export type UserProfilePublicData = {
projectsSetting: PersonalProjectsSetting;
};

export type RefreshPersonalProjectsSettingCallback = (s: PersonalProjectsSetting) => void;

export type TanStackQueryKey = readonly unknown[];

Expand Down

0 comments on commit a94d8c1

Please sign in to comment.