Skip to content

Commit c6a08bc

Browse files
pichflwinsmith
andauthored
Standalone SDK: Package for Browser and Node.js (#23)
* Standalone SDK: Package for Browser and Node.js - BREAKING: Removed UMD compatibility - BREAKING: No longer works in browsers that do not support ESM. - BREAKING: Does not ingest signals stored on `window.td` - BREAKING: Changed default export to Class - BREAKING: Does not instantiate itself when used in browser context - BREAKING: Removed methods for updating AppID, user - Uses API v2 - Replace Jest with Ava * Add queue * Update README * Update minimum node version * Update store implementation & use receivedAt * Payload is optional * Linter fixes * Update Linter commands * Use “private“ methods * Prefer `crypto` import over `node:crypto` Reduces compatibility issues * Prefer "clientUser" over user * Update README, crypto Implementation, and jsdoc comments * Add built-in crypto digest to tests * Fix a bug where changing the salt would not invalidate the hash cache * Generate TypeScript Type Definitions on build --------- Co-authored-by: Daniel Jilg <daniel@telemetrydeck.com>
1 parent 8bfd433 commit c6a08bc

27 files changed

+2539
-3025
lines changed

.eslintrc.cjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = {
2+
env: {
3+
node: true,
4+
browser: true,
5+
},
6+
globals: {
7+
globalThis: true,
8+
},
9+
plugins: ['prettier', 'unicorn', 'ava'],
10+
extends: [
11+
'eslint:recommended',
12+
'plugin:prettier/recommended',
13+
'plugin:unicorn/recommended',
14+
'plugin:ava/recommended',
15+
],
16+
rules: {
17+
'unicorn/prefer-module': 'off',
18+
},
19+
};

.eslintrc.js

Lines changed: 0 additions & 31 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ jobs:
2121
version: ${{ env.PNPM_VERSION }}
2222
- uses: actions/setup-node@v2
2323
with:
24-
node-version: 16.x
24+
node-version: 18.x
2525
cache: pnpm
2626
- run: pnpm install
2727
- run: pnpm run lint
28-
28+
2929
test:
3030
name: Testing
3131
runs-on: ubuntu-latest
@@ -36,7 +36,7 @@ jobs:
3636
version: ${{ env.PNPM_VERSION }}
3737
- uses: actions/setup-node@v2
3838
with:
39-
node-version: 16.x
39+
node-version: 18.x
4040
cache: pnpm
4141
- run: pnpm install
4242
- run: pnpm run test

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ jobs:
1919
version: ${{ env.PNPM_VERSION }}
2020
- uses: actions/setup-node@v2
2121
with:
22-
node-version: 16.x
22+
node-version: 18.x
2323
cache: pnpm
2424
- run: pnpm install
2525
- run: npm publish
2626
env:
27-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
27+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

.prettierignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,8 @@
88
/coverage/
99
!.*
1010
.eslintcache
11+
12+
/package.json
13+
/pnpm-lock.yaml
14+
15+
/CHANGELOG.md

.release-it.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ module.exports = {
1616
npm: {
1717
publish: false,
1818
},
19-
};
19+
};

.vscode/launch.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
// Verwendet IntelliSense zum Ermitteln möglicher Attribute.
3+
// Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
4+
// Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Programm starten",
11+
"program": "${workspaceFolder}/node_modules/ava/entrypoints/cli.mjs",
12+
"args": ["--serial", "${file}"],
13+
"outputCapture": "std",
14+
"console": "integratedTerminal", // optional
15+
"skipFiles": ["<node_internals>/**/*.js"]
16+
}
17+
]
18+
}

README.md

Lines changed: 73 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,112 @@
11
# Telemetry Deck JavaScript SDK
22

