@momentco-ai/moment-sdk 0.3.3-dev.22 → 0.4.0-dev.26
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 +33 -2
- package/README.md +23 -20
- package/dist/moment-sdk.js +2 -2
- package/dist/moment-sdk.mjs +102 -72
- package/dist/types/sdk/button.d.ts +1 -4
- package/dist/types/sdk/moment-sdk.d.ts +2 -0
- package/dist/types/sdk/types.d.ts +63 -19
- package/examples/README.md +1 -0
- package/examples/index.html +4 -0
- package/examples/schedule/index.html +43 -0
- package/examples/schedule/main.js +23 -0
- package/examples/serve.mjs +2 -0
- package/package.json +1 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [Unreleased
|
|
8
|
+
## [0.4.0] - Unreleased
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`schedule` trigger type** — `data-moment-trigger-type="schedule"` with `teamSlug` only opens the full brand schedule experience, where a fan multi-selects any mix of collections (playlists) and individual events (moments) and syncs them all at once. Opens a dedicated Live `/embed/schedule` route.
|
|
13
|
+
- `scheduleMomentIds` / `schedulePlaylistIds` on `MomentOAuthStartPayload` — the mixed selection is carried through the OAuth popup URL so it survives the redirect and browser third-party storage partitioning.
|
|
14
|
+
- `'partial'` result on `MomentResultPayload`, plus a `counts` breakdown (`requested`, `succeeded`, `failed`, `moments`, `playlists`) for multi-resource (schedule) syncs.
|
|
15
|
+
- `moment.embed.close` message (`MomentClosePayload`) — lets the schedule experience's interactive terminal screens (header close, "Done") dismiss the modal explicitly; the SDK keeps the modal open on a `cancelled` result so the iframe can show retry UI.
|
|
16
|
+
- `moment.embed.theme` message (`MomentThemePayload`) — the `schedule` experience forwards the brand's configured surface border radius so the SDK rounds the modal frame (wrapper + container) to match the in-iframe surfaces. The SDK makes no config request of its own; the iframe resolves the brand SDK config and only passes the radius over the already-trusted (origin + source validated) channel.
|
|
17
|
+
|
|
18
|
+
## [0.3.3] - 2026-06-24
|
|
19
|
+
|
|
20
|
+
> **Reverted** the same day (#25) and rolled back to 0.3.2. This was an initial cut of `schedule` trigger support; the feature was reworked and re-introduced in 0.4.0.
|
|
21
|
+
|
|
22
|
+
## [0.3.2] - 2026-06-24
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Multi-slug `playlist` trigger — `playlistSlugs` on `MomentSdkOpenPayload` / `createMomentButton()` and the `data-moment-playlist-slugs` attribute. With a single slug Live syncs that playlist directly; with several it prompts the user to pick which one to sync.
|
|
27
|
+
|
|
28
|
+
## [0.3.1] - 2026-06-22
|
|
9
29
|
|
|
10
30
|
### Added
|
|
11
31
|
|
|
@@ -64,6 +84,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
64
84
|
- Removed transitional `subscriptionType` fallback handling to align with the intentional `triggerType` protocol.
|
|
65
85
|
- Ensured modal iframe and OAuth popup URLs both pass `triggerType` consistently to Moment Live embed routes.
|
|
66
86
|
|
|
87
|
+
## [0.1.1] - 2026-04-07
|
|
88
|
+
|
|
89
|
+
### Added
|
|
90
|
+
|
|
91
|
+
- Trigger styling examples and expanded embed documentation in the README.
|
|
92
|
+
|
|
67
93
|
## [0.1.0] - 2026-04-01
|
|
68
94
|
|
|
69
95
|
### Added
|
|
@@ -79,9 +105,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
79
105
|
- UMD + ESM builds with TypeScript declarations
|
|
80
106
|
- Build-time environment configuration via `VITE_LIVE_BASE_URL`
|
|
81
107
|
|
|
108
|
+
[0.4.0]: https://github.com/Moment-Co/moment-sdk/compare/v0.3.2...HEAD
|
|
109
|
+
[0.3.3]: https://github.com/Moment-Co/moment-sdk/compare/v0.3.2...v0.3.3
|
|
110
|
+
[0.3.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.3.1...v0.3.2
|
|
111
|
+
[0.3.1]: https://github.com/Moment-Co/moment-sdk/compare/v0.3.0...v0.3.1
|
|
82
112
|
[0.3.0]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.2...v0.3.0
|
|
83
113
|
[0.2.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.1...v0.2.2
|
|
84
114
|
[0.2.1]: https://github.com/Moment-Co/moment-sdk/compare/v0.2.0...v0.2.1
|
|
85
115
|
[0.2.0]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.2...v0.2.0
|
|
86
|
-
[0.1.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.
|
|
116
|
+
[0.1.2]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.1...v0.1.2
|
|
117
|
+
[0.1.1]: https://github.com/Moment-Co/moment-sdk/compare/v0.1.0...v0.1.1
|
|
87
118
|
[0.1.0]: https://github.com/Moment-Co/moment-sdk/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -91,14 +91,16 @@ Clicking the button opens a modal where the fan selects Google Calendar or Outlo
|
|
|
91
91
|
|
|
92
92
|
Set `data-moment-trigger-type` (or `triggerType` in `open()` / `createMomentButton()`):
|
|
93
93
|
|
|
94
|
-
| Type | Required attributes
|
|
95
|
-
| ------------------ |
|
|
96
|
-
| `moment` (default) | `data-moment-team-slug` + `data-moment-slug`
|
|
97
|
-
| `list` | `data-moment-team-slug` + `data-moment-list-slug`
|
|
98
|
-
| `team` | `data-moment-team-slug`
|
|
99
|
-
| `brand-playlist` | `data-moment-team-slug`
|
|
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) |
|
|
100
|
+
| `playlist` | `data-moment-team-slug` + `data-moment-playlist-slugs` | One of several playlists (picker when >1 slug) |
|
|
101
|
+
| `schedule` | `data-moment-team-slug` | Full schedule experience — user picks a mix of playlists + events |
|
|
100
102
|
|
|
101
|
-
For `team
|
|
103
|
+
For `team`, `brand-playlist`, and `schedule`, omit moment/list/ID and source-resolution attributes — only `data-moment-team-slug` is required.
|
|
102
104
|
|
|
103
105
|
---
|
|
104
106
|
|
|
@@ -106,18 +108,19 @@ For `team` and `brand-playlist`, omit moment/list/ID and source-resolution attri
|
|
|
106
108
|
|
|
107
109
|
Place these on any element matching the trigger selector (default: `.moment-sync-trigger`):
|
|
108
110
|
|
|
109
|
-
| Attribute | Required
|
|
110
|
-
| ------------------------------- |
|
|
111
|
-
| `data-moment-team-slug` | Yes
|
|
112
|
-
| `data-moment-slug` | For `moment`
|
|
113
|
-
| `data-moment-list-slug` | For `list`
|
|
114
|
-
| `data-moment-trigger-type` | No
|
|
115
|
-
| `data-moment-
|
|
116
|
-
| `data-moment-
|
|
117
|
-
| `data-moment-
|
|
118
|
-
| `data-
|
|
119
|
-
| `data-
|
|
120
|
-
| `data-external-event-id
|
|
111
|
+
| Attribute | Required | Description |
|
|
112
|
+
| ------------------------------- | -------------- | ------------------------------------------------------------------------------- |
|
|
113
|
+
| `data-moment-team-slug` | Yes | Team/brand slug provided by Moment |
|
|
114
|
+
| `data-moment-slug` | For `moment` | Brand-level moment slug (resolved server-side) |
|
|
115
|
+
| `data-moment-list-slug` | For `list` | List slug |
|
|
116
|
+
| `data-moment-trigger-type` | No | `moment` (default), `list`, `team`, `brand-playlist`, `playlist`, or `schedule` |
|
|
117
|
+
| `data-moment-playlist-slugs` | For `playlist` | Comma-separated playlist slugs (picker shown when more than one) |
|
|
118
|
+
| `data-moment-ids` | No | Comma-separated moment IDs (fallback if slugs don't resolve) |
|
|
119
|
+
| `data-moment-game-id` | No | Alias for `data-moment-ids` |
|
|
120
|
+
| `data-moment-calendar` | No | Pre-select provider: `google` or `outlook` |
|
|
121
|
+
| `data-api-source-id` | No | Sync-subscription source ID (pairs with external id attributes) |
|
|
122
|
+
| `data-external-event-id` | No | Exact external id (`event_id:showing_id`) resolved against source |
|
|
123
|
+
| `data-external-event-id-prefix` | No | Upstream event id only — user picks a showing in the embed modal |
|
|
121
124
|
|
|
122
125
|
When `data-api-source-id` is set with either `data-external-event-id` (one showing) or `data-external-event-id-prefix` (all showings for an event), Live resolves moments server-side — useful when your CMS emits native event IDs instead of Moment slugs.
|
|
123
126
|
|
|
@@ -273,7 +276,7 @@ The `onAnalytics` callback receives:
|
|
|
273
276
|
}
|
|
274
277
|
```
|
|
275
278
|
|
|
276
|
-
`result` is one of `success`, `error`, or `
|
|
279
|
+
`result` is one of `success`, `error`, `cancelled`, or `partial` (schedule trigger, when only some resources synced). `provider` is `google`, `outlook`, or `apple` when applicable. `triggerType` echoes the trigger (`moment`, `list`, `team`, `brand-playlist`, `playlist`, or `schedule`). For `schedule`, an optional `counts: { requested, succeeded, failed }` reports the aggregate outcome.
|
|
277
280
|
|
|
278
281
|
---
|
|
279
282
|
|
package/dist/moment-sdk.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(v){"use strict";class x{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=s=>{if(s.origin!==this.opts.liveOrigin||s.source!==this.popup)return;const r=s.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),e.scheduleMomentIds?.length&&i.set("scheduleMomentIds",e.scheduleMomentIds.join(",")),e.schedulePlaylistIds?.length&&i.set("schedulePlaylistIds",e.schedulePlaylistIds.join(",")),`${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)}}function S(n,e,t=typeof window<"u"?window.location.origin:""){if(e.triggerType==="schedule"){const o=new URLSearchParams;return o.set("teamSlug",e.teamSlug),o.set("returnOrigin",t),o.set("embed","1"),`${n}/en/embed/schedule?${o.toString()}`}const i=new URLSearchParams;return i.set("teamSlug",e.teamSlug),e.momentSlug&&i.set("momentSlug",e.momentSlug),e.listSlug&&i.set("listSlug",e.listSlug),i.set("triggerType",e.triggerType),e.ids?.length&&i.set("ids",e.ids.join(",")),e.playlistSlugs?.length&&i.set("playlistSlugs",e.playlistSlugs.join(",")),e.calendar&&i.set("calendar",e.calendar),e.apiSourceId&&i.set("subscriptionId",e.apiSourceId),e.externalEventId&&i.set("externalId",e.externalEventId),e.externalEventIdPrefix&&!e.externalEventId&&i.set("externalIdPrefix",e.externalEventIdPrefix),i.set("returnOrigin",t),i.set("embed","1"),`${n}/en/embed/sync?${i.toString()}`}const b="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 n=document.createElement("style");n.id=v,n.textContent=T,document.head.appendChild(n)}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){e.externalEventId&&e.externalEventIdPrefix&&console.warn("[MomentSdk] Both externalEventId and externalEventIdPrefix provided; using externalEventId only.");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=s=>{s.preventDefault(),this.handleTriggerClick(t)},l=this.boundClickHandlers.get(t);l&&t.removeEventListener("click",l),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,l=e.getAttribute("data-moment-list-slug")??void 0,s=e.getAttribute("data-moment-trigger-type");let r="moment";s!=null&&s!==""&&(s==="moment"||s==="list"||s==="team"||s==="brand-playlist"||s==="playlist"||s==="schedule"?r=s:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",s));const a=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",m=a?a.split(",").filter(Boolean):[],d=e.getAttribute("data-moment-playlist-slugs")??"",g=d?d.split(",").map(A=>A.trim()).filter(Boolean):[],o=e.getAttribute("data-moment-calendar");let c;o!=null&&o!==""&&(o==="google"||o==="outlook"?c=o:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",o));const h=e.getAttribute("data-api-source-id")??void 0,u=e.getAttribute("data-external-event-id")??void 0,p=e.getAttribute("data-external-event-id-prefix")??void 0;if(u&&p&&console.warn("[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."),!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const P=r==="team"||r==="brand-playlist"||r==="playlist"||r==="schedule";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:P?[]:m}),this.open({teamSlug:t,momentSlug:i,listSlug:l,triggerType:r,ids:m,playlistSlugs:g,calendar:c??void 0,apiSourceId:h,externalEventId:p&&!u?void 0:u,externalEventIdPrefix:u?void 0:p})}scopeOpenPayload(e){return e.triggerType==="playlist"||e.triggerType==="schedule"?{teamSlug:e.teamSlug,triggerType:e.triggerType,playlistSlugs:e.playlistSlugs,calendar:e.calendar}: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",o=>{o.target===this.overlay&&this.handleCancel()});const i=o=>{o.key==="Escape"&&this.handleCancel()};document.addEventListener("keydown",i);const l=document.createElement("div");Object.assign(l.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 s=document.createElement("div");s.setAttribute("role","dialog"),s.setAttribute("aria-modal","true"),s.setAttribute("aria-label","Calendar sync modal"),s.tabIndex=-1,Object.assign(s.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"}),s.appendChild(r),s.appendChild(this.iframe);const a=document.createElement("div");Object.assign(a.style,{display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",padding:"12px 0"});const m=document.createElement("span");m.textContent="Powered by",Object.assign(m.style,{fontFamily:"Inter, system-ui, -apple-system, sans-serif",fontSize:"12px",lineHeight:"16px",color:"#ffffff"});const d=document.createElement("img");d.alt="Moment",d.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(d.style,{height:"auto",width:"67px"}),a.appendChild(m),a.appendChild(d),l.appendChild(s),l.appendChild(a),this.overlay.appendChild(l),document.body.appendChild(this.overlay);const g=o=>{if(o.key!=="Tab")return;const c=s.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(c.length===0){o.preventDefault(),s.focus();return}const h=c[0],u=c[c.length-1],p=document.activeElement;o.shiftKey&&p===h?(o.preventDefault(),u.focus()):!o.shiftKey&&p===u&&(o.preventDefault(),h.focus())};s.addEventListener("keydown",g),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=g}buildIframeUrl(e){return x(this.liveBaseUrl,e)}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 l=Math.round(window.innerHeight*.8);this.iframe.style.height=`${Math.min(i,l)}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(n){const e=document.createElement("button");return e.type="button",e.classList.add("moment-sync-trigger"),n.className&&n.className.split(" ").forEach(t=>{t&&e.classList.add(t)}),e.setAttribute("data-moment-team-slug",n.teamSlug),n.momentSlug&&e.setAttribute("data-moment-slug",n.momentSlug),n.listSlug&&e.setAttribute("data-moment-list-slug",n.listSlug),n.triggerType&&e.setAttribute("data-moment-trigger-type",n.triggerType),n.ids?.length&&e.setAttribute("data-moment-ids",n.ids.join(",")),n.playlistSlugs?.length&&e.setAttribute("data-moment-playlist-slugs",n.playlistSlugs.join(",")),n.calendar&&e.setAttribute("data-moment-calendar",n.calendar),n.apiSourceId&&e.setAttribute("data-api-source-id",n.apiSourceId),n.externalEventId&&e.setAttribute("data-external-event-id",n.externalEventId),n.externalEventIdPrefix&&e.setAttribute("data-external-event-id-prefix",n.externalEventIdPrefix),e.textContent=n.label??"Add to Calendar",e}if(typeof window<"u"){const n=window;n.MomentSdk=b,n.createMomentButton=y}f.MomentSdk=b,f.createMomentButton=y,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|
|
25
|
+
`;function E(){if(typeof document>"u"||document.getElementById(b))return;const n=document.createElement("style");n.id=b,n.textContent=T,document.head.appendChild(n)}const I=".moment-sync-trigger",P="https://live.dev.momentco.ai";class y{opts;liveBaseUrl;liveOrigin;overlay=null;iframe=null;wrapper=null;container=null;popupBridge=null;messageHandler=null;boundClickHandlers=new Map;activePayload=null;previousFocusedElement=null;previousBodyOverflow="";didManageBodyOverflow=!1;closeTimer=null;constructor(e){this.opts={triggerSelector:I,...e},this.liveBaseUrl=P;try{this.liveOrigin=new URL(this.liveBaseUrl).origin}catch{throw new Error(`[MomentSdk] Invalid VITE_LIVE_BASE_URL: "${this.liveBaseUrl}"`)}E(),this.bindTriggers(),this.emitAnalytics("embed.init")}open(e){e.externalEventId&&e.externalEventIdPrefix&&console.warn("[MomentSdk] Both externalEventId and externalEventIdPrefix provided; using externalEventId only.");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=s=>{s.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,s=e.getAttribute("data-moment-trigger-type");let r="moment";s!=null&&s!==""&&(s==="moment"||s==="list"||s==="team"||s==="brand-playlist"||s==="playlist"||s==="schedule"?r=s:console.warn("[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",s));const h=e.getAttribute("data-moment-ids")??e.getAttribute("data-moment-game-id")??"",d=h?h.split(",").filter(Boolean):[],c=e.getAttribute("data-moment-playlist-slugs")??"",m=c?c.split(",").map(k=>k.trim()).filter(Boolean):[],a=e.getAttribute("data-moment-calendar");let l;a!=null&&a!==""&&(a==="google"||a==="outlook"?l=a:console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:",a));const p=e.getAttribute("data-api-source-id")??void 0,u=e.getAttribute("data-external-event-id")??void 0,g=e.getAttribute("data-external-event-id-prefix")??void 0;if(u&&g&&console.warn("[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."),!t){console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");return}const f=r==="team"||r==="brand-playlist"||r==="playlist"||r==="schedule";this.emitAnalytics("embed.trigger.click",{teamSlug:t,triggerType:r,momentIds:f?[]:d}),this.open({teamSlug:t,momentSlug:i,listSlug:o,triggerType:r,ids:d,playlistSlugs:m,calendar:l??void 0,apiSourceId:p,externalEventId:g&&!u?void 0:u,externalEventIdPrefix:u?void 0:g})}scopeOpenPayload(e){return e.triggerType==="schedule"?{teamSlug:e.teamSlug,triggerType:e.triggerType}:e.triggerType==="playlist"?{teamSlug:e.teamSlug,triggerType:e.triggerType,playlistSlugs:e.playlistSlugs,calendar:e.calendar}: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",transition:"border-radius 0.3s ease",overflow:"hidden",backgroundColor:"#000000",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",display:"flex",flexDirection:"column"});const s=document.createElement("div");s.setAttribute("role","dialog"),s.setAttribute("aria-modal","true"),s.setAttribute("aria-label","Calendar sync modal"),s.tabIndex=-1,Object.assign(s.style,{position:"relative",width:"100%",maxHeight:"80vh",borderRadius:"12px",transition:"border-radius 0.3s ease",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());const h=e.triggerType!=="schedule";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"}),h&&s.appendChild(r),s.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 m=document.createElement("img");m.alt="Moment",m.src=`${this.liveBaseUrl}/brand/moment-text-white.png`,Object.assign(m.style,{height:"auto",width:"67px"}),d.appendChild(c),d.appendChild(m),this.wrapper=o,this.container=s,o.appendChild(s),o.appendChild(d),this.overlay.appendChild(o),document.body.appendChild(this.overlay);const a=l=>{if(l.key!=="Tab")return;const p=s.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');if(p.length===0){l.preventDefault(),s.focus();return}const u=p[0],g=p[p.length-1],f=document.activeElement;l.shiftKey&&f===u?(l.preventDefault(),g.focus()):!l.shiftKey&&f===g&&(l.preventDefault(),u.focus())};s.addEventListener("keydown",a),h?r.focus():s.focus(),this.previousBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden",this.didManageBodyOverflow=!0,this.setupMessageListener(),this.overlay._escHandler=i,this.overlay._focusTrapHandler=a}buildIframeUrl(e){return S(this.liveBaseUrl,e)}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.theme"){const{borderRadius:i}=t;if(typeof i=="number"&&Number.isFinite(i)){const o=Math.max(0,Math.min(i,32));this.container&&(this.container.style.borderRadius=`${o}px`),this.wrapper&&(this.wrapper.style.borderRadius=o===0?"0px":`${o+4}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.close"){this.handleCancel();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 x({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"||e.result==="partial")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"),e.triggerType!=="schedule"&&(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.wrapper=null,this.container=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 w(n){const e=document.createElement("button");return e.type="button",e.classList.add("moment-sync-trigger"),n.className&&n.className.split(" ").forEach(t=>{t&&e.classList.add(t)}),e.setAttribute("data-moment-team-slug",n.teamSlug),n.momentSlug&&e.setAttribute("data-moment-slug",n.momentSlug),n.listSlug&&e.setAttribute("data-moment-list-slug",n.listSlug),n.triggerType&&e.setAttribute("data-moment-trigger-type",n.triggerType),n.ids?.length&&e.setAttribute("data-moment-ids",n.ids.join(",")),n.playlistSlugs?.length&&e.setAttribute("data-moment-playlist-slugs",n.playlistSlugs.join(",")),n.calendar&&e.setAttribute("data-moment-calendar",n.calendar),n.apiSourceId&&e.setAttribute("data-api-source-id",n.apiSourceId),n.externalEventId&&e.setAttribute("data-external-event-id",n.externalEventId),n.externalEventIdPrefix&&e.setAttribute("data-external-event-id-prefix",n.externalEventIdPrefix),e.textContent=n.label??"Add to Calendar",e}if(typeof window<"u"){const n=window;n.MomentSdk=y,n.createMomentButton=w}v.MomentSdk=y,v.createMomentButton=w,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})})(this.MomentSdk=this.MomentSdk||{});
|
package/dist/moment-sdk.mjs
CHANGED
|
@@ -12,18 +12,18 @@ class y {
|
|
|
12
12
|
*/
|
|
13
13
|
open(e) {
|
|
14
14
|
this.cleanup(), this.resultReceived = !1;
|
|
15
|
-
const t = this.buildPopupUrl(e), i = Math.round(window.screenX + (window.outerWidth - 500) / 2),
|
|
15
|
+
const t = this.buildPopupUrl(e), i = Math.round(window.screenX + (window.outerWidth - 500) / 2), o = Math.round(window.screenY + (window.outerHeight - 700) / 2);
|
|
16
16
|
if (this.popup = window.open(
|
|
17
17
|
t,
|
|
18
18
|
"moment-oauth-popup",
|
|
19
|
-
`width=500,height=700,left=${i},top=${
|
|
19
|
+
`width=500,height=700,left=${i},top=${o},menubar=no,toolbar=no,location=yes,status=no`
|
|
20
20
|
), !this.popup || this.popup.closed) {
|
|
21
21
|
this.sendStatusToIframe("popup-blocked"), this.opts.onPopupBlocked();
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
this.sendStatusToIframe("popup-opened"), this.startPolling(), this.messageHandler = (
|
|
25
|
-
if (
|
|
26
|
-
const r =
|
|
24
|
+
this.sendStatusToIframe("popup-opened"), this.startPolling(), this.messageHandler = (n) => {
|
|
25
|
+
if (n.origin !== this.opts.liveOrigin || n.source !== this.popup) return;
|
|
26
|
+
const r = n.data;
|
|
27
27
|
!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());
|
|
28
28
|
}, window.addEventListener("message", this.messageHandler);
|
|
29
29
|
}
|
|
@@ -46,7 +46,7 @@ class y {
|
|
|
46
46
|
triggerType: e.triggerType,
|
|
47
47
|
returnOrigin: t
|
|
48
48
|
});
|
|
49
|
-
return e.subscriptionType && i.set("subscriptionType", e.subscriptionType), e.
|
|
49
|
+
return e.subscriptionType && i.set("subscriptionType", e.subscriptionType), e.scheduleMomentIds?.length && i.set("scheduleMomentIds", e.scheduleMomentIds.join(",")), e.schedulePlaylistIds?.length && i.set("schedulePlaylistIds", e.schedulePlaylistIds.join(",")), `${this.opts.liveBaseUrl}/en/embed/oauth-popup?${i.toString()}`;
|
|
50
50
|
}
|
|
51
51
|
startPolling() {
|
|
52
52
|
this.pollTimer = setInterval(() => {
|
|
@@ -63,11 +63,15 @@ class y {
|
|
|
63
63
|
this.opts.iframeWindow.postMessage(t, this.opts.liveOrigin);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
function w(
|
|
66
|
+
function w(s, e, t = typeof window < "u" ? window.location.origin : "") {
|
|
67
|
+
if (e.triggerType === "schedule") {
|
|
68
|
+
const o = new URLSearchParams();
|
|
69
|
+
return o.set("teamSlug", e.teamSlug), o.set("returnOrigin", t), o.set("embed", "1"), `${s}/en/embed/schedule?${o.toString()}`;
|
|
70
|
+
}
|
|
67
71
|
const i = new URLSearchParams();
|
|
68
|
-
return i.set("teamSlug", e.teamSlug), e.momentSlug && i.set("momentSlug", e.momentSlug), e.listSlug && i.set("listSlug", e.listSlug), i.set("triggerType", e.triggerType), e.ids?.length && i.set("ids", e.ids.join(",")), e.playlistSlugs?.length && i.set("playlistSlugs", e.playlistSlugs.join(",")), e.calendar && i.set("calendar", e.calendar), e.apiSourceId && i.set("subscriptionId", e.apiSourceId), e.externalEventId && i.set("externalId", e.externalEventId), e.externalEventIdPrefix && !e.externalEventId && i.set("externalIdPrefix", e.externalEventIdPrefix), i.set("returnOrigin", t), i.set("embed", "1"), `${
|
|
72
|
+
return i.set("teamSlug", e.teamSlug), e.momentSlug && i.set("momentSlug", e.momentSlug), e.listSlug && i.set("listSlug", e.listSlug), i.set("triggerType", e.triggerType), e.ids?.length && i.set("ids", e.ids.join(",")), e.playlistSlugs?.length && i.set("playlistSlugs", e.playlistSlugs.join(",")), e.calendar && i.set("calendar", e.calendar), e.apiSourceId && i.set("subscriptionId", e.apiSourceId), e.externalEventId && i.set("externalId", e.externalEventId), e.externalEventIdPrefix && !e.externalEventId && i.set("externalIdPrefix", e.externalEventIdPrefix), i.set("returnOrigin", t), i.set("embed", "1"), `${s}/en/embed/sync?${i.toString()}`;
|
|
69
73
|
}
|
|
70
|
-
const
|
|
74
|
+
const v = "moment-sdk-trigger-styles", x = `
|
|
71
75
|
:where(.moment-sync-trigger) {
|
|
72
76
|
background-color: #1c1917;
|
|
73
77
|
color: #ffffff;
|
|
@@ -93,17 +97,21 @@ const f = "moment-sdk-trigger-styles", x = `
|
|
|
93
97
|
}
|
|
94
98
|
`;
|
|
95
99
|
function T() {
|
|
96
|
-
if (typeof document > "u" || document.getElementById(
|
|
97
|
-
const
|
|
98
|
-
|
|
100
|
+
if (typeof document > "u" || document.getElementById(v)) return;
|
|
101
|
+
const s = document.createElement("style");
|
|
102
|
+
s.id = v, s.textContent = x, document.head.appendChild(s);
|
|
99
103
|
}
|
|
100
|
-
const
|
|
104
|
+
const S = ".moment-sync-trigger", E = "https://live.dev.momentco.ai";
|
|
101
105
|
class I {
|
|
102
106
|
opts;
|
|
103
107
|
liveBaseUrl;
|
|
104
108
|
liveOrigin;
|
|
105
109
|
overlay = null;
|
|
106
110
|
iframe = null;
|
|
111
|
+
// Outer black frame + inner white card — kept so a `moment.embed.theme` hint
|
|
112
|
+
// from the iframe can round them to match the in-iframe surfaces.
|
|
113
|
+
wrapper = null;
|
|
114
|
+
container = null;
|
|
107
115
|
popupBridge = null;
|
|
108
116
|
messageHandler = null;
|
|
109
117
|
boundClickHandlers = /* @__PURE__ */ new Map();
|
|
@@ -114,9 +122,9 @@ class I {
|
|
|
114
122
|
closeTimer = null;
|
|
115
123
|
constructor(e) {
|
|
116
124
|
this.opts = {
|
|
117
|
-
triggerSelector:
|
|
125
|
+
triggerSelector: S,
|
|
118
126
|
...e
|
|
119
|
-
}, this.liveBaseUrl =
|
|
127
|
+
}, this.liveBaseUrl = E;
|
|
120
128
|
try {
|
|
121
129
|
this.liveOrigin = new URL(this.liveBaseUrl).origin;
|
|
122
130
|
} catch {
|
|
@@ -158,10 +166,10 @@ class I {
|
|
|
158
166
|
// ── Private: trigger binding ──────────────────────────────────────
|
|
159
167
|
bindTriggers() {
|
|
160
168
|
document.querySelectorAll(this.opts.triggerSelector).forEach((t) => {
|
|
161
|
-
const i = (
|
|
162
|
-
|
|
163
|
-
},
|
|
164
|
-
|
|
169
|
+
const i = (n) => {
|
|
170
|
+
n.preventDefault(), this.handleTriggerClick(t);
|
|
171
|
+
}, o = this.boundClickHandlers.get(t);
|
|
172
|
+
o && t.removeEventListener("click", o), t.addEventListener("click", i), this.boundClickHandlers.set(t, i);
|
|
165
173
|
});
|
|
166
174
|
}
|
|
167
175
|
unbindTriggers() {
|
|
@@ -170,43 +178,46 @@ class I {
|
|
|
170
178
|
}), this.boundClickHandlers.clear();
|
|
171
179
|
}
|
|
172
180
|
handleTriggerClick(e) {
|
|
173
|
-
const t = e.getAttribute("data-moment-team-slug") ?? "", i = e.getAttribute("data-moment-slug") ?? void 0,
|
|
181
|
+
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");
|
|
174
182
|
let r = "moment";
|
|
175
|
-
|
|
183
|
+
n != null && n !== "" && (n === "moment" || n === "list" || n === "team" || n === "brand-playlist" || n === "playlist" || n === "schedule" ? r = n : console.warn(
|
|
176
184
|
"[MomentSdk] Invalid data-moment-trigger-type, falling back to 'moment':",
|
|
177
|
-
|
|
185
|
+
n
|
|
178
186
|
));
|
|
179
|
-
const
|
|
180
|
-
let
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
if (u &&
|
|
187
|
+
const h = e.getAttribute("data-moment-ids") ?? e.getAttribute("data-moment-game-id") ?? "", d = h ? h.split(",").filter(Boolean) : [], c = e.getAttribute("data-moment-playlist-slugs") ?? "", m = c ? c.split(",").map((b) => b.trim()).filter(Boolean) : [], a = e.getAttribute("data-moment-calendar");
|
|
188
|
+
let l;
|
|
189
|
+
a != null && a !== "" && (a === "google" || a === "outlook" ? l = a : console.warn("[MomentSdk] Invalid data-moment-calendar, ignoring value:", a));
|
|
190
|
+
const p = e.getAttribute("data-api-source-id") ?? void 0, u = e.getAttribute("data-external-event-id") ?? void 0, g = e.getAttribute("data-external-event-id-prefix") ?? void 0;
|
|
191
|
+
if (u && g && console.warn(
|
|
184
192
|
"[MomentSdk] Both data-external-event-id and data-external-event-id-prefix are set; using data-external-event-id only."
|
|
185
193
|
), !t) {
|
|
186
194
|
console.warn("[MomentSdk] Missing data-moment-team-slug on trigger element");
|
|
187
195
|
return;
|
|
188
196
|
}
|
|
189
|
-
const
|
|
197
|
+
const f = r === "team" || r === "brand-playlist" || r === "playlist" || r === "schedule";
|
|
190
198
|
this.emitAnalytics("embed.trigger.click", {
|
|
191
199
|
teamSlug: t,
|
|
192
200
|
triggerType: r,
|
|
193
|
-
momentIds:
|
|
201
|
+
momentIds: f ? [] : d
|
|
194
202
|
}), this.open({
|
|
195
203
|
teamSlug: t,
|
|
196
204
|
momentSlug: i,
|
|
197
|
-
listSlug:
|
|
205
|
+
listSlug: o,
|
|
198
206
|
triggerType: r,
|
|
199
|
-
ids:
|
|
200
|
-
playlistSlugs:
|
|
201
|
-
calendar:
|
|
202
|
-
apiSourceId:
|
|
203
|
-
externalEventId:
|
|
204
|
-
externalEventIdPrefix: u ? void 0 :
|
|
207
|
+
ids: d,
|
|
208
|
+
playlistSlugs: m,
|
|
209
|
+
calendar: l ?? void 0,
|
|
210
|
+
apiSourceId: p,
|
|
211
|
+
externalEventId: g && !u ? void 0 : u,
|
|
212
|
+
externalEventIdPrefix: u ? void 0 : g
|
|
205
213
|
});
|
|
206
214
|
}
|
|
207
215
|
/** Drop moment/list/source scoping for brand-level subscription triggers. */
|
|
208
216
|
scopeOpenPayload(e) {
|
|
209
|
-
return e.triggerType === "
|
|
217
|
+
return e.triggerType === "schedule" ? {
|
|
218
|
+
teamSlug: e.teamSlug,
|
|
219
|
+
triggerType: e.triggerType
|
|
220
|
+
} : e.triggerType === "playlist" ? {
|
|
210
221
|
teamSlug: e.teamSlug,
|
|
211
222
|
triggerType: e.triggerType,
|
|
212
223
|
playlistSlugs: e.playlistSlugs,
|
|
@@ -231,31 +242,36 @@ class I {
|
|
|
231
242
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
232
243
|
backdropFilter: "blur(2px)",
|
|
233
244
|
padding: "16px"
|
|
234
|
-
}), this.overlay.addEventListener("click", (
|
|
235
|
-
|
|
245
|
+
}), this.overlay.addEventListener("click", (l) => {
|
|
246
|
+
l.target === this.overlay && this.handleCancel();
|
|
236
247
|
});
|
|
237
|
-
const i = (
|
|
238
|
-
|
|
248
|
+
const i = (l) => {
|
|
249
|
+
l.key === "Escape" && this.handleCancel();
|
|
239
250
|
};
|
|
240
251
|
document.addEventListener("keydown", i);
|
|
241
|
-
const
|
|
242
|
-
Object.assign(
|
|
252
|
+
const o = document.createElement("div");
|
|
253
|
+
Object.assign(o.style, {
|
|
243
254
|
position: "relative",
|
|
244
255
|
width: "100%",
|
|
245
256
|
maxWidth: "380px",
|
|
246
257
|
borderRadius: "16px",
|
|
258
|
+
// Ease radius changes so a brand's configured radius (arriving via
|
|
259
|
+
// `moment.embed.theme` after the iframe resolves its config) settles
|
|
260
|
+
// smoothly instead of snapping.
|
|
261
|
+
transition: "border-radius 0.3s ease",
|
|
247
262
|
overflow: "hidden",
|
|
248
263
|
backgroundColor: "#000000",
|
|
249
264
|
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.25)",
|
|
250
265
|
display: "flex",
|
|
251
266
|
flexDirection: "column"
|
|
252
267
|
});
|
|
253
|
-
const
|
|
254
|
-
|
|
268
|
+
const n = document.createElement("div");
|
|
269
|
+
n.setAttribute("role", "dialog"), n.setAttribute("aria-modal", "true"), n.setAttribute("aria-label", "Calendar sync modal"), n.tabIndex = -1, Object.assign(n.style, {
|
|
255
270
|
position: "relative",
|
|
256
271
|
width: "100%",
|
|
257
272
|
maxHeight: "80vh",
|
|
258
273
|
borderRadius: "12px",
|
|
274
|
+
transition: "border-radius 0.3s ease",
|
|
259
275
|
overflow: "auto",
|
|
260
276
|
backgroundColor: "#ffffff"
|
|
261
277
|
});
|
|
@@ -279,47 +295,49 @@ class I {
|
|
|
279
295
|
r.style.color = "#666";
|
|
280
296
|
}), r.addEventListener("mouseleave", () => {
|
|
281
297
|
r.style.color = "#999";
|
|
282
|
-
}), r.addEventListener("click", () => this.handleCancel())
|
|
298
|
+
}), r.addEventListener("click", () => this.handleCancel());
|
|
299
|
+
const h = e.triggerType !== "schedule";
|
|
300
|
+
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, {
|
|
283
301
|
width: "100%",
|
|
284
302
|
border: "none",
|
|
285
303
|
display: "block",
|
|
286
304
|
height: "420px",
|
|
287
305
|
overflow: "hidden",
|
|
288
306
|
transition: "height 0.15s ease"
|
|
289
|
-
}),
|
|
290
|
-
const
|
|
291
|
-
Object.assign(
|
|
307
|
+
}), h && n.appendChild(r), n.appendChild(this.iframe);
|
|
308
|
+
const d = document.createElement("div");
|
|
309
|
+
Object.assign(d.style, {
|
|
292
310
|
display: "flex",
|
|
293
311
|
alignItems: "center",
|
|
294
312
|
justifyContent: "center",
|
|
295
313
|
gap: "6px",
|
|
296
314
|
padding: "12px 0"
|
|
297
315
|
});
|
|
298
|
-
const
|
|
299
|
-
|
|
316
|
+
const c = document.createElement("span");
|
|
317
|
+
c.textContent = "Powered by", Object.assign(c.style, {
|
|
300
318
|
fontFamily: "Inter, system-ui, -apple-system, sans-serif",
|
|
301
319
|
fontSize: "12px",
|
|
302
320
|
lineHeight: "16px",
|
|
303
321
|
color: "#ffffff"
|
|
304
322
|
});
|
|
305
|
-
const
|
|
306
|
-
|
|
323
|
+
const m = document.createElement("img");
|
|
324
|
+
m.alt = "Moment", m.src = `${this.liveBaseUrl}/brand/moment-text-white.png`, Object.assign(m.style, {
|
|
307
325
|
height: "auto",
|
|
308
326
|
width: "67px"
|
|
309
|
-
}),
|
|
310
|
-
const
|
|
311
|
-
if (
|
|
312
|
-
const
|
|
327
|
+
}), d.appendChild(c), d.appendChild(m), this.wrapper = o, this.container = n, o.appendChild(n), o.appendChild(d), this.overlay.appendChild(o), document.body.appendChild(this.overlay);
|
|
328
|
+
const a = (l) => {
|
|
329
|
+
if (l.key !== "Tab") return;
|
|
330
|
+
const p = n.querySelectorAll(
|
|
313
331
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
314
332
|
);
|
|
315
|
-
if (
|
|
316
|
-
|
|
333
|
+
if (p.length === 0) {
|
|
334
|
+
l.preventDefault(), n.focus();
|
|
317
335
|
return;
|
|
318
336
|
}
|
|
319
|
-
const
|
|
320
|
-
|
|
337
|
+
const u = p[0], g = p[p.length - 1], f = document.activeElement;
|
|
338
|
+
l.shiftKey && f === u ? (l.preventDefault(), g.focus()) : !l.shiftKey && f === g && (l.preventDefault(), u.focus());
|
|
321
339
|
};
|
|
322
|
-
|
|
340
|
+
n.addEventListener("keydown", a), h ? r.focus() : n.focus(), this.previousBodyOverflow = document.body.style.overflow, document.body.style.overflow = "hidden", this.didManageBodyOverflow = !0, this.setupMessageListener(), this.overlay._escHandler = i, this.overlay._focusTrapHandler = a;
|
|
323
341
|
}
|
|
324
342
|
buildIframeUrl(e) {
|
|
325
343
|
return w(this.liveBaseUrl, e);
|
|
@@ -341,8 +359,16 @@ class I {
|
|
|
341
359
|
if (t.source === "moment-live-embed" && t.type === "moment.embed.resize") {
|
|
342
360
|
const i = typeof t.height == "number" ? t.height : 0;
|
|
343
361
|
if (i > 0 && this.iframe) {
|
|
344
|
-
const
|
|
345
|
-
this.iframe.style.height = `${Math.min(i,
|
|
362
|
+
const o = Math.round(window.innerHeight * 0.8);
|
|
363
|
+
this.iframe.style.height = `${Math.min(i, o)}px`;
|
|
364
|
+
}
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (t.source === "moment-live-embed" && t.type === "moment.embed.theme") {
|
|
368
|
+
const { borderRadius: i } = t;
|
|
369
|
+
if (typeof i == "number" && Number.isFinite(i)) {
|
|
370
|
+
const o = Math.max(0, Math.min(i, 32));
|
|
371
|
+
this.container && (this.container.style.borderRadius = `${o}px`), this.wrapper && (this.wrapper.style.borderRadius = o === 0 ? "0px" : `${o + 4}px`);
|
|
346
372
|
}
|
|
347
373
|
return;
|
|
348
374
|
}
|
|
@@ -351,6 +377,10 @@ class I {
|
|
|
351
377
|
i && typeof i == "string" && i.startsWith("webcal://") && window.open(i, "_blank");
|
|
352
378
|
return;
|
|
353
379
|
}
|
|
380
|
+
if (t.source === "moment-live-embed" && t.type === "moment.embed.close") {
|
|
381
|
+
this.handleCancel();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
354
384
|
if (t.source === "moment-live-embed" && t.type === "moment.embed.result") {
|
|
355
385
|
const i = t;
|
|
356
386
|
this.handleResult(i);
|
|
@@ -379,7 +409,7 @@ class I {
|
|
|
379
409
|
}), this.popupBridge.open(e);
|
|
380
410
|
}
|
|
381
411
|
handleResult(e) {
|
|
382
|
-
if (e.result === "success")
|
|
412
|
+
if (e.result === "success" || e.result === "partial")
|
|
383
413
|
this.emitAnalytics("subscribe.success", {
|
|
384
414
|
provider: e.provider,
|
|
385
415
|
triggerType: e.triggerType,
|
|
@@ -393,7 +423,7 @@ class I {
|
|
|
393
423
|
}), this.opts.onError?.(e);
|
|
394
424
|
else if (e.result === "cancelled")
|
|
395
425
|
return;
|
|
396
|
-
this.opts.onClose?.(e), this.emitAnalytics("embed.close"), this.closeTimer = setTimeout(() => this.cleanup(), 1500);
|
|
426
|
+
this.opts.onClose?.(e), this.emitAnalytics("embed.close"), e.triggerType !== "schedule" && (this.closeTimer = setTimeout(() => this.cleanup(), 1500));
|
|
397
427
|
}
|
|
398
428
|
handleCancel() {
|
|
399
429
|
this.emitAnalytics("embed.close");
|
|
@@ -414,7 +444,7 @@ class I {
|
|
|
414
444
|
const i = this.overlay.firstElementChild;
|
|
415
445
|
i && t && i.removeEventListener("keydown", t), this.overlay.remove(), this.overlay = null;
|
|
416
446
|
}
|
|
417
|
-
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;
|
|
447
|
+
this.iframe = null, this.wrapper = null, this.container = 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;
|
|
418
448
|
}
|
|
419
449
|
// ── Private: analytics ────────────────────────────────────────────
|
|
420
450
|
emitAnalytics(e, t) {
|
|
@@ -425,15 +455,15 @@ class I {
|
|
|
425
455
|
});
|
|
426
456
|
}
|
|
427
457
|
}
|
|
428
|
-
function P(
|
|
458
|
+
function P(s) {
|
|
429
459
|
const e = document.createElement("button");
|
|
430
|
-
return e.type = "button", e.classList.add("moment-sync-trigger"),
|
|
460
|
+
return e.type = "button", e.classList.add("moment-sync-trigger"), s.className && s.className.split(" ").forEach((t) => {
|
|
431
461
|
t && e.classList.add(t);
|
|
432
|
-
}), e.setAttribute("data-moment-team-slug",
|
|
462
|
+
}), 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.playlistSlugs?.length && e.setAttribute("data-moment-playlist-slugs", s.playlistSlugs.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), s.externalEventIdPrefix && e.setAttribute("data-external-event-id-prefix", s.externalEventIdPrefix), e.textContent = s.label ?? "Add to Calendar", e;
|
|
433
463
|
}
|
|
434
464
|
if (typeof window < "u") {
|
|
435
|
-
const
|
|
436
|
-
|
|
465
|
+
const s = window;
|
|
466
|
+
s.MomentSdk = I, s.createMomentButton = P;
|
|
437
467
|
}
|
|
438
468
|
export {
|
|
439
469
|
I as MomentSdk,
|
|
@@ -10,10 +10,7 @@ export declare function createMomentButton(options: {
|
|
|
10
10
|
listSlug?: string;
|
|
11
11
|
triggerType?: MomentTriggerType;
|
|
12
12
|
ids?: string[];
|
|
13
|
-
/**
|
|
14
|
-
* Playlist slugs offered under a `playlist` trigger (picker when >1) or the
|
|
15
|
-
* collections shown in the `schedule` trigger's multi-select modal.
|
|
16
|
-
*/
|
|
13
|
+
/** Playlist slugs offered under a `playlist` trigger (picker when >1). */
|
|
17
14
|
playlistSlugs?: string[];
|
|
18
15
|
calendar?: CalendarProvider;
|
|
19
16
|
/** Sync subscription source id; pairs with `externalEventId` for server-side resolution. */
|
|
@@ -9,12 +9,18 @@
|
|
|
9
9
|
* - `playlist` → subscribe to one of several specific playlists. When more than
|
|
10
10
|
* one slug is supplied, Live fetches each playlist and prompts the user to
|
|
11
11
|
* pick which one to sync.
|
|
12
|
-
* - `schedule` → open the brand's schedule
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* - `schedule` → open the brand's full schedule experience: a multi-select
|
|
13
|
+
* surface where the fan picks any mix of the brand's collections (playlists)
|
|
14
|
+
* and individual events (moments) and syncs them all at once.
|
|
15
15
|
*/
|
|
16
16
|
export type MomentTriggerType = 'moment' | 'list' | 'team' | 'brand-playlist' | 'playlist' | 'schedule';
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Calendar providers. `google` / `outlook` are the OAuth providers a partner may
|
|
19
|
+
* pre-select via `data-moment-calendar`. `apple` is never a hint — it only ever
|
|
20
|
+
* appears on a result payload's `provider` (guest webcal path), so consumers
|
|
21
|
+
* switching on `provider` must tolerate it.
|
|
22
|
+
*/
|
|
23
|
+
export type CalendarProvider = 'google' | 'outlook' | 'apple';
|
|
18
24
|
export interface MomentSdkInitOptions {
|
|
19
25
|
/** CSS selector for trigger elements. Default: ".moment-sync-trigger" */
|
|
20
26
|
triggerSelector?: string;
|
|
@@ -45,10 +51,8 @@ export interface MomentSdkInitOptions {
|
|
|
45
51
|
* With a single slug Live syncs that playlist directly; with several it shows
|
|
46
52
|
* a picker. Omit `momentSlug`, `listSlug`, `ids`, `apiSourceId`, and
|
|
47
53
|
* `externalEventId` for this trigger type.
|
|
48
|
-
* - `schedule` → `teamSlug`
|
|
49
|
-
*
|
|
50
|
-
* syncs them together. Omit `momentSlug`, `listSlug`, `ids`, `apiSourceId`,
|
|
51
|
-
* and `externalEventId` for this trigger type.
|
|
54
|
+
* - `schedule` → `teamSlug` only. Opens the brand's full schedule experience.
|
|
55
|
+
* Omit every other field (including `calendar`, which is ignored here).
|
|
52
56
|
*/
|
|
53
57
|
export interface MomentSdkOpenPayload {
|
|
54
58
|
teamSlug: string;
|
|
@@ -58,11 +62,10 @@ export interface MomentSdkOpenPayload {
|
|
|
58
62
|
triggerType: MomentTriggerType;
|
|
59
63
|
ids?: string[];
|
|
60
64
|
/**
|
|
61
|
-
* Playlist slugs to offer under a `playlist`
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* as the `playlistSlugs` query param (comma-separated).
|
|
65
|
+
* Playlist slugs to offer under a `playlist` trigger. Live fetches each
|
|
66
|
+
* playlist's details; with more than one slug it renders a picker so the user
|
|
67
|
+
* can choose which playlist to sync. Forwarded to the Live embed as the
|
|
68
|
+
* `playlistSlugs` query param (comma-separated).
|
|
66
69
|
*/
|
|
67
70
|
playlistSlugs?: string[];
|
|
68
71
|
calendar?: CalendarProvider;
|
|
@@ -107,11 +110,13 @@ export interface MomentOAuthStartPayload {
|
|
|
107
110
|
*/
|
|
108
111
|
subscriptionType?: MomentSubscriptionType;
|
|
109
112
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
+
* Present for the `schedule` trigger only: the mixed selection's moment ids.
|
|
114
|
+
* Carried in the popup URL (not localStorage) so the handoff survives browser
|
|
115
|
+
* third-party storage partitioning, matching the single-resource `ids` flow.
|
|
113
116
|
*/
|
|
114
|
-
|
|
117
|
+
scheduleMomentIds?: string[];
|
|
118
|
+
/** Present for the `schedule` trigger only: the mixed selection's playlist ids. */
|
|
119
|
+
schedulePlaylistIds?: string[];
|
|
115
120
|
returnOrigin: string;
|
|
116
121
|
}
|
|
117
122
|
/** Parent → Iframe: relay OAuth popup status */
|
|
@@ -124,10 +129,25 @@ export interface MomentOAuthStatusPayload {
|
|
|
124
129
|
export interface MomentResultPayload {
|
|
125
130
|
source: 'moment-live-embed';
|
|
126
131
|
type: 'moment.embed.result';
|
|
127
|
-
|
|
132
|
+
/**
|
|
133
|
+
* `partial` is emitted only by the `schedule` trigger when some — but not all —
|
|
134
|
+
* of a mixed selection subscribed successfully. Inspect `counts` for detail.
|
|
135
|
+
*/
|
|
136
|
+
result: 'success' | 'error' | 'cancelled' | 'partial';
|
|
128
137
|
provider?: CalendarProvider;
|
|
129
138
|
triggerType: MomentTriggerType;
|
|
130
139
|
momentIds: string[];
|
|
140
|
+
/**
|
|
141
|
+
* Aggregate outcome for multi-resource syncs (schedule trigger). `moments` and
|
|
142
|
+
* `playlists` break down the successful count by resource type.
|
|
143
|
+
*/
|
|
144
|
+
counts?: {
|
|
145
|
+
requested: number;
|
|
146
|
+
succeeded: number;
|
|
147
|
+
failed: number;
|
|
148
|
+
moments?: number;
|
|
149
|
+
playlists?: number;
|
|
150
|
+
};
|
|
131
151
|
message?: string;
|
|
132
152
|
}
|
|
133
153
|
/**
|
|
@@ -139,6 +159,30 @@ export interface MomentOpenUrlPayload {
|
|
|
139
159
|
type: 'moment.embed.open.url';
|
|
140
160
|
url: string;
|
|
141
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Iframe → Parent: request to close the modal.
|
|
164
|
+
* Used by interactive embeds (e.g. the `schedule` trigger) whose in-iframe
|
|
165
|
+
* controls — a header close, a "Done" on the success screen — must dismiss the
|
|
166
|
+
* modal. A `cancelled` result is deliberately NOT enough (the SDK keeps the
|
|
167
|
+
* modal open on cancel so the iframe can show retry UI), so this is explicit.
|
|
168
|
+
*/
|
|
169
|
+
export interface MomentClosePayload {
|
|
170
|
+
source: 'moment-live-embed';
|
|
171
|
+
type: 'moment.embed.close';
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Iframe → Parent: forward the brand's resolved surface radius so the SDK can
|
|
175
|
+
* round the modal frame (wrapper + container) to match the in-iframe surfaces.
|
|
176
|
+
* Emitted only by the `schedule` experience, and only when the brand configures
|
|
177
|
+
* a radius. The SDK does NOT fetch config itself — the iframe already resolved
|
|
178
|
+
* it from the SDK config; this is purely a style hint over the trusted channel.
|
|
179
|
+
*/
|
|
180
|
+
export interface MomentThemePayload {
|
|
181
|
+
source: 'moment-live-embed';
|
|
182
|
+
type: 'moment.embed.theme';
|
|
183
|
+
/** Border radius in px for the modal frame. */
|
|
184
|
+
borderRadius: number;
|
|
185
|
+
}
|
|
142
186
|
export type MomentAnalyticsEventName = 'embed.init' | 'embed.trigger.click' | 'embed.open' | 'oauth.start' | 'oauth.popup.blocked' | 'oauth.popup.closed' | 'subscribe.success' | 'subscribe.error' | 'embed.close';
|
|
143
187
|
export interface MomentAnalyticsEvent {
|
|
144
188
|
event: MomentAnalyticsEventName;
|
|
@@ -148,4 +192,4 @@ export interface MomentAnalyticsEvent {
|
|
|
148
192
|
momentIds?: string[];
|
|
149
193
|
timestamp: number;
|
|
150
194
|
}
|
|
151
|
-
export type MomentPostMessage = MomentOAuthStartPayload | MomentOAuthStatusPayload | MomentResultPayload | MomentOpenUrlPayload;
|
|
195
|
+
export type MomentPostMessage = MomentOAuthStartPayload | MomentOAuthStatusPayload | MomentResultPayload | MomentOpenUrlPayload | MomentClosePayload | MomentThemePayload;
|
package/examples/README.md
CHANGED
|
@@ -23,6 +23,7 @@ Opens **http://localhost:5051** with:
|
|
|
23
23
|
| `/moment/` | `moment` |
|
|
24
24
|
| `/collection/` | `list` |
|
|
25
25
|
| `/brand-schedule/` | `brand-playlist` |
|
|
26
|
+
| `/schedule/` | `schedule` |
|
|
26
27
|
|
|
27
28
|
Demo slugs are in `shared/fixtures.js`. Edit that file if your account uses different values.
|
|
28
29
|
|
package/examples/index.html
CHANGED
|
@@ -29,6 +29,10 @@
|
|
|
29
29
|
<h2>Brand schedule</h2>
|
|
30
30
|
<p>Follow the full schedule with <code>triggerType: brand-playlist</code>.</p>
|
|
31
31
|
</a>
|
|
32
|
+
<a class="hub-card" href="/schedule/">
|
|
33
|
+
<h2>Schedule</h2>
|
|
34
|
+
<p>Multi-select collections + events with <code>triggerType: schedule</code>.</p>
|
|
35
|
+
</a>
|
|
32
36
|
</div>
|
|
33
37
|
|
|
34
38
|
<div id="demo-log-host"></div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Moment SDK — Schedule trigger</title>
|
|
7
|
+
<link rel="stylesheet" href="/shared/demo.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main class="demo-shell">
|
|
11
|
+
<div id="demo-nav" class="demo-top"></div>
|
|
12
|
+
|
|
13
|
+
<h1>Schedule trigger</h1>
|
|
14
|
+
<p class="lead">
|
|
15
|
+
The full brand schedule experience. The fan picks any mix of the brand's collections
|
|
16
|
+
(playlists) and individual events, then syncs them all at once. Only
|
|
17
|
+
<code>data-moment-team-slug</code> and <code>data-moment-trigger-type="schedule"</code>
|
|
18
|
+
are required.
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<article class="hero-card">
|
|
22
|
+
<div class="card-info">
|
|
23
|
+
<div class="card-title">Customize your sdk-demo schedule</div>
|
|
24
|
+
<div class="card-meta">Team slug <code id="team-slug-label"></code></div>
|
|
25
|
+
</div>
|
|
26
|
+
<button type="button" id="schedule-trigger" class="moment-sync-trigger demo-btn">
|
|
27
|
+
View Schedule
|
|
28
|
+
</button>
|
|
29
|
+
</article>
|
|
30
|
+
|
|
31
|
+
<section class="section">
|
|
32
|
+
<h2>Embed snippet</h2>
|
|
33
|
+
<div class="snippet">
|
|
34
|
+
<button class="copy-btn" type="button" data-copy-target="snippet-code">Copy</button>
|
|
35
|
+
<pre id="snippet-code">Loading snippet…</pre>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
|
|
39
|
+
<div id="demo-log-host"></div>
|
|
40
|
+
</main>
|
|
41
|
+
<script type="module" src="/schedule/main.js"></script>
|
|
42
|
+
</body>
|
|
43
|
+
</html>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DEMO_FIXTURES } from '../shared/fixtures.js';
|
|
2
|
+
import { embedSnippetHtml, initDemoSdk, mountLogPanel, mountNav } from '../shared/boot.js';
|
|
3
|
+
|
|
4
|
+
mountNav({ title: 'Schedule' });
|
|
5
|
+
mountLogPanel();
|
|
6
|
+
|
|
7
|
+
// Allow ?teamSlug=<brand> to point the demo at any brand on the connected
|
|
8
|
+
// backend (handy for local testing); falls back to the sdk-demo fixture.
|
|
9
|
+
const teamSlug =
|
|
10
|
+
new URLSearchParams(window.location.search).get('teamSlug') || DEMO_FIXTURES.teamSlug;
|
|
11
|
+
|
|
12
|
+
document.getElementById('team-slug-label').textContent = teamSlug;
|
|
13
|
+
|
|
14
|
+
const trigger = document.getElementById('schedule-trigger');
|
|
15
|
+
trigger.setAttribute('data-moment-team-slug', teamSlug);
|
|
16
|
+
trigger.setAttribute('data-moment-trigger-type', 'schedule');
|
|
17
|
+
|
|
18
|
+
document.getElementById('snippet-code').textContent = await embedSnippetHtml({
|
|
19
|
+
triggerType: 'schedule',
|
|
20
|
+
label: 'View Schedule',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
await initDemoSdk();
|
package/examples/serve.mjs
CHANGED
|
@@ -26,6 +26,8 @@ const ROUTES = {
|
|
|
26
26
|
'/collection/': 'collection/index.html',
|
|
27
27
|
'/brand-schedule': 'brand-schedule/index.html',
|
|
28
28
|
'/brand-schedule/': 'brand-schedule/index.html',
|
|
29
|
+
'/schedule': 'schedule/index.html',
|
|
30
|
+
'/schedule/': 'schedule/index.html',
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
function send(res, status, body, type = 'text/plain; charset=utf-8') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@momentco-ai/moment-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-dev.26",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Embeddable calendar sync widget for external team websites",
|
|
6
6
|
"author": "Moment Co. <support@momentco.ai> (https://momentco.ai)",
|
|
@@ -55,7 +55,6 @@
|
|
|
55
55
|
"build": "vite build && tsc --emitDeclarationOnly --outDir dist/types",
|
|
56
56
|
"build:dev": "vite build --mode development && tsc --emitDeclarationOnly --outDir dist/types",
|
|
57
57
|
"build:prod": "vite build --mode production && tsc --emitDeclarationOnly --outDir dist/types",
|
|
58
|
-
"build:local": "VITE_LIVE_BASE_URL=http://localhost:8081 vite build && tsc --emitDeclarationOnly --outDir dist/types",
|
|
59
58
|
"type-check": "tsc --noEmit",
|
|
60
59
|
"typecheck": "pnpm run type-check",
|
|
61
60
|
"lint": "eslint .",
|
|
@@ -65,8 +64,6 @@
|
|
|
65
64
|
"quality": "pnpm run type-check && pnpm run lint && pnpm run format",
|
|
66
65
|
"test": "vitest run",
|
|
67
66
|
"examples": "pnpm build && node examples/serve.mjs",
|
|
68
|
-
"integration-test": "node integration-test/serve.mjs",
|
|
69
|
-
"integration-test:local": "INTEGRATION_USE_LOCAL_SDK=1 node integration-test/serve.mjs",
|
|
70
67
|
"prepare": "husky"
|
|
71
68
|
},
|
|
72
69
|
"devDependencies": {
|