@linktr.ee/create-link-app 2.0.0-rc.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.
@@ -35,7 +35,6 @@ const container = document.getElementById('root');
35
35
  const root = (0, client_1.createRoot)(container);
36
36
  function EmbeddedExtension() {
37
37
  const wrapperRef = (0, react_1.useRef)(null);
38
- const [extensionData, setExtensionData] = (0, react_1.useState)(extension_dev_data_1.default);
39
38
  (0, react_1.useEffect)(() => {
40
39
  const postReady = (height) => {
41
40
  const ready = { type: 'extension-ready', data: { ready: true, height } };
@@ -56,15 +55,10 @@ function EmbeddedExtension() {
56
55
  ro.observe(wrapperRef.current);
57
56
  const onMessage = (event) => {
58
57
  if (event.data?.type === 'extension-data') {
59
- const data = event.data?.data || {};
60
58
  // eslint-disable-next-line no-console
61
- console.info('[Extension] received extension-data', data);
62
- setExtensionData((prev) => ({ ...prev, ...data }));
59
+ console.info('[Extension] received extension-data', event.data?.data);
63
60
  }
64
61
  };
65
- // forward interaction events to the parent (parity with production)
66
- const onInteraction = (event) => window.parent.postMessage({ type: 'interaction-event', data: event.detail }, '*');
67
- document.addEventListener('interaction-event', onInteraction);
68
62
  window.addEventListener('message', onMessage);
69
63
  return () => {
70
64
  if (wrapperRef.current) {
@@ -75,12 +69,11 @@ function EmbeddedExtension() {
75
69
  // ignore unobserve errors in dev simulator
76
70
  }
77
71
  }
78
- document.removeEventListener('interaction-event', onInteraction);
79
72
  window.removeEventListener('message', onMessage);
80
73
  };
81
74
  }, []);
82
75
  return (react_1.default.createElement("div", { ref: wrapperRef, style: { display: 'block' } },
83
- react_1.default.createElement(extension_1.default, { ...extensionData })));
76
+ react_1.default.createElement(extension_1.default, { ...extension_dev_data_1.default })));
84
77
  }
