@sesamy/sesamy-js 1.0.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/.env +2 -0
- package/.env.production +2 -0
- package/.github/workflows/release.yml +22 -0
- package/.nvmrc +1 -0
- package/README.md +34 -0
- package/article.html +13 -0
- package/index.html +23 -0
- package/package.json +63 -0
- package/public/sesamy.png +0 -0
- package/renovate.json +6 -0
- package/src/actions/index.ts +1 -0
- package/src/app.ts +91 -0
- package/src/constants.ts +2 -0
- package/src/events/ready.ts +11 -0
- package/src/javascript-api.ts +30 -0
- package/src/main.ts +29 -0
- package/src/services/analytics/element-tracker.ts +124 -0
- package/src/services/analytics/index.ts +214 -0
- package/src/services/analytics/listeners/index.ts +2 -0
- package/src/services/analytics/listeners/route.ts +8 -0
- package/src/services/analytics/listeners/scroll.ts +61 -0
- package/src/services/analytics/types/analytics/activity-utils/index.d.ts +52 -0
- package/src/services/analytics/types/analytics/router-utils/index.d.ts +7 -0
- package/src/services/analytics/types/analytics/scroll-utils/index.d.ts +4 -0
- package/src/services/analytics/types/track-event.ts +70 -0
- package/src/services/auth/index.ts +60 -0
- package/src/state.ts +3 -0
- package/src/style.css +97 -0
- package/src/types/Config.ts +11 -0
- package/src/types/Events.ts +3 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +23 -0
- package/vite.config.js +18 -0
package/.env
ADDED
package/.env.production
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
release:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v2
|
|
13
|
+
- uses: actions/setup-node@v2
|
|
14
|
+
with:
|
|
15
|
+
node-version: "18"
|
|
16
|
+
registry-url: "https://registry.npmjs.org"
|
|
17
|
+
- run: npm ci
|
|
18
|
+
- run: npm run build:prod
|
|
19
|
+
- run: npx semantic-release
|
|
20
|
+
env:
|
|
21
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
22
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20.6.1
|
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# sesamy-js
|
|
2
|
+
|
|
3
|
+
The Sesamy browser javascript API
|
|
4
|
+
|
|
5
|
+
The project handles analytics, authentication and communication with the Seasmy API
|
|
6
|
+
|
|
7
|
+
## Analytics
|
|
8
|
+
|
|
9
|
+
The analytics module is used to track events and page views. It is using the [GetAnalytics](https://getanalytics.io/) library to send events to the Sesamy interaction endpoint.
|
|
10
|
+
|
|
11
|
+
The following events are tracked:
|
|
12
|
+
|
|
13
|
+
- Page views, with events triggered on router updates
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
The script can either be initiated with a JSON object in a script tag or by calling the init method.
|
|
20
|
+
|
|
21
|
+
The script will look for a script tag with the id `sesamy-js`, and if it isn't found it will wait for a call to the init function before initializing.
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script type="application/json" id="sesamy-js">
|
|
25
|
+
{
|
|
26
|
+
"clientId": "demo"
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## TODO
|
|
32
|
+
|
|
33
|
+
- We should consider replacing the saturated library as it keeps the messages in memory. We should maybe replace it with a library that stores the messages in local storage in case the user is offline.
|
|
34
|
+
- Ensure that the client-id is fetched from the page or config
|
package/article.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/png" href="/sesamy.png" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Article</title>
|
|
8
|
+
<script type="module" src="/src/main.ts"></script>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<h1>Article</h1>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/index.html
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/png" href="/sesamy.png" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Sesamy JS</title>
|
|
8
|
+
<script type="application/json" id="sesamy-js">
|
|
9
|
+
{
|
|
10
|
+
"clientId": "demo",
|
|
11
|
+
"analytics": {
|
|
12
|
+
"enabled": true
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
</script>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="app"></div>
|
|
19
|
+
<sesamy-app></sesamy-app>
|
|
20
|
+
<script type="module" src="/src/main.ts"></script>
|
|
21
|
+
<script type="module" src="/src/app.ts"></script>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sesamy/sesamy-js",
|
|
3
|
+
"private": false,
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"version": "1.0.0",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/sesamy-js.es.js",
|
|
12
|
+
"require": "./dist/sesamy-js.umd.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"dev": "vite",
|
|
17
|
+
"build": "tsc && vite build",
|
|
18
|
+
"build:prod": "tsc && vite build --mode production",
|
|
19
|
+
"preview": "vite preview",
|
|
20
|
+
"type-check": "tsc"
|
|
21
|
+
},
|
|
22
|
+
"release": {
|
|
23
|
+
"branches": [
|
|
24
|
+
"main"
|
|
25
|
+
],
|
|
26
|
+
"plugins": [
|
|
27
|
+
"@semantic-release/commit-analyzer",
|
|
28
|
+
"@semantic-release/release-notes-generator",
|
|
29
|
+
"@semantic-release/changelog",
|
|
30
|
+
[
|
|
31
|
+
"@semantic-release/npm",
|
|
32
|
+
{
|
|
33
|
+
"npmPublish": true,
|
|
34
|
+
"tarballDir": "dist"
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"@semantic-release/github",
|
|
38
|
+
[
|
|
39
|
+
"@semantic-release/git",
|
|
40
|
+
{
|
|
41
|
+
"assets": [
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"package.json"
|
|
44
|
+
],
|
|
45
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"typescript": "^5.2.2",
|
|
52
|
+
"vite": "^5.0.8",
|
|
53
|
+
"vitest": "^1.1.3"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@analytics/activity-utils": "^0.1.16",
|
|
57
|
+
"@analytics/router-utils": "^0.1.1",
|
|
58
|
+
"@analytics/scroll-utils": "^0.1.22",
|
|
59
|
+
"analytics": "^0.8.9",
|
|
60
|
+
"saturated": "^1.0.0",
|
|
61
|
+
"@auth0/auth0-spa-js": "^2.1.3"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
Binary file
|
package/renovate.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Will be stuff in here later
|
package/src/app.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { User } from "@auth0/auth0-spa-js";
|
|
2
|
+
import "./style.css";
|
|
3
|
+
import sesamyLogo from "/sesamy.png";
|
|
4
|
+
import { Events } from "./types/Events";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This class is only used for rendering the UI and not shipped in the bundle
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class SesamyAppComponent extends HTMLElement {
|
|
11
|
+
private user: User | undefined;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async _handleProfile() {
|
|
18
|
+
this.user = await window.sesamy.getUser();
|
|
19
|
+
this.render();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async connectedCallback() {
|
|
23
|
+
this.setupEventListeners();
|
|
24
|
+
|
|
25
|
+
if (!window.sesamy) {
|
|
26
|
+
window.addEventListener(Events.READY, () => this._handleProfile());
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this._handleProfile();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
render() {
|
|
34
|
+
this.innerHTML = `
|
|
35
|
+
<div>
|
|
36
|
+
<a href="https://docs.sesamy.com" target="_blank">
|
|
37
|
+
<img src="${sesamyLogo}" class="logo" alt="Vite logo" />
|
|
38
|
+
</a>
|
|
39
|
+
<h1>Sesamy JS</h1>
|
|
40
|
+
<p class="read-the-docs">
|
|
41
|
+
Click on the Sesamy logo to learn more
|
|
42
|
+
</p>
|
|
43
|
+
${this.user ? this.renderUserProfile() : this.renderLoginButton()}
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private renderLoginButton(): string {
|
|
49
|
+
return '<button part="login-btn" class="login auth-visible" id="login-btn"> Login </button>';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private renderUserProfile(): string {
|
|
53
|
+
return `<div class="user" part="user">
|
|
54
|
+
<a href="/profile" part="user-link">
|
|
55
|
+
${
|
|
56
|
+
this.user?.picture
|
|
57
|
+
? ` <img
|
|
58
|
+
class="big"
|
|
59
|
+
part="user-img img-big"
|
|
60
|
+
src=${this.user.picture}
|
|
61
|
+
alt=${this.user.name || "User avatar"}
|
|
62
|
+
/>`
|
|
63
|
+
: `<div part="avatar avatar-big" class="avatar big"> Placeholder </div>`
|
|
64
|
+
}
|
|
65
|
+
<h2 class="user-name">${this.user?.name || "User Name"}</h2>
|
|
66
|
+
</a>
|
|
67
|
+
|
|
68
|
+
<div class="logout-wrap">
|
|
69
|
+
<a href="#" class="logout-btn" id="logout-btn"> Logout </a>
|
|
70
|
+
</div>
|
|
71
|
+
</div>`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private setupEventListeners() {
|
|
75
|
+
this.addEventListener("click", (event) => {
|
|
76
|
+
const clickedElement = event.target as Element;
|
|
77
|
+
const logoutButton = clickedElement.closest("#logout-btn");
|
|
78
|
+
const loginButton = clickedElement.closest("#login-btn");
|
|
79
|
+
|
|
80
|
+
if (logoutButton) {
|
|
81
|
+
window.sesamy.logout();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (loginButton) {
|
|
85
|
+
window.sesamy.loginWithRedirect();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
customElements.define("sesamy-app", SesamyAppComponent);
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loginWithRedirect,
|
|
3
|
+
getUser,
|
|
4
|
+
isAuthenticated,
|
|
5
|
+
logout,
|
|
6
|
+
} from "./services/auth";
|
|
7
|
+
|
|
8
|
+
interface SesamyWindow {
|
|
9
|
+
sesamy: {
|
|
10
|
+
getUser: typeof getUser;
|
|
11
|
+
isAuthenticated: typeof isAuthenticated;
|
|
12
|
+
loginWithRedirect: typeof loginWithRedirect;
|
|
13
|
+
logout: typeof logout;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Extend the global Window type with your SesamyWindow interface
|
|
18
|
+
declare global {
|
|
19
|
+
interface Window extends SesamyWindow {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerAPI(namespace = "sesamy") {
|
|
23
|
+
// This makes it possible to use a different namespace
|
|
24
|
+
(window as any)[namespace] = {
|
|
25
|
+
getUser,
|
|
26
|
+
isAuthenticated,
|
|
27
|
+
loginWithRedirect,
|
|
28
|
+
logout,
|
|
29
|
+
};
|
|
30
|
+
}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { dispatchReadyEvent } from "./events/ready";
|
|
2
|
+
import { registerAPI } from "./javascript-api";
|
|
3
|
+
import { Config } from "./types/Config";
|
|
4
|
+
import { init as initAuth } from "./services/auth";
|
|
5
|
+
import { initAnalytics } from "./services/analytics";
|
|
6
|
+
|
|
7
|
+
export async function init(config: Config) {
|
|
8
|
+
await initAuth(config);
|
|
9
|
+
|
|
10
|
+
initAnalytics({
|
|
11
|
+
clientId: config.clientId,
|
|
12
|
+
// The default client id can be overridden by the config
|
|
13
|
+
...config.analytics,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
registerAPI(config.namespace);
|
|
17
|
+
|
|
18
|
+
dispatchReadyEvent();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const configElement = document.getElementById("sesamy-js");
|
|
22
|
+
if (configElement?.textContent) {
|
|
23
|
+
try {
|
|
24
|
+
const config = JSON.parse(configElement.textContent);
|
|
25
|
+
init(config);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error("Failed to parse config", err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { onUserActivity } from "@analytics/activity-utils";
|
|
2
|
+
|
|
3
|
+
// IDLE_TIME is in ms
|
|
4
|
+
const timeout = 5000;
|
|
5
|
+
|
|
6
|
+
export interface ElementTrackerOptions {
|
|
7
|
+
element: HTMLElement;
|
|
8
|
+
viewCallback?: () => void;
|
|
9
|
+
activeDurationCallback?: (duration: number) => void;
|
|
10
|
+
idleDurationCallback?: (duration: number) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default class ElementTracker {
|
|
14
|
+
element: HTMLElement;
|
|
15
|
+
|
|
16
|
+
isInViewport = false;
|
|
17
|
+
|
|
18
|
+
isAwake = false;
|
|
19
|
+
|
|
20
|
+
isFlushing?: boolean;
|
|
21
|
+
|
|
22
|
+
observer: IntersectionObserver;
|
|
23
|
+
|
|
24
|
+
lastEventAt = Date.now();
|
|
25
|
+
|
|
26
|
+
registeredView = false;
|
|
27
|
+
|
|
28
|
+
viewCallback?: () => void;
|
|
29
|
+
|
|
30
|
+
activeDurationCallback?: (duration: number, flushing?: boolean) => void;
|
|
31
|
+
|
|
32
|
+
idleDurationCallback?: (duration: number, flushing?: boolean) => void;
|
|
33
|
+
|
|
34
|
+
constructor(options: ElementTrackerOptions) {
|
|
35
|
+
this.element = options.element;
|
|
36
|
+
// We NEED to have callbacks here rather than using events as events will be lost when the browser is unloading
|
|
37
|
+
this.viewCallback = options.viewCallback;
|
|
38
|
+
this.activeDurationCallback = options.activeDurationCallback;
|
|
39
|
+
this.idleDurationCallback = options.idleDurationCallback;
|
|
40
|
+
|
|
41
|
+
// Setup the interaction observer
|
|
42
|
+
this.observer = new IntersectionObserver(
|
|
43
|
+
(entries) => {
|
|
44
|
+
// I guess there will only be one entry? Verify?
|
|
45
|
+
entries.forEach((entry) => {
|
|
46
|
+
this.handleInViewPort(entry.isIntersecting);
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
threshold: 0,
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
this.observer.observe(this.element);
|
|
54
|
+
|
|
55
|
+
// Setup the active/idle
|
|
56
|
+
onUserActivity({
|
|
57
|
+
onIdle: (elapsedTime: number) => this.handleAwake(false, elapsedTime),
|
|
58
|
+
onWakeUp: (elapsedTime: number) => this.handleAwake(true, elapsedTime),
|
|
59
|
+
timeout,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Flush the awake timer in case the page is unloading
|
|
65
|
+
*/
|
|
66
|
+
flush() {
|
|
67
|
+
this.isFlushing = true;
|
|
68
|
+
this.handleAwake(
|
|
69
|
+
!this.isAwake,
|
|
70
|
+
Math.round((Date.now() - this.lastEventAt) / 1000),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
handleInViewPort(isInViewport: boolean) {
|
|
75
|
+
if (isInViewport) {
|
|
76
|
+
// A view envent only occurs if the element is active.
|
|
77
|
+
this.isAwake = true;
|
|
78
|
+
this.trackInViewport();
|
|
79
|
+
} else {
|
|
80
|
+
// An article outside the viewport is not active
|
|
81
|
+
this.handleAwake(
|
|
82
|
+
false,
|
|
83
|
+
Math.round((Date.now() - this.lastEventAt) / 1000),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this.isInViewport = isInViewport;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
handleAwake(isAwake: boolean, elapsedTime: number) {
|
|
91
|
+
// Store the current awake state
|
|
92
|
+
this.isAwake = isAwake;
|
|
93
|
+
this.lastEventAt = isAwake
|
|
94
|
+
? Date.now() - elapsedTime * timeout
|
|
95
|
+
: Date.now();
|
|
96
|
+
|
|
97
|
+
if (!this.isInViewport) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.trackAwake(isAwake, elapsedTime);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
trackAwake(awake: boolean, elapsedTime: number) {
|
|
104
|
+
if (!awake && this.activeDurationCallback) {
|
|
105
|
+
this.activeDurationCallback(elapsedTime, this.isFlushing);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (awake && this.idleDurationCallback) {
|
|
109
|
+
this.idleDurationCallback(elapsedTime, this.isFlushing);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
trackInViewport() {
|
|
114
|
+
if (this.registeredView) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.registeredView = true;
|
|
119
|
+
|
|
120
|
+
if (this.viewCallback) {
|
|
121
|
+
this.viewCallback();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import Analytics, { AnalyticsInstance } from "analytics";
|
|
2
|
+
import saturated from "saturated";
|
|
3
|
+
import { name, version } from "../../../package.json";
|
|
4
|
+
import { TrackEvent } from "./types/track-event";
|
|
5
|
+
import { AnalyticsConfig } from "../../types/Config";
|
|
6
|
+
import { routeChangeListener } from "./listeners";
|
|
7
|
+
import ElementTracker from "./element-tracker";
|
|
8
|
+
import { ANALYTICS_BASE_URL } from "../../constants";
|
|
9
|
+
|
|
10
|
+
type Analytic = {
|
|
11
|
+
anonymousId: string;
|
|
12
|
+
userId?: string;
|
|
13
|
+
properties: { [key: string]: string | number };
|
|
14
|
+
event: string;
|
|
15
|
+
type: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let flushing = false;
|
|
19
|
+
|
|
20
|
+
let _clientId: string;
|
|
21
|
+
let _enabled: boolean;
|
|
22
|
+
let _endpoint: string;
|
|
23
|
+
|
|
24
|
+
export interface AnalyticsEventWithType extends Analytic {
|
|
25
|
+
type: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function initAnalytics({
|
|
29
|
+
clientId,
|
|
30
|
+
enabled = true,
|
|
31
|
+
endpoint = ANALYTICS_BASE_URL,
|
|
32
|
+
}: AnalyticsConfig) {
|
|
33
|
+
_clientId = clientId;
|
|
34
|
+
_enabled = enabled;
|
|
35
|
+
_endpoint = endpoint;
|
|
36
|
+
|
|
37
|
+
if (!_enabled) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Register route change listener
|
|
42
|
+
routeChangeListener();
|
|
43
|
+
|
|
44
|
+
// Register body events
|
|
45
|
+
const bodyTracker = new ElementTracker({
|
|
46
|
+
element: document.body,
|
|
47
|
+
viewCallback: () => {
|
|
48
|
+
analytics.page();
|
|
49
|
+
},
|
|
50
|
+
activeDurationCallback: (duration: number, flushing?: boolean) => {
|
|
51
|
+
analytics.track("activeDuration", {
|
|
52
|
+
duration,
|
|
53
|
+
flushing,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
idleDurationCallback: (duration: number, flushing?: boolean) => {
|
|
57
|
+
analytics.track("idleDuration", {
|
|
58
|
+
duration,
|
|
59
|
+
flushing,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
addUnloadPageListener(document.body, () => {
|
|
65
|
+
bodyTracker.flush();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function createMessage(payload: AnalyticsEventWithType[]): string {
|
|
70
|
+
return JSON.stringify(
|
|
71
|
+
payload.map((item) => ({
|
|
72
|
+
...item,
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
originalTimestamp: new Date().toISOString(),
|
|
75
|
+
version,
|
|
76
|
+
event: item.event || "page",
|
|
77
|
+
context: {
|
|
78
|
+
page: {
|
|
79
|
+
url: window.location.hostname,
|
|
80
|
+
path: window.location.pathname,
|
|
81
|
+
title: document.title,
|
|
82
|
+
search: window.location.search,
|
|
83
|
+
referrer: document.referrer,
|
|
84
|
+
},
|
|
85
|
+
locale: navigator.language,
|
|
86
|
+
library: name,
|
|
87
|
+
userAgent: navigator.userAgent,
|
|
88
|
+
clientId: _clientId,
|
|
89
|
+
},
|
|
90
|
+
})),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const queue = saturated(
|
|
95
|
+
async (arr) => {
|
|
96
|
+
if (arr.length > 0) {
|
|
97
|
+
const payload = createMessage(arr);
|
|
98
|
+
|
|
99
|
+
if (flushing) {
|
|
100
|
+
navigator.sendBeacon(_endpoint, payload);
|
|
101
|
+
} else {
|
|
102
|
+
const response = await fetch(_endpoint, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
body: payload,
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": "text/plain",
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
// TODO: Retry, maybe store data in local storage
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
max: 10, // limit
|
|
118
|
+
interval: 3000, // 3s
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
function send(payload: AnalyticsEventWithType) {
|
|
123
|
+
if (payload.properties.flushing) {
|
|
124
|
+
const payloadWithoutFlushing = { ...payload };
|
|
125
|
+
delete payloadWithoutFlushing.properties.flushing;
|
|
126
|
+
|
|
127
|
+
navigator.sendBeacon(_endpoint, createMessage([payloadWithoutFlushing]));
|
|
128
|
+
} else {
|
|
129
|
+
queue.push(payload);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export const analytics: AnalyticsInstance = Analytics({
|
|
134
|
+
app: name,
|
|
135
|
+
version,
|
|
136
|
+
plugins: [
|
|
137
|
+
{
|
|
138
|
+
name: "custom-analytics-plugin",
|
|
139
|
+
page: ({ payload }: { payload: Analytic }) => {
|
|
140
|
+
const { properties, anonymousId, userId, event } = payload;
|
|
141
|
+
const analyticsEvent = {
|
|
142
|
+
anonymousId,
|
|
143
|
+
userId,
|
|
144
|
+
properties,
|
|
145
|
+
event,
|
|
146
|
+
type: "page",
|
|
147
|
+
};
|
|
148
|
+
// Send data to custom collection endpoint
|
|
149
|
+
send(analyticsEvent);
|
|
150
|
+
},
|
|
151
|
+
track: ({ payload }: { payload: Analytic }) => {
|
|
152
|
+
const { properties, anonymousId, userId, event } = payload;
|
|
153
|
+
const analyticsEvent = {
|
|
154
|
+
anonymousId,
|
|
155
|
+
userId,
|
|
156
|
+
properties,
|
|
157
|
+
event,
|
|
158
|
+
type: "track",
|
|
159
|
+
};
|
|
160
|
+
send(analyticsEvent);
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
export function getAnonymousUserId() {
|
|
167
|
+
return analytics.user().anonymousId;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
+
export function track(eventName: string, payload: any): void {
|
|
172
|
+
analytics.track(eventName, payload);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function trackEvent(event: TrackEvent): void {
|
|
176
|
+
analytics.track(event.name, event);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function flushQueue() {
|
|
180
|
+
flushing = true;
|
|
181
|
+
return queue.flush();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function enableInteractions(callback?: () => void) {
|
|
185
|
+
analytics.plugins.enable("custom-analytics-plugin", callback);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function disableInteractions(callback?: () => void) {
|
|
189
|
+
analytics.plugins.disable(
|
|
190
|
+
"custom-analytics-plugin",
|
|
191
|
+
callback || (() => true),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export type Action = () => void;
|
|
196
|
+
const unloadPageListeners = new Map<Element, Action>();
|
|
197
|
+
|
|
198
|
+
export function addUnloadPageListener(element: Element, action: Action) {
|
|
199
|
+
unloadPageListeners.set(element, action);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function removeUnloadPageListener(element: Element) {
|
|
203
|
+
unloadPageListeners.delete(element);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
window.addEventListener("beforeunload", () => {
|
|
207
|
+
// Flush the queues for all the registered element trackers
|
|
208
|
+
unloadPageListeners.forEach((action, element) => {
|
|
209
|
+
action.bind(element)();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Flush the outbound queue
|
|
213
|
+
flushQueue();
|
|
214
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { onScrollChange } from "@analytics/scroll-utils";
|
|
2
|
+
import { TrackEvents } from "../types/track-event";
|
|
3
|
+
import { trackEvent } from "..";
|
|
4
|
+
|
|
5
|
+
export type ScrollEvent = {
|
|
6
|
+
direction: string;
|
|
7
|
+
range: number[];
|
|
8
|
+
scrollMax: number;
|
|
9
|
+
scrollMin: number;
|
|
10
|
+
trigger: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function addScrollListener(itemSrc: string, publisherContentId: string) {
|
|
14
|
+
onScrollChange({
|
|
15
|
+
25: (scrollData: ScrollEvent) => {
|
|
16
|
+
trackEvent({
|
|
17
|
+
name: TrackEvents.Scroll,
|
|
18
|
+
scrollData: scrollData.direction,
|
|
19
|
+
percentage: scrollData.trigger,
|
|
20
|
+
itemSrc,
|
|
21
|
+
publisherContentId,
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
50: (scrollData: ScrollEvent) => {
|
|
25
|
+
trackEvent({
|
|
26
|
+
name: TrackEvents.Scroll,
|
|
27
|
+
scrollData: scrollData.direction,
|
|
28
|
+
percentage: scrollData.trigger,
|
|
29
|
+
itemSrc,
|
|
30
|
+
publisherContentId,
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
75: (scrollData: ScrollEvent) => {
|
|
34
|
+
trackEvent({
|
|
35
|
+
name: TrackEvents.Scroll,
|
|
36
|
+
scrollData: scrollData.direction,
|
|
37
|
+
percentage: scrollData.trigger,
|
|
38
|
+
itemSrc,
|
|
39
|
+
publisherContentId,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
90: (scrollData: ScrollEvent) => {
|
|
43
|
+
trackEvent({
|
|
44
|
+
name: TrackEvents.Scroll,
|
|
45
|
+
scrollData: scrollData.direction,
|
|
46
|
+
percentage: scrollData.trigger,
|
|
47
|
+
itemSrc,
|
|
48
|
+
publisherContentId,
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
100: (scrollData: ScrollEvent) => {
|
|
52
|
+
trackEvent({
|
|
53
|
+
name: TrackEvents.Scroll,
|
|
54
|
+
scrollData: scrollData.direction,
|
|
55
|
+
percentage: scrollData.trigger,
|
|
56
|
+
itemSrc,
|
|
57
|
+
publisherContentId,
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
interface OnDomActivityOptions {
|
|
2
|
+
throttle?: number;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface OnUserActivityOptions {
|
|
6
|
+
onIdle?: (elapsedTime: number, event?: any) => void;
|
|
7
|
+
onWakeUp?: (elapsedTime: number, event?: any) => void;
|
|
8
|
+
onHeartbeat?: (elapsedTime: number, event?: any) => void;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
throttle?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface UserActivityStatus {
|
|
14
|
+
disable: () => () => void;
|
|
15
|
+
getStatus: () => {
|
|
16
|
+
isIdle: boolean;
|
|
17
|
+
isDisabled: boolean;
|
|
18
|
+
active: number;
|
|
19
|
+
idle: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare module "@analytics/activity-utils" {
|
|
24
|
+
/**
|
|
25
|
+
* Function to handle user inactivity.
|
|
26
|
+
* @param onIdle Function to be called when the user is idle.
|
|
27
|
+
* @param opts Optional configuration object.
|
|
28
|
+
* @returns An object with methods to control and get the status of the listener.
|
|
29
|
+
*/
|
|
30
|
+
function onIdle(
|
|
31
|
+
onIdle: (elapsedTime: number, event?: any) => void,
|
|
32
|
+
opts?: OnUserActivityOptions,
|
|
33
|
+
): UserActivityStatus;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Function to handle user wake up from idle.
|
|
37
|
+
* @param onWakeUp Function to be called when the user wakes up from idle.
|
|
38
|
+
* @param opts Optional configuration object.
|
|
39
|
+
* @returns An object with methods to control and get the status of the listener.
|
|
40
|
+
*/
|
|
41
|
+
function onWakeUp(
|
|
42
|
+
onWakeUp: (elapsedTime: number, event?: any) => void,
|
|
43
|
+
opts?: OnUserActivityOptions,
|
|
44
|
+
): UserActivityStatus;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Function to handle user activity.
|
|
48
|
+
* @param options Configuration object for user activity.
|
|
49
|
+
* @returns An object with methods to control and get the status of the listener.
|
|
50
|
+
*/
|
|
51
|
+
function onUserActivity(options: OnUserActivityOptions): UserActivityStatus;
|
|
52
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export enum TrackEvents {
|
|
2
|
+
ViewArticle = 'viewArticle',
|
|
3
|
+
ActiveDuration = 'activeDuration',
|
|
4
|
+
IdleDuration = 'idleDuration',
|
|
5
|
+
Scroll = 'scroll',
|
|
6
|
+
ChangeView = 'changeView',
|
|
7
|
+
AddToCart = 'addToCart',
|
|
8
|
+
ViewArticleListing = 'viewArticleListing',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ViewArticleListingProps {
|
|
12
|
+
name: TrackEvents.ViewArticleListing;
|
|
13
|
+
publisherContentId?: string;
|
|
14
|
+
itemSrc: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface AddToCartEventProps {
|
|
18
|
+
name: TrackEvents.AddToCart;
|
|
19
|
+
publisherContentId?: string;
|
|
20
|
+
itemSrc: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ChangeViewEventProps {
|
|
24
|
+
name: TrackEvents.ChangeView;
|
|
25
|
+
view: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ScrollEventProps {
|
|
29
|
+
name: TrackEvents.Scroll;
|
|
30
|
+
publisherContentId?: string;
|
|
31
|
+
itemSrc: string;
|
|
32
|
+
scrollData: string;
|
|
33
|
+
percentage: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface IdleDurationEventProps {
|
|
37
|
+
name: TrackEvents.IdleDuration;
|
|
38
|
+
duration: number;
|
|
39
|
+
publisherContentId?: string;
|
|
40
|
+
itemSrc: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ActiveDurationEventProps {
|
|
44
|
+
name: TrackEvents.ActiveDuration;
|
|
45
|
+
duration: number;
|
|
46
|
+
publisherContentId?: string;
|
|
47
|
+
itemSrc: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ViewArticleEventProps {
|
|
51
|
+
name: TrackEvents.ViewArticle;
|
|
52
|
+
itemSrc: string;
|
|
53
|
+
publisherContentId?: string;
|
|
54
|
+
state: 'public' | 'locked' | 'unlocked' | 'logged-in';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface Flushing {
|
|
58
|
+
flushing?: boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type TrackEvent = Flushing &
|
|
62
|
+
(
|
|
63
|
+
| ViewArticleEventProps
|
|
64
|
+
| ActiveDurationEventProps
|
|
65
|
+
| IdleDurationEventProps
|
|
66
|
+
| ScrollEventProps
|
|
67
|
+
| ChangeViewEventProps
|
|
68
|
+
| AddToCartEventProps
|
|
69
|
+
| ViewArticleListingProps
|
|
70
|
+
);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Auth0Client, createAuth0Client } from "@auth0/auth0-spa-js";
|
|
2
|
+
import { BASE_URL_DOMAIN } from "../../constants";
|
|
3
|
+
|
|
4
|
+
let auth0Client: Auth0Client;
|
|
5
|
+
|
|
6
|
+
export async function init({ clientId }: { clientId: string }) {
|
|
7
|
+
|
|
8
|
+
auth0Client = await createAuth0Client({
|
|
9
|
+
domain: `token.${BASE_URL_DOMAIN}`,
|
|
10
|
+
clientId,
|
|
11
|
+
cacheLocation: 'localstorage'
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (window.location.search.includes("code=")) {
|
|
15
|
+
try {
|
|
16
|
+
await auth0Client.handleRedirectCallback();
|
|
17
|
+
window.history.replaceState({}, document.title, "/");
|
|
18
|
+
} catch (err) {
|
|
19
|
+
// Fail silently
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function isAuthenticated() {
|
|
25
|
+
if (!auth0Client) {
|
|
26
|
+
throw new Error("Auth0 client not initialized");
|
|
27
|
+
}
|
|
28
|
+
return auth0Client.isAuthenticated();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function getUser() {
|
|
32
|
+
if (!auth0Client) {
|
|
33
|
+
throw new Error("Auth0 client not initialized");
|
|
34
|
+
}
|
|
35
|
+
return auth0Client.getUser();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function loginWithRedirect() {
|
|
39
|
+
if (!auth0Client) {
|
|
40
|
+
throw new Error("Auth0 client not initialized");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return auth0Client.loginWithRedirect({
|
|
44
|
+
authorizationParams: {
|
|
45
|
+
redirect_uri: window.location.href,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function logout() {
|
|
51
|
+
if (!auth0Client) {
|
|
52
|
+
throw new Error("Auth0 client not initialized");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return auth0Client.logout({
|
|
56
|
+
logoutParams: {
|
|
57
|
+
returnTo: window.location.href,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
package/src/state.ts
ADDED
package/src/style.css
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
font-weight: 400;
|
|
5
|
+
|
|
6
|
+
color-scheme: light dark;
|
|
7
|
+
color: rgba(255, 255, 255, 0.87);
|
|
8
|
+
background-color: #242424;
|
|
9
|
+
|
|
10
|
+
font-synthesis: none;
|
|
11
|
+
text-rendering: optimizeLegibility;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
13
|
+
-moz-osx-font-smoothing: grayscale;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
a {
|
|
17
|
+
font-weight: 500;
|
|
18
|
+
color: #646cff;
|
|
19
|
+
text-decoration: inherit;
|
|
20
|
+
}
|
|
21
|
+
a:hover {
|
|
22
|
+
color: #535bf2;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
display: flex;
|
|
28
|
+
place-items: center;
|
|
29
|
+
min-width: 320px;
|
|
30
|
+
min-height: 100vh;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h1 {
|
|
34
|
+
font-size: 3.2em;
|
|
35
|
+
line-height: 1.1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
sesamy-app {
|
|
39
|
+
max-width: 1280px;
|
|
40
|
+
margin: 0 auto;
|
|
41
|
+
padding: 2rem;
|
|
42
|
+
text-align: center;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.logo {
|
|
46
|
+
height: 6em;
|
|
47
|
+
padding: 1.5em;
|
|
48
|
+
will-change: filter;
|
|
49
|
+
transition: filter 300ms;
|
|
50
|
+
}
|
|
51
|
+
.logo:hover {
|
|
52
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
53
|
+
}
|
|
54
|
+
.logo.vanilla:hover {
|
|
55
|
+
filter: drop-shadow(0 0 2em #3178c6aa);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.card {
|
|
59
|
+
padding: 2em;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.read-the-docs {
|
|
63
|
+
color: #888;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
button {
|
|
67
|
+
border-radius: 8px;
|
|
68
|
+
border: 1px solid transparent;
|
|
69
|
+
padding: 0.6em 1.2em;
|
|
70
|
+
font-size: 1em;
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
font-family: inherit;
|
|
73
|
+
background-color: #1a1a1a;
|
|
74
|
+
cursor: pointer;
|
|
75
|
+
transition: border-color 0.25s;
|
|
76
|
+
}
|
|
77
|
+
button:hover {
|
|
78
|
+
border-color: #646cff;
|
|
79
|
+
}
|
|
80
|
+
button:focus,
|
|
81
|
+
button:focus-visible {
|
|
82
|
+
outline: 4px auto -webkit-focus-ring-color;
|
|
83
|
+
}
|
|
84
|
+
.hidden { display: none; }
|
|
85
|
+
|
|
86
|
+
@media (prefers-color-scheme: light) {
|
|
87
|
+
:root {
|
|
88
|
+
color: #213547;
|
|
89
|
+
background-color: #ffffff;
|
|
90
|
+
}
|
|
91
|
+
a:hover {
|
|
92
|
+
color: #747bff;
|
|
93
|
+
}
|
|
94
|
+
button {
|
|
95
|
+
background-color: #f9f9f9;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
|
|
9
|
+
/* Bundler mode */
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"allowImportingTsExtensions": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
|
|
16
|
+
/* Linting */
|
|
17
|
+
"strict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src"]
|
|
23
|
+
}
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// vite.config.js
|
|
2
|
+
import { defineConfig } from "vite";
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
build: {
|
|
6
|
+
lib: {
|
|
7
|
+
entry: "src/main.ts",
|
|
8
|
+
name: "sesamy-js",
|
|
9
|
+
fileName: (format) => `sesamy-js.${format}.js`,
|
|
10
|
+
},
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
input: {
|
|
13
|
+
main: "index.html",
|
|
14
|
+
// article: "article.html",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|