Skip to content

Commit

Permalink
[DX-2335] feat: Send checkout widgets version to analytics service (#873
Browse files Browse the repository at this point in the history
)
  • Loading branch information
CodeSchwert authored Sep 21, 2023
1 parent 64a9760 commit 389c4de
Show file tree
Hide file tree
Showing 21 changed files with 542 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/sdk @immutable/developer-experience
/packages/immutablex_client @immutable/developer-experience
/packages/config @immutable/developer-experience
/packages/internal/analytics @immutable/developer-experience
/packages/internal/generated-clients @immutable/developer-experience
/packages/internal/toolkit @immutable/wallets
/packages/internal/cryptofiat @immutable/wallets
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/build-lint-typecheck-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ jobs:
- name: Install dependencies
run: yarn install --immutable

- name: Build
run: yarn build

- name: Test
run: yarn test

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"packages/provider",
"packages/provider/src/sample-app",
"packages/orderbook",
"packages/internal/analytics",
"packages/internal/contracts",
"packages/internal/toolkit",
"packages/internal/cryptofiat",
Expand Down
1 change: 1 addition & 0 deletions packages/checkout/widgets-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@biom3/design-tokens": "0.2.4-beta",
"@biom3/react": "^0.9.17-beta",
"@ethersproject/providers": "^5.7.2",
"@imtbl/analytics": "0.0.0",
"@imtbl/bridge-sdk": "0.0.0",
"@imtbl/checkout-sdk": "0.0.0",
"@imtbl/checkout-widgets": "0.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ReactDOM from 'react-dom/client';
import { Web3Provider } from '@ethersproject/providers';
import { sdkVersionCheck, sdkVersion } from '@imtbl/analytics';
import { Checkout } from '@imtbl/checkout-sdk';
import { Passport } from '@imtbl/passport';
import { StrongCheckoutWidgetsConfig, withDefaultWidgetConfigs } from '../lib/withDefaultWidgetConfig';
Expand Down Expand Up @@ -44,6 +45,8 @@ export abstract class ImmutableWebComponent extends HTMLElement {
const widgetConfig = this.getAttribute('widgetconfig') || undefined;
this.widgetConfig = this.parseWidgetConfig(widgetConfig);
this.updateCheckout();
// Leave version at the end so the widgets will load even if the next call fails
sdkVersionCheck('checkout-widgets', sdkVersion);
}

private parseWidgetConfig(widgetsConfig?: string): StrongCheckoutWidgetsConfig {
Expand Down
8 changes: 8 additions & 0 deletions packages/internal/analytics/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": ["../../../.eslintrc"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"tsconfigRootDir": "."
}
}
7 changes: 7 additions & 0 deletions packages/internal/analytics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Analytics

Internal analytics package,

## Version Check

The version check is a simple function that checks the current version of the SDK.
17 changes: 17 additions & 0 deletions packages/internal/analytics/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Config } from 'jest';

const config: Config = {
clearMocks: true,
coverageProvider: 'v8',
moduleDirectories: ['node_modules', 'src'],
testEnvironment: 'jsdom',
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest',
},
transformIgnorePatterns: [],
testEnvironmentOptions: {
url: 'http://localhost',
},
};