85
78
  const App = () => {
86
79
  if (window.location.search === '?embed') {
@@ -30,22 +30,14 @@ const react_1 = __importStar(require("react"));
30
30
  const styled_components_1 = __importStar(require("styled-components"));
31
31
  const iframe_resizer_react_1 = __importDefault(require("iframe-resizer-react"));
32
32
  const extension_dev_data_1 = __importDefault(require("@linktr.ee/extension-dev-data"));
33
- function coerceExtensionData(input) {
34
- if (input !== null && typeof input === 'object' && !Array.isArray(input)) {
35
- return input;
36
- }
37
- return {};
38
- }
39
33
  const Global = (0, styled_components_1.createGlobalStyle) `
40
34
  html, body, #root { height: 100%; }
41
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; }
42
36
  `;
43
37
  const Page = styled_components_1.default.div `
44
38
  min-height: 100vh;
45
- display: flex;
46
- align-items: center;
47
- justify-content: center;
48
- gap: 24px;
39
+ display: grid;
40
+ place-items: center;
49
41
  padding: 24px;
50
42
  box-sizing: border-box;
51
43
  `;
@@ -107,16 +99,6 @@ const ScrollArea = styled_components_1.default.div `
107
99
  padding: 24px 22px 28px;
108
100
  box-sizing: border-box;
109
101
  `;
110
- const SettingsPanel = styled_components_1.default.div `
111
- width: 320px;
112
- max-width: calc(100% - 24px);
113
- background: rgba(255, 255, 255, 0.98);
114
- color: #1f1147;
115
- border-radius: 14px;
116
- box-shadow: 0 10px 24px rgba(0, 0, 0, 0.25);
117
- padding: 12px 14px;
118
- border: 1px solid rgba(0, 0, 0, 0.05);
119
- `;
120
102
  const Profile = styled_components_1.default.div `
121
103
  display: flex;
122
104
  flex-direction: column;
@@ -233,54 +215,7 @@ function kebab() {
233
215
  function Simulator() {
234
216
  const [loading, setLoading] = (0, react_1.useState)(true);
235
217
  const [loadFailed, setLoadFailed] = (0, react_1.useState)(false);
236
- const [settingsOpen, setSettingsOpen] = (0, react_1.useState)(false);
237
- // Allow overriding default dev data via window.postMessage({ type: 'extension-data', data: {...} })
238
- const [extensionData] = (0, react_1.useState)(() => coerceExtensionData(extension_dev_data_1.default));
239
- const DEFAULT_THEME = {
240
- textColor: '#54417e',
241
- backgroundColor: '#f2c2f3',
242
- borderRadius: 'var(--button-style-inner-radius)',
243
- borderColor: '#e9eaeb',
244
- isOutlineStyle: false,
245
- contrastColor: 'black',
246
- textHoverColor: '#54417e',
247
- buttonColor: '#7c3aed',
248
- };
249
- const [theme, setTheme] = (0, react_1.useState)(() => {
250
- try {
251
- const raw = window.localStorage.getItem('simulator:theme');
252
- if (raw)
253
- return { ...DEFAULT_THEME, ...JSON.parse(raw) };
254
- }
255
- catch (e) {
256
- void e;
257
- }
258
- return DEFAULT_THEME;
259
- });
260
- // IframeResizer's React wrapper forwards a proxy object, not the raw iframe
261
- // Store a generic ref and resolve the actual iframe window via a helper
262
218
  const iframeRef = (0, react_1.useRef)(null);
263
- const getIframeWindow = (0, react_1.useCallback)(() => {
264
- const node = iframeRef.current;
265
- if (!node)
266
- return null;
267
- if (node.contentWindow)
268
- return node.contentWindow;
269
- const proxy = node;
270
- if (typeof proxy.getIframeElement === 'function') {
271
- try {
272
- const el = proxy.getIframeElement();
273
- return el?.contentWindow ?? null;
274
- }
275
- catch (e) {
276
- void e;
277
- }
278
- }
279
- const maybeIframe = proxy.iframeResizer?.iframe;
280
- if (maybeIframe?.contentWindow)
281
- return maybeIframe.contentWindow;
282
- return null;
283
- }, []);
284
219
  const loadTimeoutRef = (0, react_1.useRef)(null);
285
220
  const APP_LOAD_TIMEOUT_MILLISECONDS = 8000;
286
221
  const MAX_IFRAME_HEIGHT = 900;
@@ -293,32 +228,34 @@ function Simulator() {
293
228
  }
294
229
  }, []);
295
230
  const initialiseData = (0, react_1.useCallback)(() => {
296
- const cw = getIframeWindow();
297
- if (!cw) {
231
+ if (!iframeRef.current?.contentWindow)
298
232
  return;
299
- }
300
233
  const payload = {
301
234
  type: 'extension-data',
302
235
  data: {
303
- __linkUrl: extensionData?.url ?? '',
304
- __thumbnail: extensionData?.thumbnail ?? '',
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 ?? '',
305
246
  __linkParams: null,
306
247
  displayType: 'accordion',
307
- ...extensionData,
308
- __theme: { ...theme, backgroundColor: theme.buttonColor }, // button color of the host is the background color of the link app
248
+ ...extension_dev_data_1.default,
309
249
  },
310
250
  };
311
- cw.postMessage(payload, targetOrigin);
312
- // cw.postMessage(payload, '*')
313
- }, [targetOrigin, theme, extensionData, getIframeWindow]);
251
+ iframeRef.current.contentWindow.postMessage(payload, targetOrigin);
252
+ }, [targetOrigin]);
314
253
  (0, react_1.useEffect)(() => {
315
254
  const handler = (event) => {
316
- const msg = (event?.data ?? {});
317
- const type = msg.type || '';
255
+ const type = (event?.data && event.data.type) || '';
318
256
  if (!type)
319
257
  return;
320
- const cw = getIframeWindow();
321
- if (cw && event.source !== cw)
258
+ if (iframeRef.current?.contentWindow && event.source !== iframeRef.current.contentWindow)
322
259
  return;
323
260
  switch (type) {
324
261
  case 'extension-loaded': {
@@ -327,21 +264,11 @@ function Simulator() {
327
264
  }
328
265
  case 'extension-ready': {
329
266
  setLoading(false);
330
- // Ensure data is sent if not already
331
267
  try {
332
- initialiseData();
333
- }
334
- catch (e) {
335
- void e;
336
- }
337
- try {
338
- const el = document.getElementById('extension-iframe-sim');
339
- const cw = el?.contentWindow || getIframeWindow();
340
- cw?.focus();
341
- }
342
- catch (e) {
343
- void e;
268
+ const node = document.getElementById('extension-iframe-sim');
269
+ (node || iframeRef.current)?.contentWindow?.focus();
344
270
  }
271
+ catch { }
345
272
  break;
346
273
  }
347
274
  case 'extension-error': {
@@ -349,6 +276,11 @@ function Simulator() {
349
276
  setLoadFailed(true);
350
277
  break;
351
278
  }
279
+ case 'interaction-event': {
280
+ // eslint-disable-next-line no-console
281
+ console.info('[Simulator] interaction-event', event.data?.data);
282
+ break;
283
+ }
352
284
  default:
353
285
  break;
354
286
  }
@@ -369,43 +301,14 @@ function Simulator() {
369
301
  window.clearTimeout(loadTimeoutRef.current);
370
302
  };
371
303
  }, [loading]);
372
- // persist theme and re-post data when changed
373
- (0, react_1.useEffect)(() => {
374
- try {
375
- window.localStorage.setItem('simulator:theme', JSON.stringify(theme));
376
- }
377
- catch (e) {
378
- void e;
379
- }
380
- if (getIframeWindow())
381
- initialiseData();
382
- }, [theme, initialiseData, getIframeWindow]);
383
- // Re-post data when external extension-data overrides change
384
- (0, react_1.useEffect)(() => {
385
- if (getIframeWindow())
386
- initialiseData();
387
- }, [extensionData, initialiseData, getIframeWindow]);
388
304
  const iframeSrc = (0, react_1.useMemo)(() => `${window.location}?embed`, []);
389
- const cardStyle = (0, react_1.useMemo)(() => {
390
- const bg = theme.isOutlineStyle ? 'transparent' : theme.backgroundColor;
391
- const border = `1px solid ${theme.borderColor}`;
392
- const color = theme.textColor;
393
- const radius = theme.borderRadius;
394
- const style = {
395
- background: bg,
396
- color,
397
- border,
398
- borderRadius: radius,
399
- };
400
- return style;
401
- }, [theme]);
402
305
  return (react_1.default.createElement(react_1.default.Fragment, null,
403
306
  react_1.default.createElement(Global, null),
404
307
  react_1.default.createElement(Page, null,
405
- react_1.default.createElement(Phone, { style: { background: theme.backgroundColor, color: theme.textColor } },
308
+ react_1.default.createElement(Phone, null,
406
309
  react_1.default.createElement(Notch, null),
407
310
  react_1.default.createElement(HeaderChrome, null,
408
- react_1.default.createElement(RoundIcon, { title: "Settings", onClick: () => setSettingsOpen((v) => !v), style: { cursor: 'pointer' } }, "\u2699\uFE0F"),
311
+ react_1.default.createElement(RoundIcon, { title: "Settings" }, "\u2699\uFE0F"),
409
312
  react_1.default.createElement(RoundIcon, { title: "Notifications" }, "\uD83D\uDD14")),
410
313
  react_1.default.createElement(ScrollArea, null,
411
314
  react_1.default.createElement(Profile, null,
@@ -414,7 +317,7 @@ function Simulator() {
414
317
  "Sample Creator ",
415
318
  react_1.default.createElement(Badge, { title: "Verified" }, "\u2713")),
416
319
  react_1.default.createElement(Bio, null, "Digital creator")),
417
- react_1.default.createElement(ExtensionCard, { style: cardStyle },
320
+ react_1.default.createElement(ExtensionCard, null,
418
321
  loadFailed && (react_1.default.createElement("div", { style: {
419
322
  position: 'absolute',
420
323
  inset: 0,
@@ -444,28 +347,19 @@ function Simulator() {
444
347
  'allow-forms',
445
348
  ].join(' '), forwardRef: (node) => {
446
349
  iframeRef.current = node;
447
- const cw = getIframeWindow();
448
- if (cw) {
449
- try {
450
- setTimeout(() => initialiseData(), 0);
451
- }
452
- catch (e) {
453
- void e;
454
- }
455
- }
456
350
  } })),
457
- react_1.default.createElement(Card, { style: { background: theme.buttonColor } },
351
+ react_1.default.createElement(Card, null,
458
352
  react_1.default.createElement(CardIcon, null, "\uD83D\uDCBB"),
459
353
  react_1.default.createElement("div", null,
460
354
  react_1.default.createElement(CardTitle, null, "Featured link"),
461
355
  react_1.default.createElement(CardSub, null, "example.com")),
462
356
  react_1.default.createElement(Kebab, null, kebab())),
463
- react_1.default.createElement(Card, { style: { background: theme.buttonColor } },
357
+ react_1.default.createElement(Card, null,
464
358
  react_1.default.createElement(CardIcon, null, "\uD83D\uDCAC"),
465
359
  react_1.default.createElement("div", null,
466
360
  react_1.default.createElement(CardTitle, null, "Follow me")),
467
361
  react_1.default.createElement(Kebab, null, kebab())),
468
- react_1.default.createElement(Card, { style: { background: theme.buttonColor } },
362
+ react_1.default.createElement(Card, null,
469
363
  react_1.default.createElement(CardIcon, null, "\u2709\uFE0F"),
470
364
  react_1.default.createElement("div", null,
471
365
  react_1.default.createElement(CardTitle, null, "Contact")),
@@ -474,36 +368,6 @@ function Simulator() {
474
368
  react_1.default.createElement("span", { title: "Social A" }, "in"),
475
369
  react_1.default.createElement("span", { title: "Social B" }, "\uD835\uDD4F"),
476
370
  react_1.default.createElement("span", { title: "Social C" }, "\uD83D\uDC7B")),
477
- react_1.default.createElement(Footer, null, "Report \u00B7 Privacy"))),
478
- settingsOpen && (react_1.default.createElement(SettingsPanel, null,
479
- react_1.default.createElement("div", { style: { fontWeight: 800, fontSize: 14, marginBottom: 8 } }, "Theme"),
480
- [
481
- ['Text color', 'textColor'],
482
- ['Background', 'backgroundColor'],
483
- ['Border color', 'borderColor'],
484
- ['Hover text', 'textHoverColor'],
485
- ['Contrast color', 'contrastColor'],
486
- ['Button color', 'buttonColor'],
487
- ].map(([label, key]) => (react_1.default.createElement("label", { key: key, style: {
488
- display: 'grid',
489
- gridTemplateColumns: '1fr 120px',
490
- alignItems: 'center',
491
- gap: 10,
492
- fontSize: 12,
493
- margin: '8px 0',
494
- } },
495
- react_1.default.createElement("span", null, label),
496
- react_1.default.createElement("input", { type: "color", value: theme[key], onChange: (e) => setTheme({ ...theme, [key]: e.target.value }), style: { width: 120, height: 28, borderRadius: 8, border: '1px solid #d1d5db', background: '#fff' } })))),
497
- react_1.default.createElement("label", { style: {
498
- display: 'grid',
499
- gridTemplateColumns: '1fr 120px',
500
- alignItems: 'center',
501
- gap: 10,
502
- fontSize: 12,
503
- margin: '8px 0',
504
- } },
505
- react_1.default.createElement("span", null, "Outline style"),
506
- react_1.default.createElement("div", { style: { display: 'flex', justifyContent: 'flex-end' } },
507
- react_1.default.createElement("input", { type: "checkbox", checked: theme.isOutlineStyle, onChange: (e) => setTheme({ ...theme, isOutlineStyle: e.target.checked }) }))))))));
371
+ react_1.default.createElement(Footer, null, "Report \u00B7 Privacy"))))));
508
372
  }
509
373
  exports.default = Simulator;
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.0.0-rc.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": "2.0.0-rc.1",
3
+ "version": "2.0.0",
4
4
  "description": "Create a Link App on Linktr.ee.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "Linktree",
@@ -13,8 +13,7 @@
13
13
  "@linktr.ee/create-link-app": "latest",
14
14
  "@linktr.ee/ui-link-kit": "latest",
15
15
  "@types/react": "^18.2.8",
16
- "@types/react-dom": "^18.2.4",
17
- "iframe-resizer-react": "^1.1.1"
16
+ "@types/react-dom": "^18.2.4"
18
17
  },
19
18
  "main": "lib/commonjs/index.js",
20
19
  "react-native": "src/index.ts",