@momentco-ai/moment-sdk 0.2.2-dev.17 → 0.3.0-dev.18
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/CHANGELOG.md +81 -0
- package/README.md +226 -118
- package/dist/moment-sdk.js +2 -2
- package/dist/moment-sdk.mjs +28 -19
- package/dist/types/sdk/moment-sdk.d.ts +2 -0
- package/dist/types/sdk/types.d.ts +7 -3
- package/examples/README.md +36 -0
- package/examples/brand-schedule/index.html +41 -0
- package/examples/brand-schedule/main.js +18 -0
- package/examples/collection/index.html +41 -0
- package/examples/collection/main.js +24 -0
- package/examples/index.html +38 -0
- package/examples/index.js +11 -0
- package/examples/moment/index.html +33 -0
- package/examples/moment/main.js +20 -0
- package/examples/serve.mjs +104 -0
- package/examples/shared/boot.js +166 -0
- package/examples/shared/demo.css +270 -0
- package/examples/shared/fixtures.js +24 -0
- package/package.json +9 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.3.0] - 2026-06-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `brand-playlist` trigger type — subscribe to a brand's default playlist via `data-moment-trigger-type="brand-playlist"` and `teamSlug` only. Live resolves `defaultPlaylistId` server-side.
|
|
13
|
+
- `playlist` value on `MomentSubscriptionType` for OAuth popup forwarding.
|
|
14
|
+
- **`moment-sdk-examples` CLI** — `npx moment-sdk-examples` serves interactive demos from the published npm package (port 5051)
|
|
15
|
+
- `examples/` shipped in the npm tarball (hub, moment, collection, brand-schedule pages wired to **sdk-demo** fixtures)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Brand-level trigger scoping now treats `brand-playlist` the same as `team`: moment, list, and ID attributes are ignored so the embed resolves the correct subscription resource.
|
|
20
|
+
- Examples use the Live URL baked into the installed SDK build (no dev/prod environment toggles in the UI)
|
|
21
|
+
- Replaced Vite-based examples dev server with a zero-dependency Node static server
|
|
22
|
+
|
|
23
|
+
## [0.2.2] - 2026-06-03
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Source-based moment resolution via `apiSourceId` / `externalEventId` on `MomentSdkOpenPayload`, `createMomentButton()`, and trigger data attributes `data-api-source-id` / `data-external-event-id`. When both are provided, Live resolves the moment server-side from the publisher's sync-subscription source instead of a slug.
|
|
28
|
+
|
|
29
|
+
## [0.2.1] - 2026-05-13
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- Default `.moment-sync-trigger` button styles injected on SDK init so bare HTML embed snippets render as a minimal button without partner CSS. Styles use `:where()` so partner classes can override them.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- `createMomentButton()` now uses the shared trigger stylesheet instead of inline styles.
|
|
38
|
+
|
|
39
|
+
## [0.2.0] - 2026-05-12
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- Modal UI refresh: rounded wrapper, responsive iframe sizing (auto-resize capped at 80vh), and a powered-by footer.
|
|
44
|
+
- `webcal://` link handling — the SDK opens Apple Calendar / ICS links in the parent window when requested by the embed iframe.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- **Breaking:** Renamed `triggerType` value `'schedule'` to `'team'` to align with backend `subscriptionType: 'team'` semantics for subscribing to all published moments for a brand/team. Update `data-moment-trigger-type` attributes and any explicit `triggerType: 'schedule'` passed to `open()` / `MomentSdkOpenPayload`.
|
|
49
|
+
- **Breaking:** Renamed the moment-slug embed attribute from `data-external-event-id` to `data-moment-slug`, and the corresponding payload field from `externalEventId` to `momentSlug` on `MomentSdkOpenPayload` and `createMomentButton()`. Update embed snippets and programmatic call sites accordingly.
|
|
50
|
+
|
|
51
|
+
> **Note:** `data-external-event-id` was reintroduced in 0.2.2 with a different meaning — it now pairs with `data-api-source-id` for source-based resolution, not as a moment slug alias.
|
|
52
|
+
|
|
53
|
+
## [0.1.2] - 2026-04-07
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Standardized embed popup bridge contracts on `triggerType` across SDK payloads and URL query params.
|
|
58
|
+
- Removed transitional `subscriptionType` fallback handling to align with the intentional `triggerType` protocol.
|
|
59
|
+
- Ensured modal iframe and OAuth popup URLs both pass `triggerType` consistently to Moment Live embed routes.
|
|
60
|
+
|
|
61
|
+
## [0.1.0] - 2026-04-01
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- Initial SDK release
|
|
66
|
+
- `MomentSdk` class with modal iframe + popup bridge OAuth flow
|
|
67
|
+
- `createMomentButton()` helper for programmatic button creation
|
|
68
|
+
- Data attribute-based trigger binding (`.moment-sync-trigger`)
|
|
69
|
+
- Support for `moment`, `list`, and `team` trigger types
|
|
70
|
+
- Google Calendar and Outlook Calendar provider support
|
|
71
|
+
- `onSuccess`, `onError`, `onClose`, `onAnalytics` callbacks
|
|
72
|
+
- Programmatic `open()`, `close()`, `rebind()`, `destroy()` API
|
|
73
|
+
- UMD + ESM builds with TypeScript declarations
|
|
74
|
+
- Build-time environment configuration via `VITE_LIVE_BASE_URL`
|
|
75
|
+
|
|
76
|
+
[0.3.0]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.2...v0.3.0
|
|
77
|
+
[0.2.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.1...v0.2.2
|
|
78
|
+
[0.2.1]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.0...v0.2.1
|
|
79
|
+
[0.2.0]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.2...v0.2.0
|
|
80
|
+
[0.1.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.0...v0.1.2
|
|
81
|
+
[0.1.0]: https://github.com/Moment-Co/moment-sdk/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -1,42 +1,79 @@
|
|
|
1
1
|
# Moment SDK
|
|
2
2
|
|
|
3
|
-
Embeddable calendar sync widget for
|
|
3
|
+
Embeddable calendar sync widget for partner websites. Fans click a trigger on your site, pick Google Calendar or Outlook, and subscribe to games, lists, or full team schedules — without leaving your page.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Zero runtime dependencies. Works via a single `<script>` tag or npm import.
|
|
6
|
+
|
|
7
|
+
[License: MIT](LICENSE)
|
|
8
|
+
[npm version](https://www.npmjs.com/package/@momentco-ai/moment-sdk)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What ships on npm
|
|
13
|
+
|
|
14
|
+
| Path | Format | Use case |
|
|
15
|
+
| --------------------- | ----------------------- | --------------------------------------------------------------------- |
|
|
16
|
+
| `dist/moment-sdk.js` | IIFE (UMD-style) | `<script>` tag — exposes `MomentSdk` and `createMomentButton` globals |
|
|
17
|
+
| `dist/moment-sdk.mjs` | ESM | Bundler / `import` |
|
|
18
|
+
| `dist/types/` | TypeScript declarations | Type-safe integrations |
|
|
19
|
+
| `examples/` | Static HTML + CLI | Interactive demos (`npx moment-sdk-examples`) |
|
|
20
|
+
|
|
21
|
+
Pin a version in production (see [Installation](#installation)).
|
|
6
22
|
|
|
7
23
|
---
|
|
8
24
|
|
|
9
25
|
## Installation
|
|
10
26
|
|
|
11
|
-
|
|
27
|
+
### Script tag (recommended for partner sites)
|
|
28
|
+
|
|
29
|
+
Load from a public CDN and **pin the version**:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<script src="https://cdn.jsdelivr.net/npm/@momentco-ai/moment-sdk@0.3.0"></script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Alternatives:
|
|
12
36
|
|
|
13
37
|
```html
|
|
14
|
-
|
|
38
|
+
<!-- unpkg -->
|
|
39
|
+
<script src="https://unpkg.com/@momentco-ai/moment-sdk@0.3.0"></script>
|
|
15
40
|
```
|
|
16
41
|
|
|
17
|
-
|
|
42
|
+
### npm / bundler
|
|
18
43
|
|
|
19
44
|
```bash
|
|
20
45
|
npm install @momentco-ai/moment-sdk
|
|
46
|
+
# or
|
|
47
|
+
pnpm add @momentco-ai/moment-sdk
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { MomentSdk, createMomentButton } from '@momentco-ai/moment-sdk';
|
|
52
|
+
|
|
53
|
+
const sdk = new MomentSdk({
|
|
54
|
+
onSuccess: (result) => console.log('Synced!', result),
|
|
55
|
+
});
|
|
21
56
|
```
|
|
22
57
|
|
|
23
58
|
---
|
|
24
59
|
|
|
25
|
-
## Quick
|
|
60
|
+
## Quick start
|
|
61
|
+
|
|
62
|
+
Copy this into any page. Replace `your-team-slug` and `your-event-slug` with values provided by Moment.
|
|
26
63
|
|
|
27
64
|
```html
|
|
28
|
-
<!-- 1.
|
|
65
|
+
<!-- 1. Trigger button -->
|
|
29
66
|
<button
|
|
30
67
|
class="moment-sync-trigger"
|
|
31
68
|
data-moment-team-slug="your-team-slug"
|
|
32
|
-
data-moment-slug="event-
|
|
69
|
+
data-moment-slug="your-event-slug"
|
|
33
70
|
data-moment-trigger-type="moment"
|
|
34
71
|
>
|
|
35
72
|
Add to Calendar
|
|
36
73
|
</button>
|
|
37
74
|
|
|
38
|
-
<!-- 2. Load
|
|
39
|
-
<script src="https://
|
|
75
|
+
<!-- 2. Load SDK (pin version in production) -->
|
|
76
|
+
<script src="https://cdn.jsdelivr.net/npm/@momentco-ai/moment-sdk@0.3.0"></script>
|
|
40
77
|
<script>
|
|
41
78
|
const sdk = new MomentSdk({
|
|
42
79
|
onSuccess: (result) => console.log('Synced!', result),
|
|
@@ -46,162 +83,162 @@ npm install @momentco-ai/moment-sdk
|
|
|
46
83
|
</script>
|
|
47
84
|
```
|
|
48
85
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
See [`examples/basic/`](./examples/basic/) for a complete working example.
|
|
86
|
+
Clicking the button opens a modal where the fan selects Google Calendar or Outlook and completes OAuth in a popup. Your callbacks fire when sync succeeds, fails, or the modal closes.
|
|
52
87
|
|
|
53
88
|
---
|
|
54
89
|
|
|
55
|
-
##
|
|
90
|
+
## Trigger types
|
|
91
|
+
|
|
92
|
+
Set `data-moment-trigger-type` (or `triggerType` in `open()` / `createMomentButton()`):
|
|
93
|
+
|
|
94
|
+
| Type | Required attributes | Subscribes to |
|
|
95
|
+
| ------------------ | ------------------------------------------------- | ------------------------------------------------------ |
|
|
96
|
+
| `moment` (default) | `data-moment-team-slug` + `data-moment-slug` | One or more specific games/events |
|
|
97
|
+
| `list` | `data-moment-team-slug` + `data-moment-list-slug` | A curated list of moments |
|
|
98
|
+
| `team` | `data-moment-team-slug` | All published moments for the brand/team |
|
|
99
|
+
| `brand-playlist` | `data-moment-team-slug` | The brand's default playlist (dynamic schedule thread) |
|
|
56
100
|
|
|
57
|
-
|
|
58
|
-
| ----------------- | ------------------- | -------- | ------------------------------------------------------------------ |
|
|
59
|
-
| `triggerSelector` | `string` | No | CSS selector for trigger elements. Default: `.moment-sync-trigger` |
|
|
60
|
-
| `onSuccess` | `(payload) => void` | No | Called on successful calendar sync |
|
|
61
|
-
| `onError` | `(payload) => void` | No | Called on sync failure |
|
|
62
|
-
| `onClose` | `(payload) => void` | No | Called when modal closes (success, error, or cancel) |
|
|
63
|
-
| `onAnalytics` | `(event) => void` | No | Called for every analytics event |
|
|
101
|
+
For `team` and `brand-playlist`, omit moment/list/ID and source-resolution attributes — only `data-moment-team-slug` is required.
|
|
64
102
|
|
|
65
103
|
---
|
|
66
104
|
|
|
67
|
-
## Data
|
|
105
|
+
## Data attributes
|
|
68
106
|
|
|
69
107
|
Place these on any element matching the trigger selector (default: `.moment-sync-trigger`):
|
|
70
108
|
|
|
71
|
-
| Attribute | Required
|
|
72
|
-
| -------------------------- |
|
|
73
|
-
| `data-moment-team-slug` |
|
|
74
|
-
| `data-moment-slug` |
|
|
75
|
-
| `data-moment-list-slug` |
|
|
76
|
-
| `data-moment-trigger-type` | No
|
|
77
|
-
| `data-moment-ids` | No
|
|
78
|
-
| `data-moment-game-id` | No
|
|
79
|
-
| `data-moment-calendar` | No
|
|
109
|
+
| Attribute | Required | Description |
|
|
110
|
+
| -------------------------- | ------------ | ----------------------------------------------------------------- |
|
|
111
|
+
| `data-moment-team-slug` | Yes | Team/brand slug provided by Moment |
|
|
112
|
+
| `data-moment-slug` | For `moment` | Brand-level moment slug (resolved server-side) |
|
|
113
|
+
| `data-moment-list-slug` | For `list` | List slug |
|
|
114
|
+
| `data-moment-trigger-type` | No | `moment` (default), `list`, `team`, or `brand-playlist` |
|
|
115
|
+
| `data-moment-ids` | No | Comma-separated moment IDs (fallback if slugs don't resolve) |
|
|
116
|
+
| `data-moment-game-id` | No | Alias for `data-moment-ids` |
|
|
117
|
+
| `data-moment-calendar` | No | Pre-select provider: `google` or `outlook` |
|
|
118
|
+
| `data-api-source-id` | No | Sync-subscription source ID (pairs with `data-external-event-id`) |
|
|
119
|
+
| `data-external-event-id` | No | External event ID from your feed, resolved against the source ID |
|
|
120
|
+
|
|
121
|
+
When both `data-api-source-id` and `data-external-event-id` are set, Live resolves the moment server-side — useful when your CMS emits native event IDs instead of Moment slugs.
|
|
80
122
|
|
|
81
123
|
---
|
|
82
124
|
|
|
83
|
-
##
|
|
125
|
+
## Init options
|
|
84
126
|
|
|
85
|
-
|
|
127
|
+
| Option | Type | Default | Description |
|
|
128
|
+
| ----------------- | ------------------- | ---------------------- | ---------------------------------------------------- |
|
|
129
|
+
| `triggerSelector` | `string` | `.moment-sync-trigger` | CSS selector for trigger elements |
|
|
130
|
+
| `onSuccess` | `(payload) => void` | — | Called on successful calendar sync |
|
|
131
|
+
| `onError` | `(payload) => void` | — | Called on sync failure |
|
|
132
|
+
| `onClose` | `(payload) => void` | — | Called when modal closes (success, error, or cancel) |
|
|
133
|
+
| `onAnalytics` | `(event) => void` | — | Called for every analytics event |
|
|
86
134
|
|
|
87
|
-
|
|
88
|
-
<script>
|
|
89
|
-
const sdk = new MomentSdk({
|
|
90
|
-
/* callbacks */
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Open modal
|
|
94
|
-
sdk.open({
|
|
95
|
-
teamSlug: 'your-team-slug',
|
|
96
|
-
momentSlug: 'event-123',
|
|
97
|
-
triggerType: 'moment',
|
|
98
|
-
ids: ['moment-id-1'],
|
|
99
|
-
calendar: 'google', // optional: skip provider selection
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Close modal
|
|
103
|
-
sdk.close();
|
|
135
|
+
---
|
|
104
136
|
|
|
105
|
-
|
|
106
|
-
sdk.rebind();
|
|
137
|
+
## Programmatic API
|
|
107
138
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
139
|
+
Open the modal without data attributes:
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
const sdk = new MomentSdk({
|
|
143
|
+
/* callbacks */
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Single moment
|
|
147
|
+
sdk.open({
|
|
148
|
+
teamSlug: 'your-team-slug',
|
|
149
|
+
momentSlug: 'your-event-slug',
|
|
150
|
+
triggerType: 'moment',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Source-based resolution (CMS event ID)
|
|
154
|
+
sdk.open({
|
|
155
|
+
teamSlug: 'your-team-slug',
|
|
156
|
+
triggerType: 'moment',
|
|
157
|
+
apiSourceId: 'your-sync-source-id',
|
|
158
|
+
externalEventId: 'native-event-12345',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Follow full team schedule
|
|
162
|
+
sdk.open({
|
|
163
|
+
teamSlug: 'your-team-slug',
|
|
164
|
+
triggerType: 'team',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Follow default playlist
|
|
168
|
+
sdk.open({
|
|
169
|
+
teamSlug: 'your-team-slug',
|
|
170
|
+
triggerType: 'brand-playlist',
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
sdk.close(); // Close modal
|
|
174
|
+
sdk.rebind(); // Re-bind triggers after dynamic DOM updates
|
|
175
|
+
sdk.destroy(); // Tear down instance
|
|
111
176
|
```
|
|
112
177
|
|
|
113
178
|
---
|
|
114
179
|
|
|
115
180
|
## `createMomentButton()`
|
|
116
181
|
|
|
117
|
-
|
|
182
|
+
Build a trigger button in JavaScript:
|
|
118
183
|
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
184
|
+
```javascript
|
|
185
|
+
const btn = createMomentButton({
|
|
186
|
+
teamSlug: 'your-team-slug',
|
|
187
|
+
momentSlug: 'your-event-slug',
|
|
188
|
+
triggerType: 'moment',
|
|
189
|
+
label: 'Add to Calendar',
|
|
190
|
+
className: 'my-calendar-btn', // optional — partner CSS
|
|
191
|
+
});
|
|
127
192
|
|
|
128
|
-
|
|
129
|
-
</script>
|
|
193
|
+
document.getElementById('container').appendChild(btn);
|
|
130
194
|
```
|
|
131
195
|
|
|
132
|
-
The button
|
|
196
|
+
The button gets the `.moment-sync-trigger` class and the correct `data-moment-*` attributes. Style it with your own CSS or rely on the SDK's minimal default button styles.
|
|
133
197
|
|
|
134
|
-
### Styling
|
|
198
|
+
### Styling
|
|
135
199
|
|
|
136
|
-
|
|
200
|
+
- **Trigger buttons** — fully themeable on your page. The SDK injects minimal defaults via `:where(.moment-sync-trigger)` so your classes win.
|
|
201
|
+
- **Modal UI** — hosted in a secure iframe on Moment Live; not styled by your page CSS.
|
|
137
202
|
|
|
138
203
|
```html
|
|
139
204
|
<style>
|
|
140
205
|
.my-calendar-btn {
|
|
141
|
-
font-family:
|
|
142
|
-
Inter,
|
|
143
|
-
system-ui,
|
|
144
|
-
-apple-system,
|
|
145
|
-
sans-serif;
|
|
146
|
-
font-size: 15px;
|
|
206
|
+
font-family: Inter, system-ui, sans-serif;
|
|
147
207
|
font-weight: 600;
|
|
148
|
-
letter-spacing: 0.01em;
|
|
149
208
|
border-radius: 9999px;
|
|
150
209
|
padding: 10px 18px;
|
|
151
|
-
border: 1px solid #1e3a8a;
|
|
152
210
|
background: #1d4ed8;
|
|
153
|
-
color: #
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
.my-calendar-btn:hover {
|
|
157
|
-
background: #1e40af;
|
|
211
|
+
color: #fff;
|
|
212
|
+
border: none;
|
|
213
|
+
cursor: pointer;
|
|
158
214
|
}
|
|
159
215
|
</style>
|
|
160
|
-
|
|
161
|
-
<script>
|
|
162
|
-
const primaryBtn = createMomentButton({
|
|
163
|
-
teamSlug: 'your-team-slug',
|
|
164
|
-
momentSlug: 'event-001',
|
|
165
|
-
triggerType: 'moment',
|
|
166
|
-
label: 'Get Tickets + Calendar',
|
|
167
|
-
className: 'my-calendar-btn',
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
document.getElementById('container').appendChild(primaryBtn);
|
|
171
|
-
</script>
|
|
172
216
|
```
|
|
173
217
|
|
|
174
|
-
|
|
218
|
+
Or use plain HTML triggers with your own classes:
|
|
175
219
|
|
|
176
220
|
```html
|
|
177
221
|
<button
|
|
178
222
|
class="my-calendar-btn moment-sync-trigger"
|
|
179
223
|
data-moment-team-slug="your-team-slug"
|
|
180
|
-
data-moment-slug="event-
|
|
224
|
+
data-moment-slug="your-event-slug"
|
|
181
225
|
data-moment-trigger-type="moment"
|
|
182
226
|
>
|
|
183
|
-
Add This
|
|
227
|
+
Add This Game to My Calendar
|
|
184
228
|
</button>
|
|
185
|
-
|
|
186
|
-
<script src="https://unpkg.com/@momentco-ai/moment-sdk"></script>
|
|
187
|
-
<script>
|
|
188
|
-
new MomentSdk();
|
|
189
|
-
</script>
|
|
190
229
|
```
|
|
191
230
|
|
|
192
|
-
Note: these customization options apply to trigger buttons. The modal UI itself is hosted in a secure iframe and is not themed by host-page CSS.
|
|
193
|
-
|
|
194
231
|
---
|
|
195
232
|
|
|
196
|
-
## Analytics
|
|
233
|
+
## Analytics events
|
|
197
234
|
|
|
198
|
-
The `onAnalytics` callback receives
|
|
235
|
+
The `onAnalytics` callback receives:
|
|
199
236
|
|
|
200
237
|
| Event | When |
|
|
201
238
|
| --------------------- | ----------------------------------- |
|
|
202
239
|
| `embed.init` | SDK initialized |
|
|
203
|
-
| `embed.trigger.click` | User clicked a trigger
|
|
204
|
-
| `embed.open` | Modal
|
|
240
|
+
| `embed.trigger.click` | User clicked a trigger |
|
|
241
|
+
| `embed.open` | Modal opened |
|
|
205
242
|
| `oauth.start` | OAuth popup requested |
|
|
206
243
|
| `oauth.popup.blocked` | Browser blocked the popup |
|
|
207
244
|
| `oauth.popup.closed` | User closed popup before completing |
|
|
@@ -211,37 +248,108 @@ The `onAnalytics` callback receives events with these names:
|
|
|
211
248
|
|
|
212
249
|
---
|
|
213
250
|
|
|
214
|
-
## Result
|
|
251
|
+
## Result payload
|
|
215
252
|
|
|
216
|
-
|
|
253
|
+
`onSuccess`, `onError`, and `onClose` receive:
|
|
217
254
|
|
|
218
255
|
```json
|
|
219
256
|
{
|
|
220
257
|
"source": "moment-live-embed",
|
|
221
258
|
"type": "moment.embed.result",
|
|
222
|
-
"result": "success
|
|
223
|
-
"provider": "google
|
|
224
|
-
"triggerType": "moment
|
|
259
|
+
"result": "success",
|
|
260
|
+
"provider": "google",
|
|
261
|
+
"triggerType": "moment",
|
|
225
262
|
"momentIds": ["..."],
|
|
226
263
|
"message": "optional details"
|
|
227
264
|
}
|
|
228
265
|
```
|
|
229
266
|
|
|
267
|
+
`result` is one of `success`, `error`, or `cancelled`. `provider` is `google` or `outlook` when applicable. `triggerType` is `moment`, `list`, `team`, or `brand-playlist`.
|
|
268
|
+
|
|
230
269
|
---
|
|
231
270
|
|
|
232
|
-
##
|
|
271
|
+
## Integration patterns
|
|
272
|
+
|
|
273
|
+
### Single game / event page
|
|
233
274
|
|
|
234
|
-
|
|
275
|
+
```html
|
|
276
|
+
<button
|
|
277
|
+
class="moment-sync-trigger"
|
|
278
|
+
data-moment-team-slug="your-team-slug"
|
|
279
|
+
data-moment-slug="your-moment-slug"
|
|
280
|
+
data-moment-trigger-type="moment"
|
|
281
|
+
>
|
|
282
|
+
Sync to Calendar
|
|
283
|
+
</button>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### CMS with native event IDs
|
|
287
|
+
|
|
288
|
+
When Moment provides a sync-subscription source ID for your feed:
|
|
289
|
+
|
|
290
|
+
```html
|
|
291
|
+
<button
|
|
292
|
+
class="moment-sync-trigger"
|
|
293
|
+
data-moment-team-slug="your-team-slug"
|
|
294
|
+
data-moment-trigger-type="moment"
|
|
295
|
+
data-api-source-id="your-source-id"
|
|
296
|
+
data-external-event-id="0022400123"
|
|
297
|
+
>
|
|
298
|
+
Sync to Calendar
|
|
299
|
+
</button>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Follow the full schedule
|
|
303
|
+
|
|
304
|
+
```html
|
|
305
|
+
<button
|
|
306
|
+
class="moment-sync-trigger"
|
|
307
|
+
data-moment-team-slug="your-team-slug"
|
|
308
|
+
data-moment-trigger-type="team"
|
|
309
|
+
>
|
|
310
|
+
Sync Full Schedule
|
|
311
|
+
</button>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Dynamic buttons (SPA / React / Vue)
|
|
315
|
+
|
|
316
|
+
After rendering new trigger elements, call `sdk.rebind()` so click handlers attach to the new DOM nodes.
|
|
235
317
|
|
|
236
318
|
---
|
|
237
319
|
|
|
238
|
-
##
|
|
320
|
+
## Examples playground
|
|
321
|
+
|
|
322
|
+
After installing the package, run the bundled demos (no repo clone required):
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
npx moment-sdk-examples
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Opens **http://localhost:5051** with moment, collection, and brand-schedule triggers wired to the **sdk-demo** fixture brand.
|
|
329
|
+
|
|
330
|
+
Repo maintainers: `pnpm examples` runs the same server from a local clone.
|
|
239
331
|
|
|
240
|
-
See [
|
|
332
|
+
See [examples/README.md](./examples/README.md) for routes and fixture slugs.
|
|
333
|
+
|
|
334
|
+
---
|
|
241
335
|
|
|
242
336
|
## Security
|
|
243
337
|
|
|
244
|
-
|
|
338
|
+
OAuth tokens never touch your site — authorization happens on Moment Live's backend. The SDK validates `postMessage` origin on every iframe and popup message.
|
|
339
|
+
|
|
340
|
+
See [SECURITY.md](./SECURITY.md) for the full security model and vulnerability reporting.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Changelog
|
|
345
|
+
|
|
346
|
+
See [CHANGELOG.md](./CHANGELOG.md) for version history and migration notes.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Support
|
|
351
|
+
|
|
352
|
+
- **Email:** [support@momentco.ai](mailto:support@momentco.ai)
|
|
245
353
|
|
|
246
354
|
## License
|
|
247
355
|
|
package/dist/moment-sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(h){"use strict";class w{popup=null;pollTimer=null;messageHandler=null;resultReceived=!1;opts;constructor(e){this.opts=e}open(e){this.cleanup(),this.resultReceived=!1;const t=this.buildPopupUrl(e),i=Math.round(window.screenX+(window.outerWidth-500)/2),o=Math.round(window.screenY+(window.outerHeight-700)/2);if(this.popup=window.open(t,"moment-oauth-popup",`width=500,height=700,left=${i},top=${o},menubar=no,toolbar=no,location=yes,status=no`),!this.popup||this.popup.closed){this.sendStatusToIframe("popup-blocked"),this.opts.onPopupBlocked();return}this.sendStatusToIframe("popup-opened"),this.startPolling(),this.messageHandler=n=>{if(n.origin!==this.opts.liveOrigin||n.source!==this.popup)return;const r=n.data;!r||typeof r!="object"||r.source!=="moment-live-embed"||r.type!=="moment.embed.result"||(this.resultReceived=!0,this.sendStatusToIframe("completed"),this.opts.iframeWindow&&this.opts.iframeWindow.postMessage(r,this.opts.liveOrigin),this.opts.onResult(r),this.cleanup())},window.addEventListener("message",this.messageHandler)}cleanup(){if(this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.popup&&!this.popup.closed)try{this.popup.close()}catch{}this.popup=null}buildPopupUrl(e){const t=window.location.origin,i=new URLSearchParams({provider:e.provider,teamSlug:e.teamSlug,ids:e.ids.join(","),triggerType:e.triggerType,returnOrigin:t});return e.subscriptionType&&i.set("subscriptionType",e.subscriptionType),`${this.opts.liveBaseUrl}/en/embed/oauth-popup?${i.toString()}`}startPolling(){this.pollTimer=setInterval(()=>{(!this.popup||this.popup.closed)&&(this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.resultReceived||(this.sendStatusToIframe("popup-closed"),this.opts.onPopupClosed()),this.cleanup())},500)}sendStatusToIframe(e){if(!this.opts.iframeWindow)return;const t={source:"moment-sdk",type:"moment.embed.oauth.status",status:e};this.opts.iframeWindow.postMessage(t,this.opts.liveOrigin)}}const g="moment-sdk-trigger-styles",T=`
|
|
2
2
|
:where(.moment-sync-trigger) {
|
|
3
3
|
background-color: #1c1917;
|
|
4
4
|
color: #ffffff;
|
|
@@ -22,4 +22,4 @@
|
|
|
22
22
|
outline: 2px solid #1c1917;
|
|
23
23
|
outline-offset: 2px;
|
|
24
24
|
}
|
|
25
|
-
`;function S(){if(typeof document>"u"||document.getElementById(v))return;const s=document.createElement("style");s.id=v,s.textContent=T,document.head.appendChild(s)}const E=".moment-sync-trigger",I="https://live.dev.momentco.ai";class b{opts;liveBaseUrl;liveOrigin;overlay=null;iframe=null;popupBridge=null;messageHandler=null;boundClickHandlers=new Map;activePayload=null;previousFocusedElement=null;previousBodyOverflow="";didManageBodyOverflow=!1;closeTimer=null;constructor(e){this.opts={triggerSelector:E,...e},this.liveBaseUrl=I;try{this.liveOrigin=new URL(this.liveBaseUrl).origin}catch{throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`)}S(),this.bindTriggers(),this.emitAnalytics("embed.init")}open(e){this.emitAnalytics("embed.open",{teamSlug:e.teamSlug,triggerType:e.triggerType}),this.createModal(e)}close(){this.handleCancel()}rebind(){this.unbindTriggers(),this.bindTriggers()}destroy(){this.cleanup(),this.unbindTriggers()}bindTriggers(){document.querySelectorAll(this.opts.triggerSelector).forEach(t=>{const i=n=>{n.preventDefault(),this.handleTriggerClick(t)},r=this.boundClickHandlers.get(t);r&&t.removeEventListener("click",r),t.addEventListener("click",i),this.boundClickHandlers.set(t,i)})}unbindTriggers(){this.boundClickHandlers.forEach((e,t)=>{t.removeEventListener("click",e)}),this.boundClickHandlers.clear()}handleTriggerClick(e){const t=e.getAttribute("data-moment-team-slug")??"",i=e.getAttribute("data-moment-slug")??void 0,r=e.getAttribute("data-moment-list-slug")??void 0,n=e.getAttribute("data-moment-trigger-type");let o="moment";n!=null&&n!==""&&(n==="moment"||n==="list"||n==="team"?o=n:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",n));const d=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",u=d?d.split(",").filter(Boolean):[],a=e.getAttribute("data-moment-calendar");let m;a!=null&&a!==""&&(a==="google"||a==="outlook"?m=a:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",a));const l=e.getAttribute("data-api-source-id")??void 0,c=e.getAttribute("data-external-event-id")??void 0;if(!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const h=o==="team"?void 0:i,g=o==="team"?void 0:r,p=o==="team"?[]:u;this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:o,momentIds:p}),this.open({teamSlug:t,momentSlug:h,listSlug:g,triggerType:o,ids:p,calendar:m??void 0,apiSourceId:l,externalEventId:c})}createModal(e){this.cleanup(),this.previousFocusedElement=document.activeElement||null,this.activePayload=e;const t=this.buildIframeUrl(e);this.overlay=document.createElement("div"),this.overlay.setAttribute("role","presentation"),Object.assign(this.overlay.style,{position:"fixed",inset:"0",zIndex:"999999",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"rgba(0, 0, 0, 0.5)",backdropFilter:"blur(2px)",padding:"16px"}),this.overlay.addEventListener("click",l=>{l.target===this.overlay&&this.handleCancel()});const i=l=>{l.key==="Escape"&&this.handleCancel()};document.addEventListener("keydown",i);const r=document.createElement("div");Object.assign(r.style,{position:"relative",width:"100%",maxWidth:"380px",borderRadius:"16px",overflow:"hidden",backgroundColor:"#000000",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",display:"flex",flexDirection:"column"});const n=document.createElement("div");n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Calendar sync modal"),n.tabIndex=-1,Object.assign(n.style,{position:"relative",width:"100%",maxHeight:"80vh",borderRadius:"12px",overflow:"auto",backgroundColor:"#ffffff"});const o=document.createElement("button");o.type="button",o.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',o.setAttribute("aria-label","Close"),Object.assign(o.style,{position:"absolute",top:"10px",right:"10px",zIndex:"10",border:"none",backgroundColor:"transparent",color:"#999",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",padding:"0"}),o.addEventListener("mouseenter",()=>{o.style.color="#666"}),o.addEventListener("mouseleave",()=>{o.style.color="#999"}),o.addEventListener("click",()=>this.handleCancel()),this.iframe=document.createElement("iframe"),this.iframe.src=t,this.iframe.setAttribute("allow",""),this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-popups allow-forms"),this.iframe.setAttribute("scrolling","no"),Object.assign(this.iframe.style,{width:"100%",border:"none",display:"block",height:"420px",overflow:"hidden",transition:"height 0.15s ease"}),n.appendChild(o),n.appendChild(this.iframe);const d=document.createElement("div");Object.assign(d.style,{display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",padding:"12px 0"});const u=document.createElement("span");u.textContent="Powered by",Object.assign(u.style,{fontFamily:"Inter, system-ui, -apple-system, sans-serif",fontSize:"12px",lineHeight:"16px",color:"#ffffff"});const a=document.createElement("img");a.alt="Moment",a.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(a.style,{height:"auto",width:"67px"}),d.appendChild(u),d.appendChild(a),r.appendChild(n),r.appendChild(d),this.overlay.appendChild(r),document.body.appendChild(this.overlay);const m=l=>{if(l.key!=="Tab")return;const c=n.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(c.length===0){l.preventDefault(),n.focus();return}const h=c[0],g=c[c.length-1],p=document.activeElement;l.shiftKey&&p===h?(l.preventDefault(),g.focus()):!l.shiftKey&&p===g&&(l.preventDefault(),h.focus())};n.addEventListener("keydown",m),o.focus(),this.previousBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.didManageBodyOverflow=!0,this.setupMessageListener(),this.overlay._escHandler=i,this.overlay._focusTrapHandler=m}buildIframeUrl(e){const t=new URLSearchParams;return t.set("teamSlug",e.teamSlug),e.momentSlug&&t.set("momentSlug",e.momentSlug),e.listSlug&&t.set("listSlug",e.listSlug),t.set("triggerType",e.triggerType),e.ids?.length&&t.set("ids",e.ids.join(",")),e.calendar&&t.set("calendar",e.calendar),e.apiSourceId&&t.set("subscriptionId",e.apiSourceId),e.externalEventId&&t.set("externalId",e.externalEventId),t.set("returnOrigin",window.location.origin),t.set("embed","1"),`${this.liveBaseUrl}/en/embed/sync?${t.toString()}`}setupMessageListener(){this.messageHandler=e=>{if(e.origin!==this.liveOrigin||e.source!==this.iframe?.contentWindow)return;const t=e.data;if(!(!t||typeof t!="object")){if(t.source==="moment-live-embed"&&t.type==="moment.embed.oauth.start"){const i=t;this.emitAnalytics("oauth.start",{provider:i.provider,teamSlug:i.teamSlug}),this.handleOAuthStart(i);return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.resize"){const i=typeof t.height=="number"?t.height:0;if(i>0&&this.iframe){const r=Math.round(window.innerHeight*.8);this.iframe.style.height=`${Math.min(i,r)}px`}return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.open.url"){const{url:i}=t;i&&typeof i=="string"&&i.startsWith("webcal://")&&window.open(i,"_blank");return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.result"){const i=t;this.handleResult(i)}}},window.addEventListener("message",this.messageHandler)}handleOAuthStart(e){this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.popupBridge=new w({liveBaseUrl:this.liveBaseUrl,liveOrigin:this.liveOrigin,iframeWindow:this.iframe?.contentWindow??null,onResult:t=>{this.handleResult(t)},onPopupBlocked:()=>{this.emitAnalytics("oauth.popup.blocked",{provider:e.provider})},onPopupClosed:()=>{this.emitAnalytics("oauth.popup.closed",{provider:e.provider})}}),this.popupBridge.open(e)}handleResult(e){if(e.result==="success")this.emitAnalytics("subscribe.success",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onSuccess?.(e);else if(e.result==="error")this.emitAnalytics("subscribe.error",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onError?.(e);else if(e.result==="cancelled")return;this.opts.onClose?.(e),this.emitAnalytics("embed.close"),this.closeTimer=setTimeout(()=>this.cleanup(),1500)}handleCancel(){this.emitAnalytics("embed.close");const t={source:"moment-live-embed",type:"moment.embed.result",result:"cancelled",triggerType:this.activePayload?.triggerType??"moment",momentIds:this.activePayload?.ids??[]};this.opts.onClose?.(t),this.cleanup()}cleanup(){if(this.closeTimer&&(clearTimeout(this.closeTimer),this.closeTimer=null),this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.overlay){const{_escHandler:e,_focusTrapHandler:t}=this.overlay;e&&document.removeEventListener("keydown",e);const i=this.overlay.firstElementChild;i&&t&&i.removeEventListener("keydown",t),this.overlay.remove(),this.overlay=null}this.iframe=null,this.activePayload=null,this.didManageBodyOverflow&&(document.body.style.overflow=this.previousBodyOverflow,this.didManageBodyOverflow=!1),this.previousFocusedElement&&typeof this.previousFocusedElement.focus=="function"&&this.previousFocusedElement.focus(),this.previousFocusedElement=null}emitAnalytics(e,t){this.opts.onAnalytics?.({event:e,timestamp:Date.now(),...t})}}function y(s){const e=document.createElement("button");return e.type="button",e.classList.add("moment-sync-trigger"),s.className&&s.className.split(" ").forEach(t=>{t&&e.classList.add(t)}),e.setAttribute("data-moment-team-slug",s.teamSlug),s.momentSlug&&e.setAttribute("data-moment-slug",s.momentSlug),s.listSlug&&e.setAttribute("data-moment-list-slug",s.listSlug),s.triggerType&&e.setAttribute("data-moment-trigger-type",s.triggerType),s.ids?.length&&e.setAttribute("data-moment-ids",s.ids.join(",")),s.calendar&&e.setAttribute("data-moment-calendar",s.calendar),s.apiSourceId&&e.setAttribute("data-api-source-id",s.apiSourceId),s.externalEventId&&e.setAttribute("data-external-event-id",s.externalEventId),e.textContent=s.label??"Add to Calendar",e}if(typeof window<"u"){const s=window;s.MomentSdk=b,s.createMomentButton=y}f.MomentSdk=b,f.createMomentButton=y,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|
|
25
|
+
`;function S(){if(typeof document>"u"||document.getElementById(g))return;const s=document.createElement("style");s.id=g,s.textContent=T,document.head.appendChild(s)}const E=".moment-sync-trigger",x="https://live.dev.momentco.ai";class f{opts;liveBaseUrl;liveOrigin;overlay=null;iframe=null;popupBridge=null;messageHandler=null;boundClickHandlers=new Map;activePayload=null;previousFocusedElement=null;previousBodyOverflow="";didManageBodyOverflow=!1;closeTimer=null;constructor(e){this.opts={triggerSelector:E,...e},this.liveBaseUrl=x;try{this.liveOrigin=new URL(this.liveBaseUrl).origin}catch{throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`)}S(),this.bindTriggers(),this.emitAnalytics("embed.init")}open(e){const t=this.scopeOpenPayload(e);this.emitAnalytics("embed.open",{teamSlug:t.teamSlug,triggerType:t.triggerType}),this.createModal(t)}close(){this.handleCancel()}rebind(){this.unbindTriggers(),this.bindTriggers()}destroy(){this.cleanup(),this.unbindTriggers()}bindTriggers(){document.querySelectorAll(this.opts.triggerSelector).forEach(t=>{const i=n=>{n.preventDefault(),this.handleTriggerClick(t)},o=this.boundClickHandlers.get(t);o&&t.removeEventListener("click",o),t.addEventListener("click",i),this.boundClickHandlers.set(t,i)})}unbindTriggers(){this.boundClickHandlers.forEach((e,t)=>{t.removeEventListener("click",e)}),this.boundClickHandlers.clear()}handleTriggerClick(e){const t=e.getAttribute("data-moment-team-slug")??"",i=e.getAttribute("data-moment-slug")??void 0,o=e.getAttribute("data-moment-list-slug")??void 0,n=e.getAttribute("data-moment-trigger-type");let r="moment";n!=null&&n!==""&&(n==="moment"||n==="list"||n==="team"||n==="brand-playlist"?r=n:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",n));const d=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",c=d?d.split(",").filter(Boolean):[],a=e.getAttribute("data-moment-calendar");let m;a!=null&&a!==""&&(a==="google"||a==="outlook"?m=a:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",a));const l=e.getAttribute("data-api-source-id")??void 0,u=e.getAttribute("data-external-event-id")??void 0;if(!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const p=r==="team"||r==="brand-playlist";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:p?[]:c}),this.open({teamSlug:t,momentSlug:i,listSlug:o,triggerType:r,ids:c,calendar:m??void 0,apiSourceId:l,externalEventId:u})}scopeOpenPayload(e){return e.triggerType==="team"||e.triggerType==="brand-playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,calendar:e.calendar}:e}createModal(e){this.cleanup(),this.previousFocusedElement=document.activeElement||null,this.activePayload=e;const t=this.buildIframeUrl(e);this.overlay=document.createElement("div"),this.overlay.setAttribute("role","presentation"),Object.assign(this.overlay.style,{position:"fixed",inset:"0",zIndex:"999999",display:"flex",alignItems:"center",justifyContent:"center",backgroundColor:"rgba(0, 0, 0, 0.5)",backdropFilter:"blur(2px)",padding:"16px"}),this.overlay.addEventListener("click",l=>{l.target===this.overlay&&this.handleCancel()});const i=l=>{l.key==="Escape"&&this.handleCancel()};document.addEventListener("keydown",i);const o=document.createElement("div");Object.assign(o.style,{position:"relative",width:"100%",maxWidth:"380px",borderRadius:"16px",overflow:"hidden",backgroundColor:"#000000",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",display:"flex",flexDirection:"column"});const n=document.createElement("div");n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Calendar sync modal"),n.tabIndex=-1,Object.assign(n.style,{position:"relative",width:"100%",maxHeight:"80vh",borderRadius:"12px",overflow:"auto",backgroundColor:"#ffffff"});const r=document.createElement("button");r.type="button",r.innerHTML='<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>',r.setAttribute("aria-label","Close"),Object.assign(r.style,{position:"absolute",top:"10px",right:"10px",zIndex:"10",border:"none",backgroundColor:"transparent",color:"#999",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",width:"24px",height:"24px",padding:"0"}),r.addEventListener("mouseenter",()=>{r.style.color="#666"}),r.addEventListener("mouseleave",()=>{r.style.color="#999"}),r.addEventListener("click",()=>this.handleCancel()),this.iframe=document.createElement("iframe"),this.iframe.src=t,this.iframe.setAttribute("allow",""),this.iframe.setAttribute("sandbox","allow-scripts allow-same-origin allow-popups allow-forms"),this.iframe.setAttribute("scrolling","no"),Object.assign(this.iframe.style,{width:"100%",border:"none",display:"block",height:"420px",overflow:"hidden",transition:"height 0.15s ease"}),n.appendChild(r),n.appendChild(this.iframe);const d=document.createElement("div");Object.assign(d.style,{display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",padding:"12px 0"});const c=document.createElement("span");c.textContent="Powered by",Object.assign(c.style,{fontFamily:"Inter, system-ui, -apple-system, sans-serif",fontSize:"12px",lineHeight:"16px",color:"#ffffff"});const a=document.createElement("img");a.alt="Moment",a.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(a.style,{height:"auto",width:"67px"}),d.appendChild(c),d.appendChild(a),o.appendChild(n),o.appendChild(d),this.overlay.appendChild(o),document.body.appendChild(this.overlay);const m=l=>{if(l.key!=="Tab")return;const u=n.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(u.length===0){l.preventDefault(),n.focus();return}const p=u[0],b=u[u.length-1],y=document.activeElement;l.shiftKey&&y===p?(l.preventDefault(),b.focus()):!l.shiftKey&&y===b&&(l.preventDefault(),p.focus())};n.addEventListener("keydown",m),r.focus(),this.previousBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.didManageBodyOverflow=!0,this.setupMessageListener(),this.overlay._escHandler=i,this.overlay._focusTrapHandler=m}buildIframeUrl(e){const t=new URLSearchParams;return t.set("teamSlug",e.teamSlug),e.momentSlug&&t.set("momentSlug",e.momentSlug),e.listSlug&&t.set("listSlug",e.listSlug),t.set("triggerType",e.triggerType),e.ids?.length&&t.set("ids",e.ids.join(",")),e.calendar&&t.set("calendar",e.calendar),e.apiSourceId&&t.set("subscriptionId",e.apiSourceId),e.externalEventId&&t.set("externalId",e.externalEventId),t.set("returnOrigin",window.location.origin),t.set("embed","1"),`${this.liveBaseUrl}/en/embed/sync?${t.toString()}`}setupMessageListener(){this.messageHandler=e=>{if(e.origin!==this.liveOrigin||e.source!==this.iframe?.contentWindow)return;const t=e.data;if(!(!t||typeof t!="object")){if(t.source==="moment-live-embed"&&t.type==="moment.embed.oauth.start"){const i=t;this.emitAnalytics("oauth.start",{provider:i.provider,teamSlug:i.teamSlug}),this.handleOAuthStart(i);return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.resize"){const i=typeof t.height=="number"?t.height:0;if(i>0&&this.iframe){const o=Math.round(window.innerHeight*.8);this.iframe.style.height=`${Math.min(i,o)}px`}return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.open.url"){const{url:i}=t;i&&typeof i=="string"&&i.startsWith("webcal://")&&window.open(i,"_blank");return}if(t.source==="moment-live-embed"&&t.type==="moment.embed.result"){const i=t;this.handleResult(i)}}},window.addEventListener("message",this.messageHandler)}handleOAuthStart(e){this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.popupBridge=new w({liveBaseUrl:this.liveBaseUrl,liveOrigin:this.liveOrigin,iframeWindow:this.iframe?.contentWindow??null,onResult:t=>{this.handleResult(t)},onPopupBlocked:()=>{this.emitAnalytics("oauth.popup.blocked",{provider:e.provider})},onPopupClosed:()=>{this.emitAnalytics("oauth.popup.closed",{provider:e.provider})}}),this.popupBridge.open(e)}handleResult(e){if(e.result==="success")this.emitAnalytics("subscribe.success",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onSuccess?.(e);else if(e.result==="error")this.emitAnalytics("subscribe.error",{provider:e.provider,triggerType:e.triggerType,momentIds:e.momentIds}),this.opts.onError?.(e);else if(e.result==="cancelled")return;this.opts.onClose?.(e),this.emitAnalytics("embed.close"),this.closeTimer=setTimeout(()=>this.cleanup(),1500)}handleCancel(){this.emitAnalytics("embed.close");const t={source:"moment-live-embed",type:"moment.embed.result",result:"cancelled",triggerType:this.activePayload?.triggerType??"moment",momentIds:this.activePayload?.ids??[]};this.opts.onClose?.(t),this.cleanup()}cleanup(){if(this.closeTimer&&(clearTimeout(this.closeTimer),this.closeTimer=null),this.popupBridge&&(this.popupBridge.cleanup(),this.popupBridge=null),this.messageHandler&&(window.removeEventListener("message",this.messageHandler),this.messageHandler=null),this.overlay){const{_escHandler:e,_focusTrapHandler:t}=this.overlay;e&&document.removeEventListener("keydown",e);const i=this.overlay.firstElementChild;i&&t&&i.removeEventListener("keydown",t),this.overlay.remove(),this.overlay=null}this.iframe=null,this.activePayload=null,this.didManageBodyOverflow&&(document.body.style.overflow=this.previousBodyOverflow,this.didManageBodyOverflow=!1),this.previousFocusedElement&&typeof this.previousFocusedElement.focus=="function"&&this.previousFocusedElement.focus(),this.previousFocusedElement=null}emitAnalytics(e,t){this.opts.onAnalytics?.({event:e,timestamp:Date.now(),...t})}}function v(s){const e=document.createElement("button");return e.type="button",e.classList.add("moment-sync-trigger"),s.className&&s.className.split(" ").forEach(t=>{t&&e.classList.add(t)}),e.setAttribute("data-moment-team-slug",s.teamSlug),s.momentSlug&&e.setAttribute("data-moment-slug",s.momentSlug),s.listSlug&&e.setAttribute("data-moment-list-slug",s.listSlug),s.triggerType&&e.setAttribute("data-moment-trigger-type",s.triggerType),s.ids?.length&&e.setAttribute("data-moment-ids",s.ids.join(",")),s.calendar&&e.setAttribute("data-moment-calendar",s.calendar),s.apiSourceId&&e.setAttribute("data-api-source-id",s.apiSourceId),s.externalEventId&&e.setAttribute("data-external-event-id",s.externalEventId),e.textContent=s.label??"Add to Calendar",e}if(typeof window<"u"){const s=window;s.MomentSdk=f,s.createMomentButton=v}h.MomentSdk=f,h.createMomentButton=v,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|