@lvce-editor/auth-worker 1.4.0 → 1.6.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.
@@ -1127,18 +1127,18 @@ const create = rpcId => {
1127
1127
  };
1128
1128
  };
1129
1129
 
1130
+ const Electron = 2;
1131
+
1130
1132
  const OpenerWorker = 4561;
1131
1133
  const RendererWorker = 1;
1132
1134
 
1133
1135
  const {
1134
- invoke,
1136
+ invoke: invoke$1,
1135
1137
  set: set$1
1136
1138
  } = create(OpenerWorker);
1137
- const openUrl = async (url, platform) => {
1138
- return invoke('Open.openUrl', url, platform);
1139
- };
1140
1139
 
1141
1140
  const {
1141
+ invoke,
1142
1142
  invokeAndTransfer,
1143
1143
  set
1144
1144
  } = create(RendererWorker);
@@ -1178,10 +1178,6 @@ const getBackendAuthUrl = (backendUrl, path) => {
1178
1178
  return `${trimTrailingSlashes(backendUrl)}${path}`;
1179
1179
  };
1180
1180
 
1181
- const getBackendLoginUrl = backendUrl => {
1182
- return getBackendAuthUrl(backendUrl, '/auth/login');
1183
- };
1184
-
1185
1181
  const getLoggedOutBackendAuthState = (authErrorMessage = '') => {
1186
1182
  return {
1187
1183
  authAccessToken: '',
@@ -1359,6 +1355,287 @@ const waitForBackendLogin = async (backendUrl, timeoutMs = 30_000, pollIntervalM
1359
1355
  return getLoggedOutBackendAuthState(lastErrorMessage);
1360
1356
  };
1361
1357
 
1358
+ const successHtml = `<!doctype html>
1359
+ <html lang="en">
1360
+ <head>
1361
+ <meta charset="utf-8">
1362
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1363
+ <title>Authentication Complete</title>
1364
+ <style>
1365
+ :root {
1366
+ color-scheme: light;
1367
+ --background: linear-gradient(180deg, #f4f7fb 0%, #e9eef8 100%);
1368
+ --panel: rgba(255, 255, 255, 0.92);
1369
+ --panel-border: rgba(33, 52, 88, 0.08);
1370
+ --text: #132238;
1371
+ --muted: #5f6f86;
1372
+ --accent: #1f7a5a;
1373
+ --accent-soft: #e7f6ef;
1374
+ --shadow: 0 24px 60px rgba(44, 65, 98, 0.16);
1375
+ }
1376
+
1377
+ * {
1378
+ box-sizing: border-box;
1379
+ }
1380
+
1381
+ html,
1382
+ body {
1383
+ margin: 0;
1384
+ min-height: 100%;
1385
+ font-family: Inter, sans-serif;
1386
+ background: var(--background);
1387
+ color: var(--text);
1388
+ }
1389
+
1390
+ body {
1391
+ display: flex;
1392
+ align-items: center;
1393
+ justify-content: center;
1394
+ padding: 24px;
1395
+ }
1396
+
1397
+ .card {
1398
+ width: min(100%, 460px);
1399
+ padding: 32px 28px;
1400
+ border: 1px solid var(--panel-border);
1401
+ border-radius: 20px;
1402
+ background: var(--panel);
1403
+ box-shadow: var(--shadow);
1404
+ text-align: center;
1405
+ backdrop-filter: blur(12px);
1406
+ }
1407
+
1408
+ .badge {
1409
+ display: inline-flex;
1410
+ align-items: center;
1411
+ justify-content: center;
1412
+ width: 64px;
1413
+ height: 64px;
1414
+ margin-bottom: 20px;
1415
+ border-radius: 999px;
1416
+ background: var(--accent-soft);
1417
+ color: var(--accent);
1418
+ }
1419
+
1420
+ h1 {
1421
+ margin: 0;
1422
+ font-size: 28px;
1423
+ line-height: 1.15;
1424
+ letter-spacing: -0.03em;
1425
+ }
1426
+
1427
+ p {
1428
+ margin: 14px 0 0;
1429
+ font-size: 15px;
1430
+ line-height: 1.6;
1431
+ color: var(--muted);
1432
+ }
1433
+
1434
+ .hint {
1435
+ margin-top: 22px;
1436
+ padding: 12px 14px;
1437
+ border-radius: 12px;
1438
+ background: rgba(19, 34, 56, 0.04);
1439
+ font-size: 14px;
1440
+ }
1441
+
1442
+ .button {
1443
+ margin-top: 20px;
1444
+ border: 0;
1445
+ border-radius: 999px;
1446
+ background: var(--text);
1447
+ color: #fff;
1448
+ padding: 10px 18px;
1449
+ font: inherit;
1450
+ cursor: pointer;
1451
+ }
1452
+
1453
+ .button:hover {
1454
+ background: #0d182a;
1455
+ }
1456
+ </style>
1457
+ </head>
1458
+ <body>
1459
+ <main class="card">
1460
+ <div class="badge" aria-hidden="true">
1461
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" role="presentation">
1462
+ <path d="M20 7L10 17L5 12" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"></path>
1463
+ </svg>
1464
+ </div>
1465
+ <h1>Authentication complete</h1>
1466
+ <p>Your sign-in finished successfully. You can return to the app now.</p>
1467
+ <p class="hint">This window is no longer needed and can be closed.</p>
1468
+ <button class="button" type="button" id="close-button">Close Window</button>
1469
+ </main>
1470
+ <script>
1471
+ const closeWindow = () => {
1472
+ window.close()
1473
+ }
1474
+
1475
+ document.getElementById('close-button')?.addEventListener('click', closeWindow)
1476
+ window.setTimeout(closeWindow, 1200)
1477
+ </script>
1478
+ </body>
1479
+ </html>`;
1480
+ const errorHtml = `<!doctype html>
1481
+ <html lang="en">
1482
+ <head>
1483
+ <meta charset="utf-8">
1484
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1485
+ <title>Authentication Failed</title>
1486
+ <style>
1487
+ :root {
1488
+ color-scheme: light;
1489
+ --background: linear-gradient(180deg, #fbf4f2 0%, #f7e5df 100%);
1490
+ --panel: rgba(255, 255, 255, 0.94);
1491
+ --panel-border: rgba(120, 43, 24, 0.12);
1492
+ --text: #3a1d16;
1493
+ --muted: #7f5a4c;
1494
+ --accent: #b4492d;
1495
+ --accent-soft: #fde9e2;
1496
+ --shadow: 0 24px 60px rgba(96, 48, 32, 0.14);
1497
+ }
1498
+
1499
+ * {
1500
+ box-sizing: border-box;
1501
+ }
1502
+
1503
+ html,
1504
+ body {
1505
+ margin: 0;
1506
+ min-height: 100%;
1507
+ font-family: Inter, sans-serif;
1508
+ background: var(--background);
1509
+ color: var(--text);
1510
+ }
1511
+
1512
+ body {
1513
+ display: flex;
1514
+ align-items: center;
1515
+ justify-content: center;
1516
+ padding: 24px;
1517
+ }
1518
+
1519
+ .card {
1520
+ width: min(100%, 460px);
1521
+ padding: 32px 28px;
1522
+ border: 1px solid var(--panel-border);
1523
+ border-radius: 20px;
1524
+ background: var(--panel);
1525
+ box-shadow: var(--shadow);
1526
+ text-align: center;
1527
+ backdrop-filter: blur(12px);
1528
+ }
1529
+
1530
+ .badge {
1531
+ display: inline-flex;
1532
+ align-items: center;
1533
+ justify-content: center;
1534
+ width: 64px;
1535
+ height: 64px;
1536
+ margin-bottom: 20px;
1537
+ border-radius: 999px;
1538
+ background: var(--accent-soft);
1539
+ color: var(--accent);
1540
+ }
1541
+
1542
+ h1 {
1543
+ margin: 0;
1544
+ font-size: 28px;
1545
+ line-height: 1.15;
1546
+ letter-spacing: -0.03em;
1547
+ }
1548
+
1549
+ p {
1550
+ margin: 14px 0 0;
1551
+ font-size: 15px;
1552
+ line-height: 1.6;
1553
+ color: var(--muted);
1554
+ }
1555
+
1556
+ .hint {
1557
+ margin-top: 22px;
1558
+ padding: 12px 14px;
1559
+ border-radius: 12px;
1560
+ background: rgba(58, 29, 22, 0.05);
1561
+ font-size: 14px;
1562
+ }
1563
+
1564
+ .button {
1565
+ margin-top: 20px;
1566
+ border: 0;
1567
+ border-radius: 999px;
1568
+ background: var(--text);
1569
+ color: #fff;
1570
+ padding: 10px 18px;
1571
+ font: inherit;
1572
+ cursor: pointer;
1573
+ }
1574
+
1575
+ .button:hover {
1576
+ background: #24110d;
1577
+ }
1578
+ </style>
1579
+ </head>
1580
+ <body>
1581
+ <main class="card">
1582
+ <div class="badge" aria-hidden="true">
1583
+ <svg width="28" height="28" viewBox="0 0 24 24" fill="none" role="presentation">
1584
+ <path d="M12 8V12" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"></path>
1585
+ <path d="M12 16H12.01" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"></path>
1586
+ <path d="M10.29 3.86L1.82 18A2 2 0 0 0 3.53 21H20.47A2 2 0 0 0 22.18 18L13.71 3.86A2 2 0 0 0 10.29 3.86Z" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"></path>
1587
+ </svg>
1588
+ </div>
1589
+ <h1>Authentication failed</h1>
1590
+ <p>The sign-in flow did not return an authorization code.</p>
1591
+ <p class="hint">You can close this window and try again from the app.</p>
1592
+ <button class="button" type="button" id="close-button">Close Window</button>
1593
+ </main>
1594
+ <script>
1595
+ const closeWindow = () => {
1596
+ window.close()
1597
+ }
1598
+
1599
+ document.getElementById('close-button')?.addEventListener('click', closeWindow)
1600
+ </script>
1601
+ </body>
1602
+ </html>`;
1603
+
1604
+ const getElectronRedirectUri = async (uid, invoke) => {
1605
+ return `http://localhost:${await invoke('OAuthServer.create', String(uid), successHtml, errorHtml)}`;
1606
+ };
1607
+ const getCurrentHref = async () => {
1608
+ try {
1609
+ return await invoke('Layout.getHref');
1610
+ } catch {
1611
+ // ignore
1612
+ }
1613
+ if (!globalThis.location || typeof globalThis.location.href !== 'string' || !globalThis.location.href) {
1614
+ return '';
1615
+ }
1616
+ return globalThis.location.href;
1617
+ };
1618
+ const getEffectiveRedirectUri = async (platform, uid, redirectUri) => {
1619
+ if (redirectUri) {
1620
+ return redirectUri;
1621
+ }
1622
+ if (platform === Electron) {
1623
+ return getElectronRedirectUri(uid, invoke);
1624
+ }
1625
+ return getCurrentHref();
1626
+ };
1627
+ const getBackendLoginRequest = async (backendUrl, platform = 0, uid = 0, redirectUri = '') => {
1628
+ const loginUrl = new URL(getBackendAuthUrl(backendUrl, '/login'));
1629
+ const effectiveRedirectUri = await getEffectiveRedirectUri(platform, uid, redirectUri);
1630
+ if (effectiveRedirectUri) {
1631
+ loginUrl.searchParams.set('redirect_uri', effectiveRedirectUri);
1632
+ }
1633
+ return {
1634
+ loginUrl: loginUrl.toString(),
1635
+ redirectUri: effectiveRedirectUri
1636
+ };
1637
+ };
1638
+
1362
1639
  const getLoggedInState = (state, response) => {
1363
1640
  const accessToken = typeof response.accessToken === 'string' ? response.accessToken : '';
1364
1641
  return {
@@ -1379,6 +1656,58 @@ const isLoginResponse = value => {
1379
1656
  return true;
1380
1657
  };
1381
1658
 
1659
+ const getBackendNativeExchangeUrl = backendUrl => {
1660
+ return getBackendAuthUrl(backendUrl, '/auth/native/exchange');
1661
+ };
1662
+
1663
+ const getExchangeErrorMessage = async response => {
1664
+ try {
1665
+ const payload = await response.json();
1666
+ if (payload && typeof payload === 'object' && typeof payload.error === 'string' && payload.error) {
1667
+ return payload.error;
1668
+ }
1669
+ } catch {
1670
+ // ignore
1671
+ }
1672
+ return 'Backend authentication failed.';
1673
+ };
1674
+ const exchangeElectronAuthorizationCode = async (backendUrl, code, redirectUri) => {
1675
+ const response = await fetch(getBackendNativeExchangeUrl(backendUrl), {
1676
+ body: JSON.stringify({
1677
+ code,
1678
+ redirectUri
1679
+ }),
1680
+ credentials: 'include',
1681
+ headers: {
1682
+ Accept: 'application/json',
1683
+ 'Content-Type': 'application/json'
1684
+ },
1685
+ method: 'POST'
1686
+ });
1687
+ if (!response.ok) {
1688
+ throw new Error(await getExchangeErrorMessage(response));
1689
+ }
1690
+ };
1691
+
1692
+ const hasAuthorizationCode = value => {
1693
+ return typeof value === 'string' && value.length > 0;
1694
+ };
1695
+ const waitForElectronBackendLogin = async (backendUrl, uid, redirectUri, timeoutMs = 30_000, pollIntervalMs = 1000) => {
1696
+ const started = Date.now();
1697
+ const deadline = started + timeoutMs;
1698
+ while (Date.now() < deadline) {
1699
+ const authorizationCode = await invoke('OAuthServer.getCode', String(uid));
1700
+ if (hasAuthorizationCode(authorizationCode)) {
1701
+ const elapsed = Date.now() - started;
1702
+ const remainingTime = Math.max(0, timeoutMs - elapsed);
1703
+ await exchangeElectronAuthorizationCode(backendUrl, authorizationCode, redirectUri);
1704
+ return waitForBackendLogin(backendUrl, remainingTime, pollIntervalMs);
1705
+ }
1706
+ await delay(pollIntervalMs);
1707
+ }
1708
+ return getLoggedOutBackendAuthState('Timed out waiting for backend login.');
1709
+ };
1710
+
1382
1711
  const handleClickLogin = async options => {
1383
1712
  const {
1384
1713
  backendUrl,
@@ -1411,8 +1740,14 @@ const handleClickLogin = async options => {
1411
1740
  }
1412
1741
  return getLoggedInState(signingInState, response);
1413
1742
  }
1414
- await openUrl(getBackendLoginUrl(backendUrl), platform);
1415
- const authState = await waitForBackendLogin(backendUrl);
1743
+ const uid = 0;
1744
+ const {
1745
+ loginUrl,
1746
+ redirectUri
1747
+ } = await getBackendLoginRequest(backendUrl, platform, uid);
1748
+ const authUseRedirect = false;
1749
+ await invoke$1('Open.openUrl', loginUrl, platform, authUseRedirect);
1750
+ const authState = platform === Electron ? await waitForElectronBackendLogin(backendUrl, uid, redirectUri) : await waitForBackendLogin(backendUrl);
1416
1751
  return {
1417
1752
  ...authState
1418
1753
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/auth-worker",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Auth Worker",
5
5
  "repository": {
6
6
  "type": "git",