@flowripple/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/.env.test +3 -0
- package/.env.test.example +3 -0
- package/.github/workflows/release.yml +71 -0
- package/README.md +99 -0
- package/dist/index.d.mts +50 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +110 -0
- package/dist/index.mjs +76 -0
- package/package.json +36 -0
- package/test/actual.test.ts +94 -0
- package/test/mock.test.ts +129 -0
- package/test/setup.ts +5 -0
@@ -0,0 +1,8 @@
|
|
1
|
+
# Changesets
|
2
|
+
|
3
|
+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
4
|
+
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
5
|
+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
6
|
+
|
7
|
+
We have a quick list of common questions to get you started engaging with this project in
|
8
|
+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json",
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
4
|
+
"commit": false,
|
5
|
+
"fixed": [],
|
6
|
+
"linked": [],
|
7
|
+
"access": "restricted",
|
8
|
+
"baseBranch": "main",
|
9
|
+
"updateInternalDependencies": "patch",
|
10
|
+
"ignore": []
|
11
|
+
}
|
package/.env.test
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
name: Release CI/CD
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
|
8
|
+
# Avoid overlapping runs on the same branch
|
9
|
+
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
10
|
+
|
11
|
+
permissions:
|
12
|
+
contents: write # to push changes (e.g., tags or changeset PR)
|
13
|
+
pull-requests: write # to create or update release PR
|
14
|
+
packages: write # to publish to npm
|
15
|
+
id-token: write # for provenance (optional, requires Node 16+)
|
16
|
+
|
17
|
+
jobs:
|
18
|
+
release:
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
steps:
|
21
|
+
- name: Checkout code
|
22
|
+
uses: actions/checkout@v3
|
23
|
+
|
24
|
+
- uses: pnpm/action-setup@v2 # Install pnpm first
|
25
|
+
with:
|
26
|
+
version: 9 # specify pnpm version (e.g., v8 or v9)
|
27
|
+
|
28
|
+
- name: Setup Node.js 20.x and pnpm
|
29
|
+
uses: actions/setup-node@v4
|
30
|
+
with:
|
31
|
+
node-version: 20.x
|
32
|
+
cache: "pnpm"
|
33
|
+
registry-url: "https://registry.npmjs.org" # use npm registry
|
34
|
+
|
35
|
+
- name: Install dependencies
|
36
|
+
run: pnpm install --frozen-lockfile
|
37
|
+
|
38
|
+
- name: Build package
|
39
|
+
run: pnpm build
|
40
|
+
|
41
|
+
- name: Validate environment
|
42
|
+
run: |
|
43
|
+
if [ -z "${{ secrets.NPM_TOKEN }}" ]; then
|
44
|
+
echo "NPM_TOKEN is not set"
|
45
|
+
exit 1
|
46
|
+
fi
|
47
|
+
if [ -z "${{ secrets.FLOWRIPPLE_API_KEY }}" ]; then
|
48
|
+
echo "FLOWRIPPLE_API_KEY is not set"
|
49
|
+
exit 1
|
50
|
+
fi
|
51
|
+
|
52
|
+
- name: Run tests
|
53
|
+
run: |
|
54
|
+
echo "FLOWRIPPLE_API_CLIENT_ID=${{ secrets.FLOWRIPPLE_API_CLIENT_ID }}" >> .env.test
|
55
|
+
echo "FLOWRIPPLE_API_KEY=${{ secrets.FLOWRIPPLE_API_KEY }}" >> .env.test
|
56
|
+
echo "FLOWRIPPLE_BASE_URL=${{ secrets.FLOWRIPPLE_BASE_URL }}" >> .env.test
|
57
|
+
pnpm test # Runs Jest tests
|
58
|
+
env:
|
59
|
+
FLOWRIPPLE_API_CLIENT_ID: ${{ secrets.FLOWRIPPLE_API_CLIENT_ID }}
|
60
|
+
FLOWRIPPLE_API_KEY: ${{ secrets.FLOWRIPPLE_API_KEY }}
|
61
|
+
FLOWRIPPLE_BASE_URL: ${{ secrets.FLOWRIPPLE_BASE_URL }}
|
62
|
+
|
63
|
+
|
64
|
+
- name: Create Release PR or Publish
|
65
|
+
id: changesets
|
66
|
+
uses: changesets/action@v1
|
67
|
+
with:
|
68
|
+
publish: pnpm run ci:publish # run publish script (defined in package.json)
|
69
|
+
env:
|
70
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
71
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# @flowripple/sdk
|
2
|
+
|
3
|
+
The official Node.js SDK for Flowripple - a powerful event tracking and analytics platform.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install the package using npm:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
npm install @flowripple/sdk
|
11
|
+
```
|
12
|
+
|
13
|
+
Or using yarn:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
yarn add @flowripple/sdk
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
First, import and initialize the FlowrippleClient with your API credentials:
|
22
|
+
|
23
|
+
```typescript
|
24
|
+
import { FlowrippleClient } from '@flowripple/sdk';
|
25
|
+
|
26
|
+
const client = new FlowrippleClient({
|
27
|
+
apiClientId: YOUR_CLIENT_ID,
|
28
|
+
apiKey: 'YOUR_API_KEY'
|
29
|
+
});
|
30
|
+
```
|
31
|
+
|
32
|
+
Then use the client to capture events:
|
33
|
+
|
34
|
+
```typescript
|
35
|
+
// Capture a simple event
|
36
|
+
await client.capture('user.signup', {
|
37
|
+
userId: '123',
|
38
|
+
email: 'user@example.com'
|
39
|
+
});
|
40
|
+
|
41
|
+
// Capture an event with custom properties
|
42
|
+
await client.capture('order.completed', {
|
43
|
+
orderId: 'ord_123',
|
44
|
+
amount: 99.99,
|
45
|
+
currency: 'USD',
|
46
|
+
items: [
|
47
|
+
{ id: 'prod_1', name: 'T-Shirt', quantity: 2 }
|
48
|
+
]
|
49
|
+
});
|
50
|
+
```
|
51
|
+
|
52
|
+
## API Reference
|
53
|
+
|
54
|
+
### FlowrippleClient
|
55
|
+
|
56
|
+
#### Constructor Options
|
57
|
+
|
58
|
+
The `FlowrippleClient` constructor accepts a configuration object with the following properties:
|
59
|
+
|
60
|
+
| Option | Type | Required | Default | Description |
|
61
|
+
|--------|------|----------|---------|-------------|
|
62
|
+
| `apiClientId` | number | Yes | - | Your Flowripple API Client ID |
|
63
|
+
| `apiKey` | string | Yes | - | Your Flowripple API Key |
|
64
|
+
| `baseUrl` | string | No | 'https://api.flowripple.com' | Custom API base URL |
|
65
|
+
| `silent` | boolean | No | false | If true, failed API calls return false instead of throwing errors |
|
66
|
+
| `version` | 'v1' | No | 'v1' | API version to use |
|
67
|
+
|
68
|
+
#### Methods
|
69
|
+
|
70
|
+
##### `capture(eventName: string, payload: object): Promise<false | void>`
|
71
|
+
|
72
|
+
Captures an event by sending it to the Flowripple API.
|
73
|
+
|
74
|
+
- `eventName`: The name of the event to capture
|
75
|
+
- `payload`: An object containing the event data
|
76
|
+
- Returns: A promise that resolves to `void` on success, or `false` if the request fails and silent mode is enabled
|
77
|
+
- Throws: An error if the request fails and silent mode is not enabled
|
78
|
+
|
79
|
+
## Error Handling
|
80
|
+
|
81
|
+
By default, the SDK will throw errors when API requests fail. You can enable silent mode to return `false` instead:
|
82
|
+
|
83
|
+
```typescript
|
84
|
+
const client = new FlowrippleClient({
|
85
|
+
apiClientId: YOUR_CLIENT_ID,
|
86
|
+
apiKey: 'YOUR_API_KEY',
|
87
|
+
silent: true
|
88
|
+
});
|
89
|
+
|
90
|
+
// This will return false instead of throwing if the request fails
|
91
|
+
const result = await client.capture('user.signup', { userId: '123' });
|
92
|
+
if (result === false) {
|
93
|
+
console.log('Event capture failed');
|
94
|
+
}
|
95
|
+
```
|
96
|
+
|
97
|
+
## License
|
98
|
+
|
99
|
+
MIT
|
package/dist/index.d.mts
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
/**
|
2
|
+
* Configuration options for initializing the FlowrippleClient
|
3
|
+
*/
|
4
|
+
interface FlowrippleClientOptions {
|
5
|
+
/** (Required) Flowripple API Client ID */
|
6
|
+
apiClientId: number;
|
7
|
+
/** (Required) API key for authentication with Flowripple */
|
8
|
+
apiKey: string;
|
9
|
+
/** (Optional) Base URL for the Flowripple API. Defaults to https://api.flowripple.com */
|
10
|
+
baseUrl?: string;
|
11
|
+
/** (Optional) If true, failed API calls will return false instead of throwing errors */
|
12
|
+
silent?: boolean;
|
13
|
+
/** (Optional) Version of the Flowripple API to use. Defaults to 'v1' */
|
14
|
+
version?: 'v1';
|
15
|
+
}
|
16
|
+
/**
|
17
|
+
* Client for interacting with the Flowripple API
|
18
|
+
*
|
19
|
+
* @example
|
20
|
+
* ```typescript
|
21
|
+
* const client = new FlowrippleClient({
|
22
|
+
* apiKey: 'your-api-key'
|
23
|
+
* });
|
24
|
+
*
|
25
|
+
* await client.capture('user.signup', {
|
26
|
+
* userId: '123',
|
27
|
+
* email: 'user@example.com'
|
28
|
+
* });
|
29
|
+
* ```
|
30
|
+
*/
|
31
|
+
declare class FlowrippleClient {
|
32
|
+
private readonly options;
|
33
|
+
private readonly baseUrl;
|
34
|
+
/**
|
35
|
+
* Creates a new FlowrippleClient instance
|
36
|
+
* @param options - Configuration options for the client
|
37
|
+
*/
|
38
|
+
constructor(options: FlowrippleClientOptions);
|
39
|
+
/**
|
40
|
+
* Captures an event by sending it to the Flowripple API
|
41
|
+
* @param eventName - The name of the event to capture
|
42
|
+
* @param payload - The payload data associated with the event
|
43
|
+
* @returns Promise that resolves to true if the event was successfully captured,
|
44
|
+
* or false if silent mode is enabled and the request failed
|
45
|
+
* @throws {Error} If the request fails and silent mode is not enabled
|
46
|
+
*/
|
47
|
+
capture(eventName: string, payload: object): Promise<false | void>;
|
48
|
+
}
|
49
|
+
|
50
|
+
export { FlowrippleClient };
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
/**
|
2
|
+
* Configuration options for initializing the FlowrippleClient
|
3
|
+
*/
|
4
|
+
interface FlowrippleClientOptions {
|
5
|
+
/** (Required) Flowripple API Client ID */
|
6
|
+
apiClientId: number;
|
7
|
+
/** (Required) API key for authentication with Flowripple */
|
8
|
+
apiKey: string;
|
9
|
+
/** (Optional) Base URL for the Flowripple API. Defaults to https://api.flowripple.com */
|
10
|
+
baseUrl?: string;
|
11
|
+
/** (Optional) If true, failed API calls will return false instead of throwing errors */
|
12
|
+
silent?: boolean;
|
13
|
+
/** (Optional) Version of the Flowripple API to use. Defaults to 'v1' */
|
14
|
+
version?: 'v1';
|
15
|
+
}
|
16
|
+
/**
|
17
|
+
* Client for interacting with the Flowripple API
|
18
|
+
*
|
19
|
+
* @example
|
20
|
+
* ```typescript
|
21
|
+
* const client = new FlowrippleClient({
|
22
|
+
* apiKey: 'your-api-key'
|
23
|
+
* });
|
24
|
+
*
|
25
|
+
* await client.capture('user.signup', {
|
26
|
+
* userId: '123',
|
27
|
+
* email: 'user@example.com'
|
28
|
+
* });
|
29
|
+
* ```
|
30
|
+
*/
|
31
|
+
declare class FlowrippleClient {
|
32
|
+
private readonly options;
|
33
|
+
private readonly baseUrl;
|
34
|
+
/**
|
35
|
+
* Creates a new FlowrippleClient instance
|
36
|
+
* @param options - Configuration options for the client
|
37
|
+
*/
|
38
|
+
constructor(options: FlowrippleClientOptions);
|
39
|
+
/**
|
40
|
+
* Captures an event by sending it to the Flowripple API
|
41
|
+
* @param eventName - The name of the event to capture
|
42
|
+
* @param payload - The payload data associated with the event
|
43
|
+
* @returns Promise that resolves to true if the event was successfully captured,
|
44
|
+
* or false if silent mode is enabled and the request failed
|
45
|
+
* @throws {Error} If the request fails and silent mode is not enabled
|
46
|
+
*/
|
47
|
+
capture(eventName: string, payload: object): Promise<false | void>;
|
48
|
+
}
|
49
|
+
|
50
|
+
export { FlowrippleClient };
|
package/dist/index.js
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __create = Object.create;
|
3
|
+
var __defProp = Object.defineProperty;
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
8
|
+
var __export = (target, all) => {
|
9
|
+
for (var name in all)
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
11
|
+
};
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
14
|
+
for (let key of __getOwnPropNames(from))
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
17
|
+
}
|
18
|
+
return to;
|
19
|
+
};
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
26
|
+
mod
|
27
|
+
));
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
29
|
+
var __async = (__this, __arguments, generator) => {
|
30
|
+
return new Promise((resolve, reject) => {
|
31
|
+
var fulfilled = (value) => {
|
32
|
+
try {
|
33
|
+
step(generator.next(value));
|
34
|
+
} catch (e) {
|
35
|
+
reject(e);
|
36
|
+
}
|
37
|
+
};
|
38
|
+
var rejected = (value) => {
|
39
|
+
try {
|
40
|
+
step(generator.throw(value));
|
41
|
+
} catch (e) {
|
42
|
+
reject(e);
|
43
|
+
}
|
44
|
+
};
|
45
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
46
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
47
|
+
});
|
48
|
+
};
|
49
|
+
|
50
|
+
// src/index.ts
|
51
|
+
var index_exports = {};
|
52
|
+
__export(index_exports, {
|
53
|
+
FlowrippleClient: () => FlowrippleClient
|
54
|
+
});
|
55
|
+
module.exports = __toCommonJS(index_exports);
|
56
|
+
var import_axios = __toESM(require("axios"));
|
57
|
+
var import_crypto = __toESM(require("crypto"));
|
58
|
+
var FlowrippleClient = class {
|
59
|
+
/**
|
60
|
+
* Creates a new FlowrippleClient instance
|
61
|
+
* @param options - Configuration options for the client
|
62
|
+
*/
|
63
|
+
constructor(options) {
|
64
|
+
this.options = options;
|
65
|
+
this.baseUrl = options.baseUrl || "https://api.flowripple.com";
|
66
|
+
}
|
67
|
+
/**
|
68
|
+
* Captures an event by sending it to the Flowripple API
|
69
|
+
* @param eventName - The name of the event to capture
|
70
|
+
* @param payload - The payload data associated with the event
|
71
|
+
* @returns Promise that resolves to true if the event was successfully captured,
|
72
|
+
* or false if silent mode is enabled and the request failed
|
73
|
+
* @throws {Error} If the request fails and silent mode is not enabled
|
74
|
+
*/
|
75
|
+
capture(eventName, payload) {
|
76
|
+
return __async(this, null, function* () {
|
77
|
+
var _a, _b;
|
78
|
+
try {
|
79
|
+
const body = {
|
80
|
+
event: eventName,
|
81
|
+
payload
|
82
|
+
};
|
83
|
+
const timestamp = Date.now().toString();
|
84
|
+
const stringToSign = timestamp + JSON.stringify(body);
|
85
|
+
const hmac = import_crypto.default.createHmac("sha256", this.options.apiKey).update(stringToSign).digest("hex");
|
86
|
+
const url = `${this.baseUrl.replace(/^\/+/, "")}/sdk/${(_a = this.options.version) != null ? _a : "v1"}/capture`;
|
87
|
+
yield import_axios.default.post(url, body, {
|
88
|
+
headers: {
|
89
|
+
"Content-Type": "application/json",
|
90
|
+
"X-Flowripple-Api-Client-Id": this.options.apiClientId,
|
91
|
+
"X-Flowripple-Signature": hmac,
|
92
|
+
"X-Flowripple-Timestamp": timestamp
|
93
|
+
}
|
94
|
+
});
|
95
|
+
return;
|
96
|
+
} catch (error) {
|
97
|
+
if (this.options.silent) {
|
98
|
+
return false;
|
99
|
+
}
|
100
|
+
throw new Error(
|
101
|
+
`Failed to capture event: ${(_b = error == null ? void 0 : error.message) != null ? _b : error}`
|
102
|
+
);
|
103
|
+
}
|
104
|
+
});
|
105
|
+
}
|
106
|
+
};
|
107
|
+
// Annotate the CommonJS export names for ESM import in node:
|
108
|
+
0 && (module.exports = {
|
109
|
+
FlowrippleClient
|
110
|
+
});
|
package/dist/index.mjs
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
var __async = (__this, __arguments, generator) => {
|
2
|
+
return new Promise((resolve, reject) => {
|
3
|
+
var fulfilled = (value) => {
|
4
|
+
try {
|
5
|
+
step(generator.next(value));
|
6
|
+
} catch (e) {
|
7
|
+
reject(e);
|
8
|
+
}
|
9
|
+
};
|
10
|
+
var rejected = (value) => {
|
11
|
+
try {
|
12
|
+
step(generator.throw(value));
|
13
|
+
} catch (e) {
|
14
|
+
reject(e);
|
15
|
+
}
|
16
|
+
};
|
17
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
18
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
19
|
+
});
|
20
|
+
};
|
21
|
+
|
22
|
+
// src/index.ts
|
23
|
+
import axios from "axios";
|
24
|
+
import crypto from "crypto";
|
25
|
+
var FlowrippleClient = class {
|
26
|
+
/**
|
27
|
+
* Creates a new FlowrippleClient instance
|
28
|
+
* @param options - Configuration options for the client
|
29
|
+
*/
|
30
|
+
constructor(options) {
|
31
|
+
this.options = options;
|
32
|
+
this.baseUrl = options.baseUrl || "https://api.flowripple.com";
|
33
|
+
}
|
34
|
+
/**
|
35
|
+
* Captures an event by sending it to the Flowripple API
|
36
|
+
* @param eventName - The name of the event to capture
|
37
|
+
* @param payload - The payload data associated with the event
|
38
|
+
* @returns Promise that resolves to true if the event was successfully captured,
|
39
|
+
* or false if silent mode is enabled and the request failed
|
40
|
+
* @throws {Error} If the request fails and silent mode is not enabled
|
41
|
+
*/
|
42
|
+
capture(eventName, payload) {
|
43
|
+
return __async(this, null, function* () {
|
44
|
+
var _a, _b;
|
45
|
+
try {
|
46
|
+
const body = {
|
47
|
+
event: eventName,
|
48
|
+
payload
|
49
|
+
};
|
50
|
+
const timestamp = Date.now().toString();
|
51
|
+
const stringToSign = timestamp + JSON.stringify(body);
|
52
|
+
const hmac = crypto.createHmac("sha256", this.options.apiKey).update(stringToSign).digest("hex");
|
53
|
+
const url = `${this.baseUrl.replace(/^\/+/, "")}/sdk/${(_a = this.options.version) != null ? _a : "v1"}/capture`;
|
54
|
+
yield axios.post(url, body, {
|
55
|
+
headers: {
|
56
|
+
"Content-Type": "application/json",
|
57
|
+
"X-Flowripple-Api-Client-Id": this.options.apiClientId,
|
58
|
+
"X-Flowripple-Signature": hmac,
|
59
|
+
"X-Flowripple-Timestamp": timestamp
|
60
|
+
}
|
61
|
+
});
|
62
|
+
return;
|
63
|
+
} catch (error) {
|
64
|
+
if (this.options.silent) {
|
65
|
+
return false;
|
66
|
+
}
|
67
|
+
throw new Error(
|
68
|
+
`Failed to capture event: ${(_b = error == null ? void 0 : error.message) != null ? _b : error}`
|
69
|
+
);
|
70
|
+
}
|
71
|
+
});
|
72
|
+
}
|
73
|
+
};
|
74
|
+
export {
|
75
|
+
FlowrippleClient
|
76
|
+
};
|
package/package.json
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
{
|
2
|
+
"name": "@flowripple/sdk",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"description": "FlowRipple SDK",
|
5
|
+
"repository": {
|
6
|
+
"type": "git",
|
7
|
+
"url": "https://github.com/FlowRipple/sdk.git"
|
8
|
+
},
|
9
|
+
"bugs": {
|
10
|
+
"url": "https://github.com/flowripple/sdk/issues"
|
11
|
+
},
|
12
|
+
"homepage": "https://github.com/flowripple/sdk#readme",
|
13
|
+
"license": "MIT",
|
14
|
+
"publishConfig": {
|
15
|
+
"access": "public"
|
16
|
+
},
|
17
|
+
"devDependencies": {
|
18
|
+
"@changesets/cli": "^2.27.12",
|
19
|
+
"@types/jest": "^29.5.2",
|
20
|
+
"@types/node": "^22.11.1",
|
21
|
+
"jest": "^29.7.0",
|
22
|
+
"pnpm": "^10.2.1",
|
23
|
+
"dotenv": "^16.4.5",
|
24
|
+
"ts-jest": "^29.1.0",
|
25
|
+
"tsup": "^8.3.5",
|
26
|
+
"typescript": "^5.6.3"
|
27
|
+
},
|
28
|
+
"dependencies": {
|
29
|
+
"axios": "^1.7.3"
|
30
|
+
},
|
31
|
+
"scripts": {
|
32
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
33
|
+
"test": "jest",
|
34
|
+
"ci:publish": "pnpm test && pnpm build && changeset publish --access public"
|
35
|
+
}
|
36
|
+
}
|
@@ -0,0 +1,94 @@
|
|
1
|
+
import { FlowrippleClient } from '../src/index';
|
2
|
+
|
3
|
+
const baseUrl = process.env.FLOWRIPPLE_BASE_URL || 'http://localhost:3000';
|
4
|
+
const validApiClientId = parseInt(process.env.FLOWRIPPLE_API_CLIENT_ID || '1');
|
5
|
+
const validApiKey = process.env.FLOWRIPPLE_API_KEY || 'test-api-key';
|
6
|
+
|
7
|
+
describe('FlowrippleClient', () => {
|
8
|
+
describe('constructor', () => {
|
9
|
+
it('should set default baseUrl if not provided', () => {
|
10
|
+
const client = new FlowrippleClient({
|
11
|
+
apiClientId: validApiClientId,
|
12
|
+
apiKey: validApiKey,
|
13
|
+
});
|
14
|
+
expect(client['baseUrl']).toBe('https://api.flowripple.com');
|
15
|
+
});
|
16
|
+
|
17
|
+
it('should use provided baseUrl', () => {
|
18
|
+
const client = new FlowrippleClient({
|
19
|
+
apiClientId: validApiClientId,
|
20
|
+
apiKey: validApiKey,
|
21
|
+
baseUrl: 'http://custom.url',
|
22
|
+
});
|
23
|
+
expect(client['baseUrl']).toBe('http://custom.url');
|
24
|
+
});
|
25
|
+
});
|
26
|
+
|
27
|
+
describe('capture method', () => {
|
28
|
+
describe('success cases', () => {
|
29
|
+
it('should successfully capture an event', async () => {
|
30
|
+
const client = new FlowrippleClient({
|
31
|
+
apiClientId: validApiClientId,
|
32
|
+
apiKey: validApiKey,
|
33
|
+
baseUrl,
|
34
|
+
});
|
35
|
+
|
36
|
+
const eventName = 'test.event';
|
37
|
+
const payload = { userId: '123', action: 'test' };
|
38
|
+
|
39
|
+
const result = await client.capture(eventName, payload);
|
40
|
+
expect(result).toBeUndefined();
|
41
|
+
});
|
42
|
+
|
43
|
+
it('should handle successful capture in silent mode', async () => {
|
44
|
+
const client = new FlowrippleClient({
|
45
|
+
apiClientId: validApiClientId,
|
46
|
+
apiKey: validApiKey,
|
47
|
+
baseUrl,
|
48
|
+
silent: true,
|
49
|
+
});
|
50
|
+
|
51
|
+
const result = await client.capture('test.event', { userId: '123' });
|
52
|
+
expect(result).toBeUndefined();
|
53
|
+
});
|
54
|
+
});
|
55
|
+
|
56
|
+
describe('error cases', () => {
|
57
|
+
it('should throw error with descriptive message in normal mode', async () => {
|
58
|
+
const client = new FlowrippleClient({
|
59
|
+
apiClientId: -1, // Invalid ID to force error
|
60
|
+
apiKey: validApiKey,
|
61
|
+
baseUrl,
|
62
|
+
});
|
63
|
+
|
64
|
+
await expect(client.capture('test.event', {})).rejects.toThrow(
|
65
|
+
'Failed to capture event',
|
66
|
+
);
|
67
|
+
});
|
68
|
+
|
69
|
+
it('should return false in silent mode on error', async () => {
|
70
|
+
const client = new FlowrippleClient({
|
71
|
+
apiClientId: -1, // Invalid ID to force error
|
72
|
+
apiKey: validApiKey,
|
73
|
+
baseUrl,
|
74
|
+
silent: true,
|
75
|
+
});
|
76
|
+
|
77
|
+
const result = await client.capture('test.event', {});
|
78
|
+
expect(result).toBe(false);
|
79
|
+
});
|
80
|
+
|
81
|
+
it('should handle invalid API responses', async () => {
|
82
|
+
const client = new FlowrippleClient({
|
83
|
+
apiClientId: validApiClientId,
|
84
|
+
apiKey: 'invalid-key', // Invalid key to force error
|
85
|
+
baseUrl,
|
86
|
+
});
|
87
|
+
|
88
|
+
await expect(client.capture('test.event', {})).rejects.toThrow(
|
89
|
+
'Failed to capture event',
|
90
|
+
);
|
91
|
+
});
|
92
|
+
});
|
93
|
+
});
|
94
|
+
});
|
@@ -0,0 +1,129 @@
|
|
1
|
+
import { FlowrippleClient } from '../src/index';
|
2
|
+
import axios from 'axios';
|
3
|
+
|
4
|
+
const baseUrl = process.env.FLOWRIPPLE_BASE_URL || 'http://localhost:3000';
|
5
|
+
const validApiClientId = parseInt(process.env.FLOWRIPPLE_API_CLIENT_ID || '1');
|
6
|
+
const validApiKey = process.env.FLOWRIPPLE_API_KEY || 'test-api-key';
|
7
|
+
|
8
|
+
jest.mock('axios');
|
9
|
+
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
10
|
+
|
11
|
+
describe('FlowrippleClient', () => {
|
12
|
+
beforeEach(() => {
|
13
|
+
jest.clearAllMocks();
|
14
|
+
});
|
15
|
+
|
16
|
+
describe('constructor', () => {
|
17
|
+
it('should set default baseUrl if not provided', () => {
|
18
|
+
const client = new FlowrippleClient({
|
19
|
+
apiClientId: validApiClientId,
|
20
|
+
apiKey: validApiKey,
|
21
|
+
});
|
22
|
+
expect(client['baseUrl']).toBe('https://api.flowripple.com');
|
23
|
+
});
|
24
|
+
|
25
|
+
it('should use provided baseUrl', () => {
|
26
|
+
const client = new FlowrippleClient({
|
27
|
+
apiClientId: validApiClientId,
|
28
|
+
apiKey: validApiKey,
|
29
|
+
baseUrl: 'http://custom.url',
|
30
|
+
});
|
31
|
+
expect(client['baseUrl']).toBe('http://custom.url');
|
32
|
+
});
|
33
|
+
});
|
34
|
+
|
35
|
+
describe('capture method', () => {
|
36
|
+
describe('success cases', () => {
|
37
|
+
beforeEach(() => {
|
38
|
+
mockedAxios.post.mockResolvedValue({ data: {} });
|
39
|
+
});
|
40
|
+
|
41
|
+
it('should send correct request payload and headers', async () => {
|
42
|
+
const client = new FlowrippleClient({
|
43
|
+
apiClientId: validApiClientId,
|
44
|
+
apiKey: validApiKey,
|
45
|
+
baseUrl,
|
46
|
+
});
|
47
|
+
|
48
|
+
const eventName = 'test.event';
|
49
|
+
const payload = { userId: '123', action: 'test' };
|
50
|
+
|
51
|
+
await client.capture(eventName, payload);
|
52
|
+
|
53
|
+
expect(mockedAxios.post).toHaveBeenCalledTimes(1);
|
54
|
+
const [url, body, config] = mockedAxios.post.mock.calls[0] ?? [];
|
55
|
+
|
56
|
+
expect(url).toBe(`${baseUrl}/sdk/v1/capture`);
|
57
|
+
expect(body).toEqual({ event: eventName, payload });
|
58
|
+
expect(config?.headers).toMatchObject({
|
59
|
+
'Content-Type': 'application/json',
|
60
|
+
'X-Flowripple-Api-Client-Id': validApiClientId,
|
61
|
+
'X-Flowripple-Signature': expect.any(String),
|
62
|
+
'X-Flowripple-Timestamp': expect.any(String),
|
63
|
+
});
|
64
|
+
});
|
65
|
+
|
66
|
+
it('should handle successful capture in silent mode', async () => {
|
67
|
+
const client = new FlowrippleClient({
|
68
|
+
apiClientId: validApiClientId,
|
69
|
+
apiKey: validApiKey,
|
70
|
+
baseUrl,
|
71
|
+
silent: true,
|
72
|
+
});
|
73
|
+
|
74
|
+
const result = await client.capture('test.event', { userId: '123' });
|
75
|
+
expect(result).toBeUndefined();
|
76
|
+
});
|
77
|
+
});
|
78
|
+
|
79
|
+
describe('error cases', () => {
|
80
|
+
beforeEach(() => {
|
81
|
+
mockedAxios.post.mockRejectedValue(new Error('Network error'));
|
82
|
+
});
|
83
|
+
|
84
|
+
it('should throw error with descriptive message in normal mode', async () => {
|
85
|
+
const client = new FlowrippleClient({
|
86
|
+
apiClientId: validApiClientId,
|
87
|
+
apiKey: validApiKey,
|
88
|
+
baseUrl,
|
89
|
+
});
|
90
|
+
|
91
|
+
await expect(client.capture('test.event', {})).rejects.toThrow(
|
92
|
+
'Failed to capture event: Network error',
|
93
|
+
);
|
94
|
+
});
|
95
|
+
|
96
|
+
it('should return false in silent mode on error', async () => {
|
97
|
+
const client = new FlowrippleClient({
|
98
|
+
apiClientId: validApiClientId,
|
99
|
+
apiKey: validApiKey,
|
100
|
+
baseUrl,
|
101
|
+
silent: true,
|
102
|
+
});
|
103
|
+
|
104
|
+
const result = await client.capture('test.event', {});
|
105
|
+
expect(result).toBe(false);
|
106
|
+
});
|
107
|
+
|
108
|
+
it('should handle malformed API responses', async () => {
|
109
|
+
mockedAxios.post.mockRejectedValue({
|
110
|
+
isAxiosError: true,
|
111
|
+
response: {
|
112
|
+
status: 400,
|
113
|
+
data: { message: 'Invalid payload' },
|
114
|
+
},
|
115
|
+
});
|
116
|
+
|
117
|
+
const client = new FlowrippleClient({
|
118
|
+
apiClientId: validApiClientId,
|
119
|
+
apiKey: validApiKey,
|
120
|
+
baseUrl,
|
121
|
+
});
|
122
|
+
|
123
|
+
await expect(client.capture('test.event', {})).rejects.toThrow(
|
124
|
+
'Failed to capture event',
|
125
|
+
);
|
126
|
+
});
|
127
|
+
});
|
128
|
+
});
|
129
|
+
});
|