Skip to content

Commit e80894f

Browse files
authored
Allow loading of script to be deferred (#2)
1 parent 5bb11a1 commit e80894f

File tree

9 files changed

+218
-124
lines changed

9 files changed

+218
-124
lines changed

.npmignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
.release-it.js
66
node_modules
77
RELEASE.md
8-
rollup.config.js
8+
rollup.config.js
9+
src

README.md

Lines changed: 79 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +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 your JavaScript code.
44

5-
It has no package dependencies and supports modern evergreen browsers which support [cryptography](https://caniuse.com/cryptography).
5+
It has no package dependencies and supports **modern evergreen browsers** which support [cryptography](https://caniuse.com/cryptography).
66

77
Signals sent with this version of the SDK automatically send the following payload items:
88

9-
- url
10-
- useragent
11-
- locale
12-
- platform
9+
- `url`
10+
- `useragent`
11+
- `locale`
12+
- `platform`
1313

1414
You can filter and show these values in the TelemetryDeck dashboard.
1515

1616
Test Mode is currently not supported.
1717

1818
## Usage
1919

20-
### 📄 Usage via Script Tag
20+
### 📄 Usage via Script tag
2121

22-
For regular 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.
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.
2323

24-
Include the following snippet in your page header:
24+
Include the following snippet inside the `<head>` of your HTML page:
2525

2626
```html
27-
<script src="https://unpkg.com/@telemtrydeck/sdk/dist/telemetrydeck.min.js">
27+
<script src="https://unpkg.com/@telemtrydeck/sdk/dist/telemetrydeck.min.js" defer></script>
2828
```
2929

30-
then include a script tag at the bottom of your page like this to send a signal once every time the page loads:
30+
Then add a second script tag after it like this to send a signal once every time the page loads:
3131

3232
```html
3333
<script>
34-
signal(
35-
// required options to identify your app and the user
36-
{
37-
appID: 'YOUR_APP_ID',
38-
userIdentifier: 'ANONYMOUS',
39-
},
40-
// optional: custom payload stored with the signal
41-
{
42-
route: 'some/page/path',
43-
}
44-
);
34+
window.td = window.td || [];
35+
td.push(['app', YOUR_APP_ID], ['user', USER_IDENTIFIER], ['signal']);
4536
</script>
4637
```
4738

48-
Please replace `YOUR_APP_ID` with the app ID you received from TelemetryDeck, and set a user identifier if possible.
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`.
4940

50-
### 📦 Usage for applications that use a bundler (like Webpack, Rollup, …)
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 immediatlty.
42+
43+
```js
44+
// basic signal
45+
td.push('signal');
46+
47+
// with custom data
48+
td.push('signal', { route: '/' });
49+
```
50+
51+
#### Alternative usage for more complex tracking needs
52+
53+
```html
54+
<script>
55+
// Required: queue setup
56+
td = window.td || [];
57+
// Required: Set your application id
58+
td.push(['app', YOUR_APP_ID]);
59+
// Required: Set a user idenfitier. `anonymous` is a recommended default
60+
td.push(['user', USER_IDENTIFIER ?? 'anonymous']);
61+
62+
// Custom payload sent with the signal
63+
td.push(['signal']);
64+
td.push([
65+
'signal',
66+
{
67+
route: 'some/page/path',
68+
},
69+
]);
70+
</script>
71+
```
72+
73+
### 📦 Advanced usage for applications that use a bundler (like Webpack, Rollup, …)
5174
5275
After installing the package via NPM, use it like this:
5376
5477
```js
55-
import { signal } from 'telemetry-deck';
56-
57-
//
58-
signal(
59-
// required options to identify your app and the user
60-
{
61-
appID: 'YOUR_APP_ID',
62-
userIdentifier: 'ANONYMOUS',
63-
},
64-
// custom payload stored with the signal
65-
{
66-
route: 'some/page/path',
67-
}
68-
);
78+
import { TelemetryDeck } from 'telemetry-deck';
79+
80+
const td = new TelemetryDeck({ app: YOUR_APP_ID, user: YOUR_USER_IDENTIFIER });
81+
82+
// Process any events that have been qeued up
83+
// Queued signals do not contain a client side timestamp and will be timestamped
84+
// on the server at the time of arrival. Consider adding a timestamp value to
85+
// your payloads if you need to be able to correlate them.
86+
const queuedEvents = [
87+
['app', YOUR_APP_ID],
88+
['user', YOUR_USER_IDENTIFIER],
89+
['signal'],
90+
['signal', { route: 'some/page/path' }],
91+
];
92+
td.ingest(qeuedEvents);
93+
94+
// Basic signal
95+
td.signal();
96+
97+
// Update app or user identifier
98+
td.app(YOUR_NEW_APP_ID);
99+
td.user(YOUR_NEW_USER_IDENTIFIER);
100+
101+
// Signal with custom payload
102+
td.signal({
103+
route: 'some/page/path',
104+
});
69105
```
70106
71-
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, pass it into `userIdentifier` – it will be cryptographically anonymized with a hash function.
107+
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.
72108
73-
If you want to pass optional parameters to the signal being sent, add them to the optional paylaod object.
109+
If you want to pass optional parameters to the signal being sent, add them to the optional payload object.
74110
75111
## More Info
76112
@@ -80,14 +116,14 @@ Every application and website registered to TelemetryDeck has its own unique ID
80116
81117
### 👤 Optional: User Identifiers
82118
83-
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.
119+
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.
84120
85121
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.
86122
87123
### 🚛 Optional: Payload
88124
89-
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.
125+
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.
90126
91127
### 📚 Full Docs
92128
93-
Go to [docs.telemetrydeck.com](https://docs.telemetrydeck.com) to see all documentation articles
129+
Go to [docs.telemetrydeck.com](https://docs.telemetrydeck.com) to see all documentation articles

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.0",
44
"description": "JavaScript package to send TelemetryDeck signals",
55
"main": "dist/telemetrydeck.js",
6-
"module": "src/telemetrydeck.mjs",
6+
"module": "dist/telemetrydeck.mjs",
77
"scripts": {
88
"build": "rollup -c",
99
"changelog": "lerna-changelog",

rollup.config.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import json from '@rollup/plugin-json';
22
import { terser } from 'rollup-plugin-terser';
33

44
export default [
5+
// CommonJS build for Node.js
56
{
67
input: 'src/telemetrydeck.mjs',
78
output: {
@@ -10,6 +11,25 @@ export default [
1011
},
1112
plugins: [json()],
1213
},
14+
// ES module build for browsers
15+
{
16+
input: 'src/telemetrydeck.mjs',
17+
output: {
18+
file: 'dist/telemtrydeck.mjs',
19+
format: 'module',
20+
},
21+
plugins: [json()],
22+
},
23+
// minified ES module build
24+
{
25+
input: 'src/telemetrydeck.mjs',
26+
output: {
27+
file: 'dist/telemtrydeck.min.mjs',
28+
format: 'module',
29+
},
30+
plugins: [json(), terser()],
31+
},
32+
// minified UMD build for most browsers
1333
{
1434
input: 'src/telemetrydeck.mjs',
1535
output: {

src/telemetrydeck.mjs

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,68 @@
11
import { version } from '../package.json';
2+
import sha256 from './utils/sha256.mjs';
3+
import assertKeyValue from './utils/assert-key-value.mjs';
4+
import transformPayload from './utils/transform-payload.mjs';
25

3-
const transformPayload = (payload) => Object.entries(payload).map((entry) => entry.join(':'));
6+
const APP = 'app';
7+
const USER = 'user';
8+
const SIGNAL = 'signal';
49

5-
const assertKeyValue = (key, value) => {
6-
if (!value) {
7-
throw new Error(`TelemetryDeck: ${key} is not set`);
8-
}
9-
};
10-
11-
// https://stackoverflow.com/a/48161723/54547
12-
async function sha256(message) {
13-
// encode as UTF-8
14-
const messageBuffer = new TextEncoder().encode(message);
10+
export class TelemetryDeck {
11+
constructor(options = {}) {
12+
const { target, app, user } = options;
1513

16-
// hash the message
17-
const hashBuffer = await crypto.subtle.digest('SHA-256', messageBuffer);
14+
this.target = target ?? 'https://nom.telemetrydeck.com/v1/';
15+
this._app = app;
16+
this._user = user;
17+
}
1818

19-
// convert ArrayBuffer to Array
20-
const hashArray = [...new Uint8Array(hashBuffer)];
19+
async ingest(queue) {
20+
for (const [method, data] of queue) {
21+
try {
22+
await this[method].call(this, data);
23+
} catch (error) {
24+
console.error(error);
25+
}
26+
}
27+
}
2128

22-
// convert bytes to hex string
23-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
24-
return hashHex;
25-
}
29+
[APP](appId) {
30+
this._app = appId;
31+
}
2632

27-
class TelemetryDeck {
28-
constructor(appID, target) {
29-
this.appID = appID;
30-
this.target = target ?? 'https://nom.telemetrydeck.com/v1/';
33+
[USER](identifier) {
34+
this._user = identifier;
35+
}
3136

32-
assertKeyValue('appID', appID);
37+
/**
38+
* This method is used to queue messages to be sent by TelemtryDeck
39+
* @param {string} type
40+
* @param {string} [payload]
41+
*
42+
* @returns {Promise<void>}
43+
*/
44+
push([method, data] = []) {
45+
return this[method](data);
3346
}
3447

3548
/**
3649
*
37-
* @paam {string} userIdentifier to be hashed
3850
* @param {Object?} payload custom payload to be stored with each signal
3951
* @returns <Promise<Response>> a promise with the response from the server, echoing the sent data
4052
*/
41-
async signal(userIdentifier, payload) {
53+
async [SIGNAL](payload = {}) {
4254
const { href: url } = location;
4355
const { userAgent: useragent, language: locale, userAgentData, vendor } = navigator;
56+
const { _app, target } = this;
57+
let { _user } = this;
58+
let { type } = payload;
4459

45-
payload = {
46-
url,
47-
useragent,
48-
locale,
49-
platform: userAgentData ?? '',
50-
vendor,
51-
...payload,
52-
};
53-
54-
let { appID, target } = this;
60+
delete payload.type;
5561

56-
assertKeyValue('userIdentifier', userIdentifier);
62+
assertKeyValue(APP, _app);
63+
assertKeyValue(USER, _user);
5764

58-
userIdentifier = await sha256(userIdentifier);
65+
_user = await sha256(_user);
5966

6067
return fetch(target, {
6168
method: 'POST',
@@ -65,16 +72,27 @@ class TelemetryDeck {
6572
},
6673
body: JSON.stringify([
6774
{
68-
appID,
69-
clientUser: userIdentifier,
70-
sessionID: userIdentifier,
75+
appID: _app,
76+
clientUser: _user,
77+
sessionID: _user,
7178
telemetryClientVersion: version,
72-
type: 'pageview',
73-
payload: transformPayload(payload),
79+
type: type ?? 'pageview',
80+
payload: transformPayload({
81+
url,
82+
useragent,
83+
locale,
84+
platform: userAgentData ?? '',
85+
vendor,
86+
...payload,
87+
}),
7488
},
7589
]),
7690
});
7791
}
7892
}
7993

80-
export default TelemetryDeck;
94+
if (window && window.td) {
95+
const td = new TelemetryDeck({});
96+
td.ingest(window.td);
97+
window.td = td;
98+
}

src/utils/assert-key-value.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const assertKeyValue = (key, value) => {
2+
if (!value) {
3+
throw new Error(`TelemetryDeck: "${key}" is not set`);
4+
}
5+
};
6+
7+
export default assertKeyValue;

src/utils/sha256.mjs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// https://stackoverflow.com/a/48161723/54547
2+
export default async function sha256(message) {
3+
// encode as UTF-8
4+
const messageBuffer = new TextEncoder().encode(message);
5+
6+
// hash the message
7+
const hashBuffer = await crypto.subtle.digest('SHA-256', messageBuffer);
8+
9+
// convert ArrayBuffer to Array
10+
const hashArray = [...new Uint8Array(hashBuffer)];
11+
12+
// convert bytes to hex string
13+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
14+
return hashHex;
15+
}

src/utils/transform-payload.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const transformPayload = (payload) => Object.entries(payload).map((entry) => entry.join(':'));
2+
3+
export default transformPayload;

0 commit comments

Comments
 (0)