export default config;
41 changes: 41 additions & 0 deletions packages/internal/analytics/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@imtbl/analytics",
"description": "Analytics module for Immutable SDK",
"version": "0.0.0",
"author": "Immutable",
"bugs": "https://github.com/immutable/ts-immutable-sdk/issues",
"dependencies": {
"axios": "^1.3.5"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.0.0",
"@swc/jest": "^0.2.24",
"@types/jest": "^29.4.3",
"eslint": "^8.40.0",
"http-server": "^14.1.1",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"rollup": "^3.17.2",
"typescript": "^4.9.5"
},
"engines": {
"node": ">=16.11.0"
},
"files": [
"dist"
],
"homepage": "https://github.com/immutable/ts-immutable-sdk#readme",
"main": "dist/index.js",
"private": true,
"repository": "immutable/ts-immutable-sdk.git",
"scripts": {
"build": "NODE_ENV=production rollup --config rollup.config.js",
"lint": "eslint ./src --ext .ts,.jsx,.tsx --max-warnings=0",
"start:checkout-js-server": "http-server -p 8080 -c-1 ../../../sdk/dist",
"start:checkout-web-server": "http-server -p 8081 -c-1 ./src/version-check/checkout-widgets-test",
"test": "jest",
"typecheck": "tsc --noEmit --jsx preserve"
},
"type": "module",
"types": "dist/index.d.ts"
}
10 changes: 10 additions & 0 deletions packages/internal/analytics/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import typescript from '@rollup/plugin-typescript';

export default {
input: 'src/index.ts',
output: {
dir: 'dist',
format: 'es',
},
plugins: [typescript()],
};
1 change: 1 addition & 0 deletions packages/internal/analytics/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { sdkVersionCheck, sdkVersion } from './version-check';
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Checkout Widgets (Manual) Test Site

This is a manual test site for the checkout widgets. It is used to test the `sdkVersionCheck` function called by checkout widgets in a local environment.

The run the site, you need to start a local web server to host the built JS bundle, and another web server to host the test website.

## Usage

1. Update the target endpoint the checkout bundle will send version check requests to.

- Find the `packages/internal/analytics/version-check/versionCheck.ts` file.
- Update the `imtblApi` variable (near the top of the file) to point to the endpoint you want to send the version check requests to.
- Suggest using something like `https://webhook.site/` or a local webserver setup to log requests.

2. Build the SDK:

```bash
# from the root of the SDK repo
yarn build
```

2. Start the JS bundle server:

```bash
# in the packages/internal/analytics directory start the JS server
start:checkout-js-server
```

3. Open a browser and navigate to `http://127.0.0.1:8080/browser/checkout.js` to confirm the JS bundle is hosted correctly.

- Search for `"checkout-widgets","0.0.0"` in the JS bundle to confirm the version number is correct.
- It should be `0.0.0` because the version number for the SDK is not updated in the `package.json` file.

4. Start a web server to host the built JS bundle:

```bash
# in the packages/internal/analytics directory
start:checkout-web-server
```

5. Open a browser and navigate to `http://127.0.0.1:8081/` to confirm the test site is hosted correctly.

- You should see a title that says `Welcome to the Checkout Widgets Test Page` and the `Connect a wallet` widget loaded on the page.

6. Check the version check request was sent to the endpoint you configured in step 2.

## Cleanup

Remember to revert the changes to the `packages/internal/analytics/version-check/versionCheck.ts` file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Checkout Widgets</title>
<script defer src="http://127.0.0.1:8080/browser/checkout.js"></script>
</head>
<body>
<h1>Welcome to the Checkout Widgets Test Page</h1>
<imtbl-connect widgetConfig="{theme: 'dark', environment: 'sandbox'}"></imtbl-connect>
</body>
</html>
1 change: 1 addition & 0 deletions packages/internal/analytics/src/version-check/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { sdkVersionCheck, sdkVersion } from './versionCheck';
46 changes: 46 additions & 0 deletions packages/internal/analytics/src/version-check/localStorage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
deleteItem,
getItem,
setItem,
} from './localStorage';

beforeEach(() => {
global.localStorage.clear();
});

describe('getItem', () => {
test('it should not return a value that does not exist', () => {
const value = getItem('test');
expect(value).toBe(undefined);
});
test('it should return a string value when stored', () => {
global.localStorage.setItem('__IMX-test', 'some value');
expect(getItem('test')).toBe('some value');
});
});

