@linktr.ee/create-link-app 1.9.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,33 +28,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  const react_1 = __importStar(require("react"));
30
30
  const client_1 = require("react-dom/client");
31
+ const simulator_1 = __importDefault(require("./simulator"));
31
32
  const extension_1 = __importDefault(require("@linktr.ee/extension"));
32
33
  const extension_dev_data_1 = __importDefault(require("@linktr.ee/extension-dev-data"));
33
34
  const container = document.getElementById('root');
34
35
  const root = (0, client_1.createRoot)(container);
35
- const App = () => {
36
- // TODO: refactor common postMessage to parent in both entry points
36
+ function EmbeddedExtension() {
37
+ const wrapperRef = (0, react_1.useRef)(null);
37
38
  (0, react_1.useEffect)(() => {
38
- const message = {
39
- type: 'extension-ready',
40
- data: {
41
- ready: true,
42
- height: container.clientHeight,
43
- },
39
+ const postReady = (height) => {
40
+ const ready = { type: 'extension-ready', data: { ready: true, height } };
41
+ window.parent.postMessage(ready, '*');
42
+ };
43
+ // signal loaded first, then report initial size
44
+ const loaded = { type: 'extension-loaded', data: { loaded: true } };
45
+ window.parent.postMessage(loaded, '*');
46
+ const initialHeight = Math.ceil(wrapperRef.current?.getBoundingClientRect().height || document.documentElement.scrollHeight || container.clientHeight);
47
+ postReady(initialHeight);
48
+ const ro = new window.ResizeObserver((entries) => {
49
+ const entry = entries[0];
50
+ const h = Math.ceil(entry?.contentRect?.height || wrapperRef.current?.getBoundingClientRect().height || 0);
51
+ if (h)
52
+ postReady(h);
53
+ });
54
+ if (wrapperRef.current)
55
+ ro.observe(wrapperRef.current);
56
+ const onMessage = (event) => {
57
+ if (event.data?.type === 'extension-data') {
58
+ // eslint-disable-next-line no-console
59
+ console.info('[Extension] received extension-data', event.data?.data);
60
+ }
61
+ };
62
+ window.addEventListener('message', onMessage);
63
+ return () => {
64
+ if (wrapperRef.current) {
65
+ try {
66
+ ro.unobserve(wrapperRef.current);
67
+ }
68
+ catch (e) {
69
+ // ignore unobserve errors in dev simulator
70
+ }
71
+ }
72
+ window.removeEventListener('message', onMessage);
44
73
  };
45
- window.parent.postMessage(message, '*');
46
74
  }, []);
75
+ return (react_1.default.createElement("div", { ref: wrapperRef, style: { display: 'block' } },
76
+ react_1.default.createElement(extension_1.default, { ...extension_dev_data_1.default })));
77
+ }
78
+ const App = () => {
47
79
  if (window.location.search === '?embed') {
48
- return react_1.default.createElement(extension_1.default, { ...extension_dev_data_1.default });
49
- }
50
- else {
51
- return (react_1.default.createElement("iframe", { src: `${window.location}?embed`, sandbox: ['allow-scripts', 'allow-same-origin', 'allow-popups', 'allow-popups-to-escape-sandbox', 'allow-forms'].join(' '), scrolling: "no", frameBorder: 0, style: {
52
- display: 'block',
53
- margin: '0 auto',
54
- width: '1px',
55
- minWidth: '680px',
56
- height: '100vh',
57
- } }));
80
+ return react_1.default.createElement(EmbeddedExtension, null);
58
81
  }
82
+ return react_1.default.createElement(simulator_1.default, null);
59
83
  };
60
84
  root.render(react_1.default.createElement(App, null));
@@ -0,0 +1,373 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ const react_1 = __importStar(require("react"));
30
+ const styled_components_1 = __importStar(require("styled-components"));
31
+ const iframe_resizer_react_1 = __importDefault(require("iframe-resizer-react"));
32
+ const extension_dev_data_1 = __importDefault(require("@linktr.ee/extension-dev-data"));
33
+ const Global = (0, styled_components_1.createGlobalStyle) `
34
+ html, body, #root { height: 100%; }
35
+ body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; background: #111317; }
36
+ `;
37
+ const Page = styled_components_1.default.div `
38
+ min-height: 100vh;
39
+ display: grid;
40
+ place-items: center;
41
+ padding: 24px;
42
+ box-sizing: border-box;
43
+ `;
44
+ // Simulated Linktree mobile device frame
45
+ const Phone = styled_components_1.default.div `
46
+ width: 394px; /* close to iPhone 14 width incl padding */
47
+ max-width: 94vw;
48
+ height: 800px;
49
+ border-radius: 28px;
50
+ background: radial-gradient(120% 110% at 50% 0%, #1f2143 0%, #2b2048 40%, #345a78 100%);
51
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.06);
52
+ color: #f5e8ff;
53
+ position: relative;
54
+ overflow: hidden;
55
+ &:before {
56
+ content: '';
57
+ position: absolute;
58
+ inset: -40% -20% 30% -20%;
59
+ background: radial-gradient(60% 40% at 50% 20%, rgba(214, 163, 255, 0.45) 0%, rgba(214, 163, 255, 0.18) 35%, rgba(0, 0, 0, 0) 80%);
60
+ pointer-events: none;
61
+ }
62
+ `;
63
+ const HeaderChrome = styled_components_1.default.div `
64
+ position: absolute;
65
+ top: 12px;
66
+ left: 0;
67
+ right: 0;
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: space-between;
71
+ padding: 0 18px;
72
+ `;
73
+ const Notch = styled_components_1.default.div `
74
+ position: absolute;
75
+ top: 10px;
76
+ left: 50%;
77
+ transform: translateX(-50%);
78
+ width: 120px;
79
+ height: 22px;
80
+ background: rgba(0, 0, 0, 0.42);
81
+ border-radius: 12px;
82
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15);
83
+ `;
84
+ const RoundIcon = styled_components_1.default.div `
85
+ width: 36px;
86
+ height: 36px;
87
+ border-radius: 9999px;
88
+ display: grid;
89
+ place-items: center;
90
+ background: rgba(255, 255, 255, 0.08);
91
+ color: #e8d7ff;
92
+ font-size: 16px;
93
+ backdrop-filter: blur(2px);
94
+ `;
95
+ const ScrollArea = styled_components_1.default.div `
96
+ position: absolute;
97
+ inset: 56px 0 0;
98
+ overflow-y: auto;
99
+ padding: 24px 22px 28px;
100
+ box-sizing: border-box;
101
+ `;
102
+ const Profile = styled_components_1.default.div `
103
+ display: flex;
104
+ flex-direction: column;
105
+ align-items: center;
106
+ gap: 8px;
107
+ margin-bottom: 16px;
108
+ `;
109
+ const Avatar = styled_components_1.default.div `
110
+ width: 90px;
111
+ height: 90px;
112
+ border-radius: 9999px;
113
+ overflow: hidden;
114
+ border: 3px solid rgba(255, 255, 255, 0.25);
115
+ background: linear-gradient(135deg, #a5b4fc, #f0abfc);
116
+ display: grid;
117
+ place-items: center;
118
+ color: #1f1147;
119
+ font-weight: 800;
120
+ font-size: 28px;
121
+ `;
122
+ const Name = styled_components_1.default.div `
123
+ display: inline-flex;
124
+ align-items: center;
125
+ gap: 8px;
126
+ font-size: 28px;
127
+ font-weight: 700;
128
+ letter-spacing: 0.2px;
129
+ `;
130
+ const Badge = styled_components_1.default.span `
131
+ display: inline-flex;
132
+ width: 18px;
133
+ height: 18px;
134
+ border-radius: 50%;
135
+ background: #7dd3fc;
136
+ color: #0a2540;
137
+ font-size: 12px;
138
+ align-items: center;
139
+ justify-content: center;
140
+ `;
141
+ const Bio = styled_components_1.default.div `
142
+ font-size: 14px;
143
+ opacity: 0.9;
144
+ `;
145
+ const Card = styled_components_1.default.div `
146
+ background: rgba(255, 255, 255, 0.18);
147
+ color: #2a1c44;
148
+ border-radius: 20px;
149
+ padding: 14px 14px;
150
+ display: grid;
151
+ grid-template-columns: 36px 1fr 24px;
152
+ align-items: center;
153
+ gap: 12px;
154
+ margin: 12px 0;
155
+ backdrop-filter: blur(4px);
156
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 0 rgba(0, 0, 0, 0.15);
157
+ `;
158
+ const CardTitle = styled_components_1.default.div `
159
+ color: #2a1c44;
160
+ font-weight: 700;
161
+ font-size: 16px;
162
+ `;
163
+ const CardSub = styled_components_1.default.div `
164
+ color: #2a1c44;
165
+ opacity: 0.8;
166
+ font-size: 12px;
167
+ margin-top: 2px;
168
+ `;
169
+ const CardIcon = styled_components_1.default.div `
170
+ width: 36px;
171
+ height: 36px;
172
+ border-radius: 12px;
173
+ display: grid;
174
+ place-items: center;
175
+ background: rgba(255, 255, 255, 0.55);
176
+ color: #2a1c44;
177
+ font-size: 16px;
178
+ font-weight: 700;
179
+ `;
180
+ const Kebab = styled_components_1.default.div `
181
+ opacity: 0.8;
182
+ color: #2a1c44;
183
+ display: grid;
184
+ place-items: center;
185
+ font-size: 18px;
186
+ `;
187
+ const SocialRow = styled_components_1.default.div `
188
+ display: flex;
189
+ gap: 28px;
190
+ justify-content: center;
191
+ margin: 26px 0 12px;
192
+ opacity: 0.95;
193
+ `;
194
+ const Footer = styled_components_1.default.div `
195
+ text-align: center;
196
+ font-size: 12px;
197
+ opacity: 0.75;
198
+ color: #d8c9ec;
199
+ margin-top: 18px;
200
+ `;
201
+ // Dedicated container for extension embeds to better mirror production
202
+ const ExtensionCard = styled_components_1.default.div `
203
+ background: rgba(255, 255, 255, 0.95);
204
+ color: #0a0a0a;
205
+ border-radius: 20px;
206
+ padding: 0;
207
+ margin: 12px 0;
208
+ overflow: hidden;
209
+ position: relative;
210
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 0 rgba(0, 0, 0, 0.15);
211
+ `;
212
+ function kebab() {
213
+ return react_1.default.createElement("span", { "aria-hidden": true }, "\u22EE");
214
+ }
215
+ function Simulator() {
216
+ const [loading, setLoading] = (0, react_1.useState)(true);
217
+ const [loadFailed, setLoadFailed] = (0, react_1.useState)(false);
218
+ const iframeRef = (0, react_1.useRef)(null);
219
+ const loadTimeoutRef = (0, react_1.useRef)(null);
220
+ const APP_LOAD_TIMEOUT_MILLISECONDS = 8000;
221
+ const MAX_IFRAME_HEIGHT = 900;
222
+ const targetOrigin = (0, react_1.useMemo)(() => {
223
+ try {
224
+ return __ALLOW_ANY_ORIGIN__ ? '*' : window.location.origin;
225
+ }
226
+ catch {
227
+ return '*';
228
+ }
229
+ }, []);
230
+ const initialiseData = (0, react_1.useCallback)(() => {
231
+ if (!iframeRef.current?.contentWindow)
232
+ return;
233
+ const payload = {
234
+ type: 'extension-data',
235
+ data: {
236
+ __environment: {
237
+ RECAPTCHA_SITE_KEY: 'dev-invisible',
238
+ GRAPH_ENDPOINT: `${window.location.origin}/graphql`,
239
+ GRAPHQL_CLIENT_NAME: '@linktr.ee/create-link-app',
240
+ GRAPHQL_CLIENT_VERSION: 'dev',
241
+ COUNTRY_CODE: 'US',
242
+ PROFILE: 'dev-user',
243
+ },
244
+ __linkUrl: extension_dev_data_1.default?.url ?? '',
245
+ __thumbnail: extension_dev_data_1.default?.thumbnail ?? '',
246
+ __linkParams: null,
247
+ displayType: 'accordion',
248
+ ...extension_dev_data_1.default,
249
+ },
250
+ };
251
+ iframeRef.current.contentWindow.postMessage(payload, targetOrigin);
252
+ }, [targetOrigin]);
253
+ (0, react_1.useEffect)(() => {
254
+ const handler = (event) => {
255
+ const type = (event?.data && event.data.type) || '';
256
+ if (!type)
257
+ return;
258
+ if (iframeRef.current?.contentWindow && event.source !== iframeRef.current.contentWindow)
259
+ return;
260
+ switch (type) {
261
+ case 'extension-loaded': {
262
+ initialiseData();
263
+ break;
264
+ }
265
+ case 'extension-ready': {
266
+ setLoading(false);
267
+ try {
268
+ const node = document.getElementById('extension-iframe-sim');
269
+ (node || iframeRef.current)?.contentWindow?.focus();
270
+ }
271
+ catch { }
272
+ break;
273
+ }
274
+ case 'extension-error': {
275
+ setLoading(false);
276
+ setLoadFailed(true);
277
+ break;
278
+ }
279
+ case 'interaction-event': {
280
+ // eslint-disable-next-line no-console
281
+ console.info('[Simulator] interaction-event', event.data?.data);
282
+ break;
283
+ }
284
+ default:
285
+ break;
286
+ }
287
+ };
288
+ window.addEventListener('message', handler);
289
+ return () => window.removeEventListener('message', handler);
290
+ }, [initialiseData]);
291
+ (0, react_1.useEffect)(() => {
292
+ if (loading) {
293
+ if (loadTimeoutRef.current)
294
+ window.clearTimeout(loadTimeoutRef.current);
295
+ loadTimeoutRef.current = window.setTimeout(() => {
296
+ setLoadFailed(true);
297
+ }, APP_LOAD_TIMEOUT_MILLISECONDS);
298
+ }
299
+ return () => {
300
+ if (loadTimeoutRef.current)
301
+ window.clearTimeout(loadTimeoutRef.current);
302
+ };
303
+ }, [loading]);
304
+ const iframeSrc = (0, react_1.useMemo)(() => `${window.location}?embed`, []);
305
+ return (react_1.default.createElement(react_1.default.Fragment, null,
306
+ react_1.default.createElement(Global, null),
307
+ react_1.default.createElement(Page, null,
308
+ react_1.default.createElement(Phone, null,
309
+ react_1.default.createElement(Notch, null),
310
+ react_1.default.createElement(HeaderChrome, null,
311
+ react_1.default.createElement(RoundIcon, { title: "Settings" }, "\u2699\uFE0F"),
312
+ react_1.default.createElement(RoundIcon, { title: "Notifications" }, "\uD83D\uDD14")),
313
+ react_1.default.createElement(ScrollArea, null,
314
+ react_1.default.createElement(Profile, null,
315
+ react_1.default.createElement(Avatar, null, "SC"),
316
+ react_1.default.createElement(Name, null,
317
+ "Sample Creator ",
318
+ react_1.default.createElement(Badge, { title: "Verified" }, "\u2713")),
319
+ react_1.default.createElement(Bio, null, "Digital creator")),
320
+ react_1.default.createElement(ExtensionCard, null,
321
+ loadFailed && (react_1.default.createElement("div", { style: {
322
+ position: 'absolute',
323
+ inset: 0,
324
+ display: 'grid',
325
+ placeItems: 'center',
326
+ background: 'rgba(31,17,71,0.06)',
327
+ color: '#92400e',
328
+ zIndex: 2,
329
+ } }, "Failed to load link app")),
330
+ loading && !loadFailed && (react_1.default.createElement("div", { style: {
331
+ position: 'absolute',
332
+ inset: 0,
333
+ display: 'grid',
334
+ placeItems: 'center',
335
+ background: 'rgba(31,17,71,0.03)',
336
+ color: '#4c1d95',
337
+ zIndex: 1,
338
+ } }, "Loading\u2026")),
339
+ react_1.default.createElement(iframe_resizer_react_1.default, { id: "extension-iframe-sim", style: { height: '0px', width: '1px', minWidth: '100%' }, maxHeight: MAX_IFRAME_HEIGHT, checkOrigin: false, src: iframeSrc, onResized: () => {
340
+ /* no-op: iFrameResizer manages height */
341
+ }, allow: ['clipboard-write'].join(' '), heightCalculationMethod: "max", sandbox: [
342
+ 'allow-popups',
343
+ 'allow-popups-to-escape-sandbox',
344
+ 'allow-top-navigation',
345
+ 'allow-same-origin',
346
+ 'allow-scripts',
347
+ 'allow-forms',
348
+ ].join(' '), forwardRef: (node) => {
349
+ iframeRef.current = node;
350
+ } })),
351
+ react_1.default.createElement(Card, null,
352
+ react_1.default.createElement(CardIcon, null, "\uD83D\uDCBB"),
353
+ react_1.default.createElement("div", null,
354
+ react_1.default.createElement(CardTitle, null, "Featured link"),
355
+ react_1.default.createElement(CardSub, null, "example.com")),
356
+ react_1.default.createElement(Kebab, null, kebab())),
357
+ react_1.default.createElement(Card, null,
358
+ react_1.default.createElement(CardIcon, null, "\uD83D\uDCAC"),
359
+ react_1.default.createElement("div", null,
360
+ react_1.default.createElement(CardTitle, null, "Follow me")),
361
+ react_1.default.createElement(Kebab, null, kebab())),
362
+ react_1.default.createElement(Card, null,
363
+ react_1.default.createElement(CardIcon, null, "\u2709\uFE0F"),
364
+ react_1.default.createElement("div", null,
365
+ react_1.default.createElement(CardTitle, null, "Contact")),
366
+ react_1.default.createElement(Kebab, null, kebab())),
367
+ react_1.default.createElement(SocialRow, null,
368
+ react_1.default.createElement("span", { title: "Social A" }, "in"),
369
+ react_1.default.createElement("span", { title: "Social B" }, "\uD835\uDD4F"),
370
+ react_1.default.createElement("span", { title: "Social C" }, "\uD83D\uDC7B")),
371
+ react_1.default.createElement(Footer, null, "Report \u00B7 Privacy"))))));
372
+ }
373
+ exports.default = Simulator;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.9.1",
2
+ "version": "2.0.0",
3
3
  "commands": {
4
4
  "build": {
5
5
  "id": "build",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/create-link-app",
3
- "version": "1.9.1",
3
+ "version": "2.0.0",
4
4
  "description": "Create a Link App on Linktr.ee.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Linktree",