3-
This package allows you to send signals to [TelemetryDeck](https://telemetrydeck.com) from your JavaScript code.
3+
This package allows you to send signals to [TelemetryDeck](https://telemetrydeck.com) from JavaScript code.
44

5-
It has no package dependencies and supports **modern evergreen browsers** which support [cryptography](https://caniuse.com/cryptography).
5+
TelemetryDeck allows you to capture and analyize users moving through your app and get help deciding how to grow, all without compromising privacy!
66

7-
Signals sent with this version of the SDK automatically send the following payload items:
7+
> [!NOTE]
8+
> If you want to use TelemetryDeck for your blog or static website, we recommend the [TelemetryDeck Web SDK](https://github.com/TelemetryDeck/WebSDK) instead of this JavaScript SDK.
89
9-
- `url`
10-
- `useragent`
11-
- `locale`
12-
- `platform`
10+
# Set Up
1311

14-
You can filter and show these values in the TelemetryDeck dashboard.
12+
The TelemetryDeck SDK has no dependencies and supports **modern evergreen browsers** and **modern versions of Node.js** with support for [cryptography](https://caniuse.com/cryptography).
1513

16-
Test Mode is currently not supported.
14+
## Set up in Browser Based Applications that use a bundler (React, Vue, Angular, Svelte, Ember, …)
1715

18-
## Usage
16+
### 1. Installing the package
1917

20-
### 📄 Usage via Script tag
18+
Please install the package using npm or the package manager of your choice
2119

22-
For websites and to try out the code quickly, you can use [UNPKG](https://unpkg.com), a free CDN which allows you to load files from any npm package.
20+
### 2. Initializing TelemetryDeck
2321

24-
Include the following snippet inside the `<head>` of your HTML page:
22+
Initialize the TelemetryDeck SDK with your app ID and your user's user identifer.
2523

26-
```html
27-
<script src="https://unpkg.com/@telemetrydeck/sdk/dist/telemetrydeck.min.js" defer></script>
24+
```javascript
25+
import TelemetryDeck from '@telemetrydeck/sdk';
26+
27+
const td = new TelemetryDeck({
28+
appID: '<YOUR_APP_ID>'
29+
user: '<YOUR_USER_IDENTIFIER>',
30+
});
2831
```
2932

30-
Then add a second script tag after it like this to send a signal once every time the page loads:
33+
Please replace `<YOUR_APP_ID>` with the app ID in TelemetryDeck ([Dashboard](https://dashboard.telemetrydeck.com) -> App -> Set Up App).
3134

32-
```html
33-
<script>
34-
window.td = window.td || [];
35-
td.push(['app', YOUR_APP_ID], ['user', USER_IDENTIFIER], ['signal']);
36-
</script>
37-
```
35+
You also need to identify your logged in user. Instead of `<YOUR_USER_IDENTIFIER>`, pass in any string that uniquely identifies your user, such as an email address. It will be cryptographically anonymized with a hash function.
3836

39-
Please replace `YOUR_APP_ID` with the app ID you received from TelemetryDeck, and `USER_IDENTIFIER` with a user identifier. If you have none, consider `anonymous`.
40-
41-
You can add as many signals as you need to track different interactions with your page. Once the page and script are fully loaded, signals will be sent immediately.
42-
43-
#### Alternative usage for more complex tracking needs
44-
45-
```html
46-
<script>
47-
// Required: queue setup
48-
td = window.td || [];
49-
// Required: Set your application id
50-
td.push(['app', YOUR_APP_ID]);
51-
// Required: Set a user idenfitier. `anonymous` is a recommended default
52-
td.push(['user', USER_IDENTIFIER ?? 'anonymous']);
53-
54-
// Custom payload sent with the signal
55-
td.push(['signal']);
56-
td.push([
57-
'signal',
58-
{
59-
route: 'some/page/path',
60-
},
61-
]);
62-
</script>
63-
```
37+
If can't specify a user identifer at initialization, you can set it later by setting `td.clientUser`.
6438

65-
### 📦 Advanced usage for applications that use a bundler (like Webpack, Rollup, …)
39+
Please note that `td.signal` is an async function that returns a promise.
6640

67-
After installing the package via NPM, use it like this:
41+
## Set up in Node.js Applications
6842

69-
```js
70-
import { TelemetryDeck } from '@telemetrydeck/sdk';
43+
### 1. Installing the package
7144

72-
const td = new TelemetryDeck({ app: YOUR_APP_ID, user: YOUR_USER_IDENTIFIER });
45+
Please install the package using npm or the package manager of your choice
7346

74-
// Basic signal
75-
td.signal();
47+
### 2. Initializing TelemetryDeck
7648

77-
// Adanced: Signal with custom payload
78-
td.signal({
79-
route: 'some/page/path',
80-
});
49+
Initialize the TelemetryDeck SDK with your app ID and your user's user identifer. Since `globalThis.crypto.subtle.digest` does not exist in Node.js, you need to pass in an alternative implementation provided by Node.js.
50+
51+
```javascript
52+
import TelemetryDeck from '@telemetrydeck/sdk';
53+
import crypto from 'crypto';
8154

55+
const td = new TelemetryDeck({
56+
appID: '<YOUR_APP_ID>'
57+
user: '<YOUR_USER_IDENTIFIER>',
58+
cryptoDigest: crypto.webcrypto.subtle.digest,
59+
});
8260
```
8361

84-
Please replace `YOUR_APP_ID` with the app ID you received from TelemetryDeck. If you have any string that identifies your user, such as an email address, use it as `YOUR_USER_IDENTIFIER` – it will be cryptographically anonymized with a hash function.
62+
Please replace `<YOUR_APP_ID>` with the app ID in TelemetryDeck ([Dashboard](https://dashboard.telemetrydeck.com) -> App -> Set Up App).
8563

86-
If you want to pass optional parameters to the signal being sent, add them to the optional payload object.
64+
You also need to identify your logged in user. Instead of `<YOUR_USER_IDENTIFIER>`, pass in any string that uniquely identifies your user, such as an email address. It will be cryptographically anonymized with a hash function.
8765

88-
You can also update your user identifier or queue events like this:
66+
If can't specify a user identifer at initialization, you can set it later by setting `td.clientUser`.
8967

90-
```js
91-
// Optional: Update app or user identifier
92-
td.app(YOUR_NEW_APP_ID);
93-
td.user(YOUR_NEW_USER_IDENTIFIER);
68+
Please note that `td.signal` is an async function that returns a promise.
9469

95-
// Optional: Process any events that have been qeued up
96-
// Queued signals do not contain a client side timestamp and will be timestamped
97-
// on the server at the time of arrival. Consider adding a timestamp value to
98-
// your payloads if you need to be able to correlate them.
99-
const queuedEvents = [
100-
['app', YOUR_APP_ID],
101-
['user', YOUR_USER_IDENTIFIER],
102-
['signal'],
103-
['signal', { route: 'some/page/path' }],
104-
];
105-
td.ingest(qeuedEvents);
106-
```
70+
> [!NOTE]
71+
> If you are using React Native, React Native does not support the `crypto` module, which is required for the SDK to work. We found [react-native-quick-crypto](https://github.com/margelo/react-native-quick-crypto) to be a suitable polyfill. Please note that this is not an officially supported solution.
10772
108-
## More Info
73+
## Advanced Initalization Options
10974

110-
### 📱 You need an App ID
75+
See the [source code](./src/telemetrydeck.js#L6-L17) for a full list of availble options acepted by the `TelemetryDeck` constructor.
11176

112-
Every application and website registered to TelemetryDeck has its own unique ID that we use to assign incoming signals to the correct app. To get started, create a new app in the TelemetryDeck UI and copy its ID.
77+
# Sending Signals
11378

114-
### 👤 Optional: User Identifiers
79+
Send a basic signal by calling `td.signal()` with a signal type:
11580

116-
TelemetryDeck can count users if you assign it a unique identifier for each user that doesn't change. This identifier can be any string that is unique to the user, such as their email address, or a randomly generated UUID.
81+
```javascript
82+
td.signal('<SIGNAL_TYPE>');
83+
```
84+
85+
Send a signal with a custom payload by passing an object as the second argument. The payload's values will be [converted to Strings](./src/tests/store.test.js.js#L278-L310), except for `floatValue`, which can be a Float.
86+
87+
```javascript
88+
td.signal('Volume.Set', {
89+
band: 'Spinal Tap',
90+
floatValue: 11.0,
91+
});
92+
```
93+
94+
# Advanced: Queueing Signals
11795

118-
Feel free to use personally identifiable information as the user identifier: We use a cryptographically secure double-hasing process on client and server to make sure the data that arrives at our servers is anonymized and can not be traced back to individual users via their identifiers. A user's identifier is hashed inside the library, and then salted+hashed again on arrival at the server. This way the data is anonymized as defined by the GDPR and you don't have to ask for user consent for procesing or storing this data.
96+
The `TelemetryDeck` class comes with a built-in queuing mechanism for storing signals until they are flushed in a single request. Queued signals are sent with `receivedAt` prefilled with the time they were queued.
11997

120-
### 🚛 Optional: Payload
98+
This uses an in-memory store by default. The store is not persisted between page reloads or app restarts. If you want to persist the store, you can pass a `store` object to the `TelemetryDeck` constructor. The store must implement the following interface:
99+
100+
```javascript
101+
export class Store {
102+
async push() // signal bodys are async and need to be awaited before stored
103+
clear() // called after flush
104+
values() // returns an array of resolved signal bodys in the order they were pushed
105+
}
106+
```
121107

122-
You can optionally attach an object with string values to the signal. This will allow you to filter and aggregate signal by these values in the dashboard.
108+
The default implementation can be found in `src/utils/store.js`.
123109

124-
### 📚 Full Docs
110+
---
125111

126-
Go to [telemetrydeck.com/docs](https://telemetrydeck.com/docs) to see all documentation articles
112+
[TelemetryDeck](https://telemetrydeck.com?source=github) helps you build better products with live usage data. Try it out for free.

ava.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default {
2+
files: ['tests/**/*.test.js'],
3+
};

jest.config.js

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)