describe('setItem', () => {
test('it should store items in a namespaced key', () => {
setItem('test', 1);
expect(global.localStorage.getItem('__IMX-test')).toBe('1');
});

test('it should serialise an object accurately when storing', () => {
const returnVal = setItem('test', { a: 1, b: 'hello' });
expect(global.localStorage.getItem('__IMX-test')).toBe(
JSON.stringify({ a: 1, b: 'hello' }),
);
expect(returnVal).toBe(true);
});
});

describe('deleteItem', () => {
test('should remove item that is stored', () => {
global.localStorage.setItem('__IMX-test', 'test');
deleteItem('test');
expect(global.localStorage.getItem('__IMX--test')).toBeNull();
});
test('should do nothing if key not stored', () => {
expect(() => global.localStorage.getItem('__IMX-random')).not.toThrow();
});
});
57 changes: 57 additions & 0 deletions packages/internal/analytics/src/version-check/localStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Abstraction on localstorage
*/

const localStoragePrefix = '__IMX-';

const hasLocalstorage = () => typeof window !== 'undefined' && window.localStorage;

const parseItem = (payload: string | null) => {
// Try to parse, if can't be parsed assume string
// and return string
if (!payload) return undefined;

try {
return JSON.parse(payload);
} catch (error) {
return payload;
}
};

const serialiseItem = (payload: any) => {
if (typeof payload === 'string') {
return payload;
}
return JSON.stringify(payload);
};

/**
* GenKey will take into account the namespace
* as well as if being run in the Link, it will tap into the link
* @param {string} key
* @returns key
*/
const genKey = (key: string) => `${localStoragePrefix}${key}`;

export const getItem = (key: string): any => {
if (hasLocalstorage()) {
return parseItem(window.localStorage.getItem(genKey(key)));
}
return undefined;
};

export const setItem = (key: string, payload: any): boolean => {
if (hasLocalstorage()) {
window.localStorage.setItem(genKey(key), serialiseItem(payload));
return true;
}
return false;
};

export const deleteItem = (key: string): boolean => {
if (hasLocalstorage()) {
window.localStorage.removeItem(genKey(key));
return true;
}
return false;
};
60 changes: 60 additions & 0 deletions packages/internal/analytics/src/version-check/versionCheck.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import axios from 'axios';
import { sdkVersionCheck } from './versionCheck';

jest.mock('axios');

describe('sdkVersionCheck', () => {
test('should now throw errors', () => {
expect(() => sdkVersionCheck('test-package', '1.0.0')).not.toThrow();
expect(() => sdkVersionCheck()).not.toThrow();
});

test('should send request to analytics API', () => {
const defaultApi = 'https://api.x.immutable.com';
const defaultVersionApi = '/v1/check';

sdkVersionCheck();

expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(expect.stringContaining(`${defaultApi}${defaultVersionApi}`));
});

test('should include SDK version', () => {
const defaultApi = 'https://api.x.immutable.com';
const defaultVersionApi = '/v1/check';
const sdkVersion = '__SDK_VERSION__';
const expectedUrl = `${defaultApi}${defaultVersionApi}`;
const expectedQueryParams = `?version=imtbl-sdk-${sdkVersion}`;

sdkVersionCheck();

expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(expect.stringContaining(`${expectedUrl}${expectedQueryParams}`));
});

test('should send details query parameter', () => {
sdkVersionCheck();

expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(expect.stringContaining('&details='));
});

test('should not send id query parameters if runtimeId is not set', () => {
sdkVersionCheck();

expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(expect.not.stringContaining('&id='));
});

test('should send app name and version number', () => {
const sdkVersion = '__SDK_VERSION__';
const packageName = 'test-package';
const packageVersion = '1.0.0';
const expectedQueryParams = `?version=imtbl-sdk-${sdkVersion},${packageName}-${packageVersion}`;

sdkVersionCheck(packageName, packageVersion);

expect(axios.get).toHaveBeenCalledTimes(1);
expect(axios.get).toHaveBeenCalledWith(expect.stringContaining(`${expectedQueryParams}`));
});
});
Loading

0 comments on commit 389c4de

Please sign in to comment.